Annotation Interface Fixed


@Target(PARAMETER) @Retention(SOURCE) public @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
    Modifier and Type
    Class
    Description
    static enum 
    Defines the parameter scope
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    Should this attribute be in the Evaluator's toString?
    boolean
    Adopts JIT-time constant folding for this parameter.
    Defines the scope of the parameter.
  • Element Details

    • includeInToString

      boolean includeInToString
      Should this attribute be in the Evaluator's toString?
      Default:
      true
    • scope

      Defines 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 jitConstant
      Adopts 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 via org.elasticsearch.compute.operator.ConstantMethodResultSpecializer with the value baked in as static 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

      1. 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.
      2. 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 Standard subclass (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 (rhs in MOD x BY 60, prefix in STARTS_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 in sweep/AdmissionStress.java.
      3. Only valid on parameters with SINGLETON scope. THREAD_LOCAL parameters cannot use this flag.

      When to use jitConstant = true

      Adopt only when measurement shows a clear win in steady-state. Two tiers:

      Tier 1 — parameter was previously *variable per row* (no @Fixed before). Adoption saves one Block fetch 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-row Block fetch 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_LOCAL scope (spinner is SINGLETON-only).
      Default:
      false