You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Improving trace quality by tracking "fitness" and "exit quality" #146073
The performance of the JIT depends heavily on the quality of the traces that the front-end produces. If traces are too short, very long, overlap a lot, or drop into the interpreter too often, performance suffers.
To avoid those cases of poor performance, we should track two values during tracing:
The fitness of the current trace
The quality of the end of the trace as a point to stop the trace.
If fitness < exit_quality we should stop the trace.
Fitness:
Fitness should:
start high
be reduced by branches, how much depending on the bias of branch
be reduced (by quite a lot) by backward edges
be reduced a small amount by each instruction (to discourage excessively long traces)
start lower for non-branch side exits to reduce code replication
Exit quality
Exit quality should be high for:
The starting point of the trace (very high for this)
ENTER_EXECUTOR instructions
Merge points in the control flow graph (will need the bytecode compiler to generate this data)
Exit quality should be low for:
Specializable instructions
Has this already been discussed elsewhere?
This is a minor feature, which does not need previous discussion elsewhere
Hi @Fidget-Spinner@markshannon. I wrote some details about the fitness/exit quality mechanism, I’d love to hear your thoughts or suggestions on this.
Proposal Details
Fitness
Decreases monotonically from an initial value, with additional consumption in specific scenarios:
Each bytecode instruction: Minor deduction (e.g., 2)
Conditional branches: The amount deducted is determined by the bias of RECORD_BRANCH_TAKEN—branches that are fully predictable incur almost no deduction, while 50%/50% branches incur a significant deduction
Backward jumps (JUMP_BACKWARD series): Significant deduction, as a backward jump means the trace enters the second iteration of a loop, and the benefits diminish as it continues
Function Call Inlining: Should be frame depth-dependent; deeper nesting incurs heavier penalties
Root traces and side traces should have different initial values—since side traces are derived from exits, they should be allocated fewer “stamina” points.
Exit Quality
Evaluates how good this location would be as an exit point if the tracer were to stop here:
ENTER_EXECUTOR position (Best): A compiled executor is already available; stopping here allows for direct execution.
Ordinary bytecode location (Normal): Neither particularly good nor particularly bad
Specializable instruction (Poor): Stopping here means the tracer must re-specialize the instruction the next time it reaches this point
Decision Rule
fitness < exit_quality
Last Known Good Exit
The tracer should continuously record the best exit encountered during tracing. When it needs to stop, it should prioritize reverting to that position rather than stopping at the current location, which might be suboptimal.
This mechanism can also improve existing unsupported and trace_too_long paths—when these situations occur, the tracer can revert to a previously encountered good position.
Implementation
Phase 1: Core Mechanism
Minimum viable version, enabling the tracer to “actively stop at good positions”:
Implement structures for fitness, best exit, frame depth, etc.
@cocolato
I'd suggest coming up with a set of inequalities to make sure that the fitness values do not break any invariants.
For example:
fitness at MAX_ABSTRACT_FRAME_DEPTH - 1 < min(exit_quality). Note we can change MAX_ABSTRACT_FRAME_DEPTH if needed (although it is probably too high already)
The fitness after a backwards edge + N instructions == exit_quality of the start point. Choose N to allow flow into loops, but avoid excess unrolling, maybe some where in the range of 20 to 50?
Fitness after ~3 perfectly balanced branches should < exit_quality of a "good" exit, and after ~4 such branches it should be less than the exit_quality of a "normal instruction" (to avoid too many overlapping traces)
We also need some guidelines to ensure that we get good traces, for example:
The fitness after a call and return should be close to the fitness if neither call nor return were present.
Avoid reasons other than fitness and exit quality for ending traces unless unavoidable (like oparg > 0xffff)
Aside:
Only a few instructions can have oparg > 0xffff and they are all branches, packing or unpacking instructions. The branches get converted into exits (with a target not an oparg), so it is really only the packing and unpacking instructions that are a problem. In order to keep stack usage under control, we should eliminate those instructions in the bytecode compiler anyway.
Starting fitness
We want the fitness to allow a few 100s of uops in an optimized trace with branches.
The optimizer will leave about half of the uops (it will leave fewer as we improve it).
Given OPTIMIZER_EFFECTIVENESS = estimate_of(uops_in_recorded_trace/uops_in_optimzed_trace)
A good starting fitness would be MAX_TARGET_LENGTH * OPTIMIZER_EFFECTIVENESS.
I'd suggest MAX_TARGET_LENGTH = 400 to start with, but we can easily change it.
The fitness of side exits traces should probably not be much lower than a rooted trace, but maybe could be lower for longer chains. Maybe (8-chain_depth) * BASE / 8?
Other changes
You might need to remove the close_loop_instr and jump_backward_instr attributes and rely only on the start_instr field for detecting loops. The fitness should prevent overly long traces and excessive loop unrolling.
Activity
Fidget-Spinner commentedon Mar 24, 2026
Note: we should also consider frame depth in fitness. #146368
henry-the-frog commentedon Mar 25, 2026
Fidget-Spinner commentedon Mar 31, 2026
@cocolato is doing this apparently. In the future, please let us know if you want to take an issue!
cocolato commentedon Mar 31, 2026
Hi @Fidget-Spinner @markshannon. I wrote some details about the
fitness/exit qualitymechanism, I’d love to hear your thoughts or suggestions on this.Proposal Details
Fitness
Decreases monotonically from an initial value, with additional consumption in specific scenarios:
RECORD_BRANCH_TAKEN—branches that are fully predictable incur almost no deduction, while 50%/50% branches incur a significant deductionJUMP_BACKWARDseries): Significant deduction, as a backward jump means the trace enters the second iteration of a loop, and the benefits diminish as it continuesRoot traces and side traces should have different initial values—since side traces are derived from exits, they should be allocated fewer “stamina” points.
Exit Quality
Evaluates how good this location would be as an exit point if the tracer were to stop here:
ENTER_EXECUTORposition (Best): A compiled executor is already available; stopping here allows for direct execution.Decision Rule
fitness < exit_qualityLast Known Good Exit
The tracer should continuously record the best exit encountered during tracing. When it needs to stop, it should prioritize reverting to that position rather than stopping at the current location, which might be suboptimal.
This mechanism can also improve existing
unsupportedandtrace_too_longpaths—when these situations occur, the tracer can revert to a previously encountered good position.Implementation
Phase 1: Core Mechanism
Minimum viable version, enabling the tracer to “actively stop at good positions”:
update_trace_fitness()— decrement per frame + branch bias penalty + back-edge penaltyPhase 2: Tuning and Enhancement
Further optimization based on real-world data from Phase 1:
Phase 3: Long-Term Evolution
gh-146073: Add fitness/exit quality mechanism for JIT trace frontend (G…
Revert "pythongh-146073: Add fitness/exit quality mechanism for JIT t…
gh-146073: Revert "gh-146073: Add fitness/exit quality mechanism for …
markshannon commentedon Apr 7, 2026
@cocolato
I'd suggest coming up with a set of inequalities to make sure that the fitness values do not break any invariants.
For example:
MAX_ABSTRACT_FRAME_DEPTH - 1<min(exit_quality). Note we can changeMAX_ABSTRACT_FRAME_DEPTHif needed (although it is probably too high already)exit_qualityof the start point. Choose N to allow flow into loops, but avoid excess unrolling, maybe some where in the range of 20 to 50?exit_qualityof a "good" exit, and after ~4 such branches it should be less than theexit_qualityof a "normal instruction" (to avoid too many overlapping traces)We also need some guidelines to ensure that we get good traces, for example:
Aside:
Starting fitness
We want the fitness to allow a few 100s of uops in an optimized trace with branches.
The optimizer will leave about half of the uops (it will leave fewer as we improve it).
Given
OPTIMIZER_EFFECTIVENESS = estimate_of(uops_in_recorded_trace/uops_in_optimzed_trace)A good starting fitness would be
MAX_TARGET_LENGTH * OPTIMIZER_EFFECTIVENESS.I'd suggest
MAX_TARGET_LENGTH = 400to start with, but we can easily change it.The fitness of side exits traces should probably not be much lower than a rooted trace, but maybe could be lower for longer chains. Maybe
(8-chain_depth) * BASE / 8?Other changes
You might need to remove the
close_loop_instrandjump_backward_instrattributes and rely only on thestart_instrfield for detecting loops. The fitness should prevent overly long traces and excessive loop unrolling.GH-146073: Add example script for dumping JIT traces (GH-148840)
gh-146073: Add fitness/exit quality mechanism for JIT trace frontend (G…
pythongh-146073: Add fitness/exit quality mechanism for JIT trace fro…
pythongh-146073: Revert "pythongh-146073: Add fitness/exit quality me…
pythonGH-146073: Add example script for dumping JIT traces (pythonGH-…
pythongh-146073: Add fitness/exit quality mechanism for JIT trace fro…
GH-146073: Add fitness to executor dumps. (GH-148959)
markshannon commentedon Sep 23, 2026
This is done well enough, and given the JIT hiatus and general rethink, I'm closing this.