Skip to content

Commit 3ece6e6

Browse files
[3.11] GH-93516: Backport GH-93769: Speedup line number checks when tracing (GH-94127)
Co-authored-by: Pablo Galindo <[email protected]>
1 parent 8c2af49 commit 3ece6e6

File tree

7 files changed

+409
-298
lines changed

7 files changed

+409
-298
lines changed

Doc/data/python3.11.abi

Lines changed: 308 additions & 290 deletions
Large diffs are not rendered by default.

Include/cpython/code.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ typedef uint16_t _Py_CODEUNIT;
6262
PyObject *co_exceptiontable; /* Byte string encoding exception handling \
6363
table */ \
6464
int co_flags; /* CO_..., see below */ \
65-
int co_warmup; /* Warmup counter for quickening */ \
65+
short co_warmup; /* Warmup counter for quickening */ \
66+
short _co_linearray_entry_size; /* Size of each entry in _co_linearray */ \
6667
\
6768
/* The rest are not so impactful on performance. */ \
6869
int co_argcount; /* #arguments, except *args */ \
@@ -88,6 +89,7 @@ typedef uint16_t _Py_CODEUNIT;
8889
PyObject *co_qualname; /* unicode (qualname, for reference) */ \
8990
PyObject *co_linetable; /* bytes object that holds location info */ \
9091
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
92+
char *_co_linearray; /* array of line offsets */ \
9193
/* Scratch space for extra data relating to the code object. \
9294
Type is a void* to keep the format private in codeobject.c to force \
9395
people to go through the proper APIs. */ \

Include/internal/pycore_code.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,35 @@ write_location_entry_start(uint8_t *ptr, int code, int length)
475475
}
476476

477477

478+
/* Line array cache for tracing */
479+
480+
extern int _PyCode_CreateLineArray(PyCodeObject *co);
481+
482+
static inline int
483+
_PyCode_InitLineArray(PyCodeObject *co)
484+
{
485+
if (co->_co_linearray) {
486+
return 0;
487+
}
488+
return _PyCode_CreateLineArray(co);
489+
}
490+
491+
static inline int
492+
_PyCode_LineNumberFromArray(PyCodeObject *co, int index)
493+
{
494+
assert(co->_co_linearray != NULL);
495+
assert(index >= 0);
496+
assert(index < Py_SIZE(co));
497+
if (co->_co_linearray_entry_size == 2) {
498+
return ((int16_t *)co->_co_linearray)[index];
499+
}
500+
else {
501+
assert(co->_co_linearray_entry_size == 4);
502+
return ((int32_t *)co->_co_linearray)[index];
503+
}
504+
}
505+
506+
478507
#ifdef __cplusplus
479508
}
480509
#endif
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Lazily create a table mapping bytecode offsets to line numbers to speed up
2+
calculation of line numbers when tracing.

Objects/codeobject.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
336336
co->co_extra = NULL;
337337

338338
co->co_warmup = QUICKENING_INITIAL_WARMUP_VALUE;
339+
co->_co_linearray_entry_size = 0;
340+
co->_co_linearray = NULL;
339341
memcpy(_PyCode_CODE(co), PyBytes_AS_STRING(con->code),
340342
PyBytes_GET_SIZE(con->code));
341343
}
@@ -694,13 +696,60 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
694696
lnotab_notes.txt for the details of the lnotab representation.
695697
*/
696698

699+
int
700+
_PyCode_CreateLineArray(PyCodeObject *co)
701+
{
702+
assert(co->_co_linearray == NULL);
703+
PyCodeAddressRange bounds;
704+
int size;
705+
int max_line = 0;
706+
_PyCode_InitAddressRange(co, &bounds);
707+
while(_PyLineTable_NextAddressRange(&bounds)) {
708+
if (bounds.ar_line > max_line) {
709+
max_line = bounds.ar_line;
710+
}
711+
}
712+
if (max_line < (1 << 15)) {
713+
size = 2;
714+
}
715+
else {
716+
size = 4;
717+
}
718+
co->_co_linearray = PyMem_Malloc(Py_SIZE(co)*size);
719+
if (co->_co_linearray == NULL) {
720+
PyErr_NoMemory();
721+
return -1;
722+
}
723+
co->_co_linearray_entry_size = size;
724+
_PyCode_InitAddressRange(co, &bounds);
725+
while(_PyLineTable_NextAddressRange(&bounds)) {
726+
int start = bounds.ar_start / sizeof(_Py_CODEUNIT);
727+
int end = bounds.ar_end / sizeof(_Py_CODEUNIT);
728+
for (int index = start; index < end; index++) {
729+
assert(index < (int)Py_SIZE(co));
730+
if (size == 2) {
731+
assert(((int16_t)bounds.ar_line) == bounds.ar_line);
732+
((int16_t *)co->_co_linearray)[index] = bounds.ar_line;
733+
}
734+
else {
735+
assert(size == 4);
736+
((int32_t *)co->_co_linearray)[index] = bounds.ar_line;
737+
}
738+
}
739+
}
740+
return 0;
741+
}
742+
697743
int
698744
PyCode_Addr2Line(PyCodeObject *co, int addrq)
699745
{
700746
if (addrq < 0) {
701747
return co->co_firstlineno;
702748
}
703749
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
750+
if (co->_co_linearray) {
751+
return _PyCode_LineNumberFromArray(co, addrq / sizeof(_Py_CODEUNIT));
752+
}
704753
PyCodeAddressRange bounds;
705754
_PyCode_InitAddressRange(co, &bounds);
706755
return _PyCode_CheckLineNumber(addrq, &bounds);
@@ -1539,6 +1588,9 @@ code_dealloc(PyCodeObject *co)
15391588
if (co->co_weakreflist != NULL) {
15401589
PyObject_ClearWeakRefs((PyObject*)co);
15411590
}
1591+
if (co->_co_linearray) {
1592+
PyMem_Free(co->_co_linearray);
1593+
}
15421594
if (co->co_warmup == 0) {
15431595
_Py_QuickenedCount--;
15441596
}
@@ -2095,6 +2147,10 @@ _PyStaticCode_Dealloc(PyCodeObject *co)
20952147
PyObject_ClearWeakRefs((PyObject *)co);
20962148
co->co_weakreflist = NULL;
20972149
}
2150+
if (co->_co_linearray) {
2151+
PyMem_Free(co->_co_linearray);
2152+
co->_co_linearray = NULL;
2153+
}
20982154
}
20992155

21002156
int

Python/ceval.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6853,9 +6853,10 @@ call_trace(Py_tracefunc func, PyObject *obj,
68536853
tstate->tracing_what = what;
68546854
PyThreadState_EnterTracing(tstate);
68556855
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
6856-
initialize_trace_info(&tstate->trace_info, frame);
6857-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6858-
f->f_lineno = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6856+
if (_PyCode_InitLineArray(frame->f_code)) {
6857+
return -1;
6858+
}
6859+
f->f_lineno = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
68596860
result = func(obj, f, what, arg);
68606861
f->f_lineno = 0;
68616862
PyThreadState_LeaveTracing(tstate);
@@ -6892,7 +6893,9 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
68926893
represents a jump backwards, update the frame's line number and
68936894
then call the trace function if we're tracing source lines.
68946895
*/
6895-
initialize_trace_info(&tstate->trace_info, frame);
6896+
if (_PyCode_InitLineArray(frame->f_code)) {
6897+
return -1;
6898+
}
68966899
int entry_point = 0;
68976900
_Py_CODEUNIT *code = _PyCode_CODE(frame->f_code);
68986901
while (_PyOpcode_Deopt[_Py_OPCODE(code[entry_point])] != RESUME) {
@@ -6903,10 +6906,9 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
69036906
lastline = -1;
69046907
}
69056908
else {
6906-
lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &tstate->trace_info.bounds);
6909+
lastline = _PyCode_LineNumberFromArray(frame->f_code, instr_prev);
69076910
}
6908-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6909-
int line = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6911+
int line = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
69106912
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
69116913
if (f == NULL) {
69126914
return -1;

Tools/scripts/deepfreeze.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
262262
self.write(f".co_exceptiontable = {co_exceptiontable},")
263263
self.field(code, "co_flags")
264264
self.write(".co_warmup = QUICKENING_INITIAL_WARMUP_VALUE,")
265+
self.write("._co_linearray_entry_size = 0,")
265266
self.field(code, "co_argcount")
266267
self.field(code, "co_posonlyargcount")
267268
self.field(code, "co_kwonlyargcount")
@@ -278,6 +279,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
278279
self.write(f".co_name = {co_name},")
279280
self.write(f".co_qualname = {co_qualname},")
280281
self.write(f".co_linetable = {co_linetable},")
282+
self.write("._co_linearray = NULL,")
281283
self.write(f".co_code_adaptive = {co_code_adaptive},")
282284
name_as_code = f"(PyCodeObject *)&{name}"
283285
self.deallocs.append(f"_PyStaticCode_Dealloc({name_as_code});")

0 commit comments

Comments
 (0)