From af45cd0e518467861b8031fa40cfa08c89857a5e Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 14:26:46 +0100 Subject: [PATCH 1/6] perf: replace dis.get_instructions with direct co_code parsing in from_code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dis.get_instructions performs two full passes over the bytecode: - _make_labels_map → findlabels → _unpack_opargs (to build a jump-label map) - _get_instructions_bytes (to iterate instructions with full metadata) Neither pass is needed here. ConcreteBytecode.from_code only needs the opname, raw arg byte, and source positions for each instruction word — all of which are directly available from co_code and co_positions(). CACHE entries are already inline in co_code on all supported Python versions, so direct 2-byte iteration handles them naturally without the per-version cache_info loop that 3.13 previously required. Throughput (round-trips of Bytecode.from_code().to_code() on the dis module's own code object, timed over 1 second, 3 runs each): Before: 92–94 round-trips/s After: 107–111 round-trips/s (~+17%) Austin CPU profile figures: dis._unpack_opargs: 5.98% own → eliminated dis._get_instructions_bytes: 3.45% own → eliminated ConcreteBytecode.from_code: 3.63% own → 4.91% own --- src/bytecode/concrete.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index 6a21d186..e1304c66 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -336,21 +336,27 @@ def from_code( code: types.CodeType, *, extended_arg: bool = False ) -> ConcreteBytecode: instructions: MutableSequence[Union[SetLineno, ConcreteInstr]] = [] - for i in dis.get_instructions(code, show_caches=True): - loc = InstrLocation.from_positions(i.positions) if i.positions else None - # dis.get_instructions automatically handle extended arg which - # we do not want, so we fold back arguments to be between 0 and 255 - instructions.append( - ConcreteInstr( - i.opname, - i.arg % 256 if i.arg is not None else UNSET, - location=loc, + bc = code.co_code + opname = _opcode.opname + # co_positions() yields one (lineno, end_lineno, col_offset, + # end_col_offset) per instruction word (including CACHE entries), + # available from Python 3.11+. CACHE entries are already inline in + # co_code on all supported versions, so iterating co_code directly + # handles all versions without dis overhead. + pos_iter: Optional[ + Iterator[Tuple[Optional[int], Optional[int], Optional[int], Optional[int]]] + ] = iter(code.co_positions()) if hasattr(code, "co_positions") else None + for offset in range(0, len(bc), 2): + op = bc[offset] + arg = bc[offset + 1] if opcode_has_argument(bc[offset]) else UNSET + if pos_iter is not None: + pos = next(pos_iter, None) + loc: Optional[InstrLocation] = ( + InstrLocation(*pos) if pos is not None else None ) - ) - # cache_info only exist on 3.13+ - for _, size, _ in (i.cache_info or ()) if PY313 else (): # type: ignore - for _ in range(size): - instructions.append(ConcreteInstr("CACHE", 0, location=loc)) + else: + loc = None + instructions.append(ConcreteInstr(opname[op], arg, location=loc)) bytecode = ConcreteBytecode() From daa1193d1a9b25b85a9f0683a452905f04868265 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 15:48:12 +0100 Subject: [PATCH 2/6] Update src/bytecode/concrete.py Co-authored-by: Matthieu Dartiailh --- src/bytecode/concrete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index e1304c66..9d502ba9 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -348,7 +348,7 @@ def from_code( ] = iter(code.co_positions()) if hasattr(code, "co_positions") else None for offset in range(0, len(bc), 2): op = bc[offset] - arg = bc[offset + 1] if opcode_has_argument(bc[offset]) else UNSET + arg = bc[offset + 1] if opcode_has_argument(op) else UNSET if pos_iter is not None: pos = next(pos_iter, None) loc: Optional[InstrLocation] = ( From 483c6832985bbe56c5117274411ea154ebb7c790 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 15:53:27 +0100 Subject: [PATCH 3/6] assume co_positions always available --- src/bytecode/concrete.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index 9d502ba9..d330277f 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -343,19 +343,15 @@ def from_code( # available from Python 3.11+. CACHE entries are already inline in # co_code on all supported versions, so iterating co_code directly # handles all versions without dis overhead. - pos_iter: Optional[ - Iterator[Tuple[Optional[int], Optional[int], Optional[int], Optional[int]]] - ] = iter(code.co_positions()) if hasattr(code, "co_positions") else None + pos_iter: Iterator[ + Tuple[Optional[int], Optional[int], Optional[int], Optional[int]] + ] = iter(code.co_positions()) for offset in range(0, len(bc), 2): - op = bc[offset] - arg = bc[offset + 1] if opcode_has_argument(op) else UNSET - if pos_iter is not None: - pos = next(pos_iter, None) - loc: Optional[InstrLocation] = ( - InstrLocation(*pos) if pos is not None else None - ) - else: - loc = None + arg = bc[offset + 1] if opcode_has_argument(op := bc[offset]) else UNSET + pos = next(pos_iter, None) + loc: Optional[InstrLocation] = ( + InstrLocation(*pos) if pos is not None else None + ) instructions.append(ConcreteInstr(opname[op], arg, location=loc)) bytecode = ConcreteBytecode() From 7b483f5ef8d2b785cda9973221db25f701b59753 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 18:30:48 +0100 Subject: [PATCH 4/6] perf: bypass validation for trusted InstrLocation and Instr construction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two fast-path factory methods that skip validation by using object.__new__ + direct slot assignment, for call sites where the inputs are already known to be valid: **InstrLocation._from_tuple** — replaces InstrLocation(...) at four internal sites where positions come from trusted sources (existing InstrLocation.lineno, SetLineno.lineno, first_lineno): - ConcreteBytecode.to_bytecode (fallback lineno-only location) - ConcreteBytecode._pack_location (propagated from existing location) - _ConvertBytecodeToConcrete.concrete_instructions (first_lineno seed and SetLineno-derived locations) **BaseInstr._from_trusted** — replaces Instr(name, arg, location=loc) in ConcreteBytecode.to_bytecode, where name/opcode/arg/location are all derived from already-validated ConcreteInstr objects. CPU own-time profile data: | Hotspot | Before | After | |---|---|---| | `ConcreteBytecode.to_bytecode` | 5.98% | 5.07% | | `Instr._check_arg` | 2.87% | eliminated | | `BaseInstr._set` (via to_bytecode) | 1.48% | eliminated | | `BaseInstr._from_trusted` | — | <1% (not in top 20) | Throughput (Bytecode.from_code().to_code() on dis module's code object, 1 second timed window, 5 runs): | | r/s range | |---|---| | Before | 103–108 | | After | 109–114 | --- src/bytecode/concrete.py | 18 +++++++++++++----- src/bytecode/instr.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index 6a21d186..f00a8f10 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -575,7 +575,9 @@ def _assemble_locations( _, size, lineno, old_location = next(iter_in) # Infer the line if location is None - old_location = old_location or InstrLocation(lineno, None, None, None) + old_location = old_location or InstrLocation._from_tuple( + lineno, None, None, None + ) lineno = first_lineno # We track the last set lineno to be able to compute deltas @@ -930,14 +932,18 @@ def to_bytecode( else: arg = c_arg - location = c_instr.location or InstrLocation(lineno, None, None, None) + location = c_instr.location or InstrLocation._from_tuple( + lineno, None, None, None + ) if jump_target is not None: arg = PLACEHOLDER_LABEL instr_index = len(instructions) jumps.append((instr_index, jump_target)) - instructions.append(Instr(c_instr.name, arg, location=location)) + instructions.append( + Instr._from_trusted(c_instr._name, c_instr._opcode, arg, location) + ) # We now insert the TryEnd entries if current_instr_offset in ex_end: @@ -1030,7 +1036,9 @@ def add(names: list[str], name: str) -> int: return index def concrete_instructions(self) -> None: - location = InstrLocation(self.bytecode.first_lineno, None, None, None) + location = InstrLocation._from_tuple( + self.bytecode.first_lineno, None, None, None + ) # Track instruction (index) using cell vars and free vars to be able to update # the index used once all the names are known. cell_instrs: list[int] = [] @@ -1086,7 +1094,7 @@ def concrete_instructions(self) -> None: continue if isinstance(instr, SetLineno): - location = InstrLocation(instr.lineno, None, None, None) + location = InstrLocation._from_tuple(instr.lineno, None, None, None) continue if isinstance(instr, TryBegin): diff --git a/src/bytecode/instr.py b/src/bytecode/instr.py index 9e043f3e..56240eac 100644 --- a/src/bytecode/instr.py +++ b/src/bytecode/instr.py @@ -621,6 +621,22 @@ def from_positions(cls, position: dis.Positions) -> InstrLocation: # type: igno position.end_col_offset, ) + @classmethod + def _from_tuple( + cls, + lineno: Optional[int], + end_lineno: Optional[int], + col_offset: Optional[int], + end_col_offset: Optional[int], + ) -> InstrLocation: + """Fast path for trusted position data (e.g. from co_positions()).""" + new = object.__new__(cls) + object.__setattr__(new, "lineno", lineno) + object.__setattr__(new, "end_lineno", end_lineno) + object.__setattr__(new, "col_offset", col_offset) + object.__setattr__(new, "end_col_offset", end_col_offset) + return new + class SetLineno: __slots__ = ("_lineno",) @@ -819,6 +835,22 @@ def copy(self: T) -> T: new._location = self._location return new + @classmethod + def _from_trusted( + cls: type[T], + name: str, + opcode: int, + arg: A, + location: Optional[InstrLocation], + ) -> T: + """Fast path for internal construction from already-validated data.""" + new = object.__new__(cls) + new._name = name + new._opcode = opcode + new._arg = arg + new._location = location + return new + def has_jump(self) -> bool: return self._has_jump(self._opcode) From 72fcf31cd1b5b715467aa06f98241900dab1b76d Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 19:50:24 +0100 Subject: [PATCH 5/6] use faster _from_tuple --- src/bytecode/concrete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index e799a0d5..2158cbcb 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -350,7 +350,7 @@ def from_code( arg = bc[offset + 1] if opcode_has_argument(op := bc[offset]) else UNSET pos = next(pos_iter, None) loc: Optional[InstrLocation] = ( - InstrLocation(*pos) if pos is not None else None + InstrLocation._from_tuple(*pos) if pos is not None else None ) instructions.append(ConcreteInstr(opname[op], arg, location=loc)) From 9f53642877475f3603d28036bd902ab05804f5a3 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 8 May 2026 22:11:13 +0100 Subject: [PATCH 6/6] undo walrus --- src/bytecode/concrete.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index 2158cbcb..9826a7e0 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -347,7 +347,8 @@ def from_code( Tuple[Optional[int], Optional[int], Optional[int], Optional[int]] ] = iter(code.co_positions()) for offset in range(0, len(bc), 2): - arg = bc[offset + 1] if opcode_has_argument(op := bc[offset]) else UNSET + op = bc[offset] + arg = bc[offset + 1] if opcode_has_argument(op) else UNSET pos = next(pos_iter, None) loc: Optional[InstrLocation] = ( InstrLocation._from_tuple(*pos) if pos is not None else None