Skip to content

perf: index stack-held sequences and read closure cells in line - #87

Merged
owenthcarey merged 1 commit into
mainfrom
perf/jit-coverage
Oct 22, 2026
Merged

owenthcarey merged 1 commit into
mainfrom
perf/jit-coverage

Conversation

@owenthcarey

Copy link
Copy Markdown
Contributor

Part of the plan in docs/PERFORMANCE-PROGRESS.md (on #86): fewer helper calls from compiled code.

Two shapes common in generator expressions and closures left the frame JIT's native code for a helper on every execution:

  • c[i] with c on the operand stack (a closure variable, an attribute, a call's result) went through h_subscr_ref. The in-line list and tuple index (seq_index) took only a local container; it now also takes one the stack holds, when another owner keeps it alive (it leaves by a plain decrement). A container the release would free still takes the helper.
  • LOAD_DEREF called h_load_deref. It now reads the cell in line through a CellLayout probed at startup, like the list and tuple layouts (the frame's cells vector, the cell's borrow counter and its value), falling back to the helper for an index out of range, a borrowed cell, or an unbound value.

Measurements

Instructions retired per steady-state call (quick build, pinned hash seed), against main:

Workload Ratio
nqueens 0.958
sudoku 0.985
generators_tree 0.990
closures_decorators 0.995
comprehensions 0.995
chaos 0.996

len(set(vec[i] + i for i in cols)) with vec a closure variable retires 4.7% fewer instructions.

Validation

  • Bundled regression suite: 422/422.

Two shapes in generator expressions and closures left compiled code
for a helper on every execution:

- c[i] with c on the operand stack (a closure variable, an attribute,
  a call's result) rather than a local went through h_subscr_ref.
  The in-line list and tuple index now also takes a stack-held
  container that another owner keeps alive, which leaves by a plain
  decrement; a container the release would free still takes the
  helper.
- LOAD_DEREF called h_load_deref. It now reads the cell in line,
  through a CellLayout probed at startup like the list and tuple
  layouts: the frame's cells vector, the cell's borrow counter and its
  value, with the helper for an index out of range, a borrowed cell,
  or an unbound value.

len(set(vec[i] + i for i in cols)) retires 4.7% fewer instructions,
nqueens 4.2%, sudoku 1.5%, generators_tree 1%.
@owenthcarey
owenthcarey merged commit a76919e into main Oct 22, 2026
28 checks passed
@owenthcarey
owenthcarey deleted the perf/jit-coverage branch October 22, 2026 20:51
owenthcarey added a commit that referenced this pull request Oct 22, 2026
Brings in #87 to #91. The conflict with #91's chunked frame compiles in
`Lower` keeps both: `never_ran` is computed from `starts` before
`self.starts = starts`.

Also fixes two problems the branch's CI found:

- Windows builds failed on `make_struct_time_tz`, which only the Unix
  `localtime` path calls (a dead-code error under `-D warnings`); it is
  now `#[cfg(unix)]`.
- The `dict_ops` bench fixture ran 1.27x the merge base's instructions
  (CI: 1.32x on Linux, 1.43x on macOS). Its `bench` compiles at a back
  edge of its first loop, during its only activation, so the later loops
  had not run yet and their blocks looked never-run (their `isinstance`
  and `list` reads were unresolved): they ran in the core loop. Blocks
  are now left out only for code compiled after finishing many
  activations (call heat); code a loop made hot compiles whole.
  dict_ops now matches main (591.2M vs 590.5M instructions).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant