From fa35a70d3608ba7ff553e07bac7aa7f84dfbd557 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 22 May 2026 10:58:24 +0100 Subject: [PATCH] perf: cache size and jump_target from first pass in to_bytecode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ConcreteBytecode.to_bytecode` makes two passes over the instruction list. The first pass (finding jump targets) already calls `c_instr.get_jump_target(offset)` and `c_instr.size` for every instruction. The main loop then called both again — redundantly, at the same offsets. This change stashes `(size, jump_target)` per instruction into a list during the first pass and consumes it via an iterator in the main loop, eliminating all second calls to `get_jump_target` and `size`. | Hotspot | Before | After | |---|---|---| | `ConcreteBytecode.to_bytecode` own | 9.56% | 8.29% | | `ConcreteBytecode.to_bytecode` total | 19.55% | 15.55% | | `ConcreteInstr.get_jump_target` total | 2.74% | gone (< top 20) | | `ConcreteInstr.size` total | 1.30% | gone (< top 20) | | | Range | Avg | |---|---|---| | Before | 214–226 r/s | ~221 r/s | | After | 224–232 r/s | ~229 r/s | ~3.5% throughput improvement. --- src/bytecode/concrete.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index 144e6314..193c4fd2 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -782,16 +782,19 @@ def to_bytecode( c_instructions = self[:] self._remove_extended_args(c_instructions) - # Find jump targets + # Find jump targets; stash (size, jump_target) to avoid recomputing in the main loop jump_targets: Set[int] = set() + _instr_props: List[Tuple[int, Optional[int]]] = [] offset = 0 for c_instr in c_instructions: if isinstance(c_instr, SetLineno): continue + size = c_instr.size target = c_instr.get_jump_target(offset) + _instr_props.append((size, target)) if target is not None: jump_targets.add(target) - offset += c_instr.size // 2 + offset += size // 2 # On 3.11+ we need to also look at the exception table for jump targets for ex_entry in self.exception_table: @@ -839,6 +842,7 @@ def to_bytecode( else: locals_lookup = self.varnames + _props_iter = iter(_instr_props) for lineno, c_instr in self._normalize_lineno( c_instructions, self.first_lineno ): @@ -864,8 +868,7 @@ def to_bytecode( tb_instrs[entry] = tb_instr instructions.append(tb_instr) - jump_target = c_instr.get_jump_target(offset) - size = c_instr.size + size, jump_target = next(_props_iter) # If an instruction uses extended args, those appear before the instruction # causing the instruction to appear at offset that accounts for extended # args. So we first update the offset to account for extended args, then