Skip to content

Commit 41c679b

Browse files
authored
perf: bypass ConcreteInstr validation in concrete_instructions (#201)
Two changes to the hot encode loop in _ConvertBytecodeToConcrete.concrete_instructions: 1. ConcreteInstr._from_trusted factory ConcreteInstr(instr_name, c_arg, location=location) at the bottom of the loop went through the full __init__ -> _check_arg -> _set chain on every instruction, even though name/opcode/arg/location are all derived from already-validated Instr objects. A new _from_trusted classmethod bypasses _check_arg entirely and replicates only the size computation from _set, using object.__new__ + direct slot assignment — the same pattern as the existing _from_opcode fast path. 2. Direct private slot access instr.location, instr.name, and instr.arg are properties whose bodies are each a single "return self._xxx". Accessing instr._location, instr._name, and instr._arg directly avoids the property descriptor lookup on every iteration. This is safe because instr is a validated Instr instance at this point (guarded by the assert isinstance check above). Profile data | Hotspot | Before | After | |---|---|---| | concrete_instructions own | 8.07% | 5.44% | | concrete_instructions total | 11.71% | 9.44% | | ConcreteInstr.__init__ (child) | 1.30% total | gone | Throughput (Bytecode.from_code().to_code() on dis module, 30 runs each) | | Avg | Stddev | |---|---|---| | Before | 223.7 r/s | ±4.7 | | After | 246.1 r/s | ±3.3 | +22.4 r/s / +10.0% throughput improvement.
1 parent bd74da9 commit 41c679b

1 file changed

Lines changed: 31 additions & 5 deletions

File tree

src/bytecode/concrete.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,30 @@ def _from_opcode(
199199
new._size = 2
200200
return new
201201

202+
@classmethod
203+
def _from_trusted(
204+
cls: Type[T],
205+
name: str,
206+
opcode: int,
207+
arg: int,
208+
location: Optional[InstrLocation],
209+
) -> T:
210+
"""Fast path for concrete_instructions: skip validation, compute size from arg."""
211+
new = object.__new__(cls)
212+
new._name = name
213+
new._opcode = opcode
214+
new._arg = arg
215+
new._location = location
216+
new._extended_args = None
217+
size = 2
218+
if arg is not UNSET:
219+
_arg = arg
220+
while _arg > 0xFF:
221+
size += 2
222+
_arg >>= 8
223+
new._size = size
224+
return new
225+
202226
@classmethod
203227
def disassemble(cls: Type[T], lineno: Optional[int], code: bytes, offset: int) -> T:
204228
index = 2 * offset
@@ -1145,12 +1169,14 @@ def concrete_instructions(self) -> None:
11451169

11461170
assert isinstance(instr, Instr)
11471171

1148-
if instr.location is not UNSET and instr.location is not None:
1149-
location = instr.location
1172+
# Access private slots directly — avoids property descriptor overhead on
1173+
# every iteration; safe because instr is a validated Instr at this point.
1174+
if instr._location is not UNSET and instr._location is not None:
1175+
location = instr._location
11501176

1151-
instr_name = instr.name
1177+
instr_name = instr._name
11521178
opcode = instr._opcode
1153-
arg = instr.arg
1179+
arg = instr._arg
11541180
is_jump = False
11551181
if isinstance(arg, Label):
11561182
label = arg
@@ -1234,7 +1260,7 @@ def concrete_instructions(self) -> None:
12341260
c_arg = arg
12351261

12361262
# The above should have performed all the necessary conversion
1237-
c_instr = ConcreteInstr(instr_name, c_arg, location=location)
1263+
c_instr = ConcreteInstr._from_trusted(instr_name, opcode, c_arg, location)
12381264
if is_jump:
12391265
self.jumps.append((len(self.instructions), label, c_instr))
12401266

0 commit comments

Comments
 (0)