Package org.elasticsearch.compute.ann
Annotation Interface Fixed
Used on parameters on methods annotated with
Evaluator to indicate
parameters that are provided to the generated evaluator's constructor rather
than recalculated for every row.-
Nested Class Summary
Nested Classes -
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanShould this attribute be in the Evaluator'stoString?booleanAdopts JIT-time constant folding for this parameter.Defines the scope of the parameter.
-
Element Details
-
includeInToString
boolean includeInToStringShould this attribute be in the Evaluator'stoString?- Default:
true
-
scope
Fixed.Scope scopeDefines the scope of the parameter. - SINGLETON (default) will build a single instance and share it across all evaluators - THREAD_LOCAL will build a new instance for each evaluator thread- Default:
SINGLETON
-
jitConstant
boolean jitConstantAdopts JIT-time constant folding for this parameter. The annotation processor emits a class shape where the generated evaluator becomes abstract over this parameter, and the Factory materialises a per-distinct-value subclass viaorg.elasticsearch.compute.operator.ConstantMethodResultSpecializerwith the value baked in asstatic final(primitive) or class-data condy (reference). This unlocks C2's constant-folding optimizations including Granlund-Montgomery strength reduction for integer divide / modulo.READ THIS FIRST — three rules, in order
- Performance testing is mandatory. Never adopt this flag based on theory. Add JMH cases for both the constant-folded path and the variable baseline; measure on at least three microarchitectures (Apple M, an x86 server, an aarch64 server). Mac alone can mislead — Apple Silicon has aggressive uop fusion that masks both wins and losses present on server CPUs. See PR #148678 for the calibration table covering 11 attempted adoptions; the framework's value is making this experiment cheap, not guaranteeing wins.
- Cardinality is bounded by the admission filter + GC. The specializer's
admission filter (default threshold = 2) refuses to spin for first-time
keys — the codegen Factory routes them to a
Standardsubclass (regular instance field, no JIT folding, runs slower but runs). Repeat keys spin once and the class lives only as long as some live evaluator references it (weak-referenced in the cache; GC reclaims when unused). This is great for parameters with low distinct-value counts (rhsinMOD x BY 60,prefixinSTARTS_WITH(s, "foo"), a fixed regex pattern). It is still sub-optimal for parameters that could be user-supplied literals varying per session, query, or field — the admission filter prevents the specialization tax (each spin is ~13 μs in steady state, post-JIT-warmup), but those queries pay the no-JIT-folding cost for their first execution. Flag parameters whose values are bounded in practice — typically ≤thousands per cluster lifetime — for best results. If in doubt, measure with the stress harness insweep/AdmissionStress.java. - Only valid on parameters with SINGLETON scope.
THREAD_LOCALparameters cannot use this flag.
When to use
Adopt only when measurement shows a clear win in steady-state. Two tiers:jitConstant = trueTier 1 — parameter was previously *variable per row* (no
@Fixedbefore). Adoption saves oneBlockfetch per row (~1 ns) plus whatever inner-loop optimization the constant unlocks. Wins typically come from:- Inner work does integer divide or modulo by the parameter — Granlund-Montgomery folds 5-10 cycles into 2-3 (e.g. MOD/DIV: 3-4x).
- Inner work calls a method whose length/shape depends on the constant, letting C2 specialize a loop bound or unroll (e.g. STARTS_WITH/ENDS_WITH on a fixed prefix/suffix: ~2x).
- Inner work devirtualizes through the constant receiver to a non-trivial method body, and the inner method is worth more than ~2 ns per call to inline.
Tier 2 — parameter was already
@Fixed(regular field, no per-rowBlockfetch to save). Adoption costs ~2 ns of abstract-accessor dispatch overhead with the only return being inner-loop optimization. The bar is much higher. Only adopt when there is a concrete multi-cycle unlock (deep call-chain devirtualization, hot-loop branch elimination, method-internal constant folding worth >2 ns). AutomataMatch (powers RLIKE) was tried this way and regressed 23% — the DFA walk didn't recover the dispatch tax.When NOT to use
jitConstant = true- High-cardinality parameter (see rule 2 above). Will thrash the cache and bloat metaspace.
- Per-row inner work is one ALU op (a CMP, an ADD, a MUL): dispatch tax will dominate. Predicted regression.
- Per-row inner work is one cheap virtual call with no GM-class optimization
to recover dispatch (e.g.
Rounding.Prepared.round()). DateTrunc was tried this way and went from 0.6 ns/op to 2.5 ns/op (4x slower). - Per-row inner work is dominated by an intrinsic (
Math.round,Math.log10,Math.exp,Math.pow). C2 already handles these; the strength-reduction gain is in the noise. - Per-row work is dominated by allocation, IO, or string parsing (JSON, regex). Adoption is at best neutral.
- The parameter would require
THREAD_LOCALscope (spinner is SINGLETON-only).
- Default:
false
-