Skip to content

Commit 0bd6735

Browse files
committed
pythonGH-93516: Speedup line number checks when tracing. (pythonGH-93763)
* Use a lookup table to reduce overhead of getting line numbers during tracing.
1 parent 81686e7 commit 0bd6735

File tree

6 files changed

+101
-8
lines changed

6 files changed

+101
-8
lines changed

Include/cpython/code.h

+3-1
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

+29
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
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

+56
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);
@@ -1534,6 +1583,9 @@ code_dealloc(PyCodeObject *co)
15341583
if (co->co_weakreflist != NULL) {
15351584
PyObject_ClearWeakRefs((PyObject*)co);
15361585
}
1586+
if (co->_co_linearray) {
1587+
PyMem_Free(co->_co_linearray);
1588+
}
15371589
if (co->co_warmup == 0) {
15381590
_Py_QuickenedCount--;
15391591
}
@@ -2090,6 +2142,10 @@ _PyStaticCode_Dealloc(PyCodeObject *co)
20902142
PyObject_ClearWeakRefs((PyObject *)co);
20912143
co->co_weakreflist = NULL;
20922144
}
2145+
if (co->_co_linearray) {
2146+
PyMem_Free(co->_co_linearray);
2147+
co->_co_linearray = NULL;
2148+
}
20932149
}
20942150

20952151
int

Python/ceval.c

+9-7
Original file line numberDiff line numberDiff line change
@@ -6854,9 +6854,10 @@ call_trace(Py_tracefunc func, PyObject *obj,
68546854
tstate->tracing_what = what;
68556855
PyThreadState_EnterTracing(tstate);
68566856
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
6857-
initialize_trace_info(&tstate->trace_info, frame);
6858-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6859-
f->f_lineno = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6857+
if (_PyCode_InitLineArray(frame->f_code)) {
6858+
return -1;
6859+
}
6860+
f->f_lineno = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
68606861
result = func(obj, f, what, arg);
68616862
f->f_lineno = 0;
68626863
PyThreadState_LeaveTracing(tstate);
@@ -6893,7 +6894,9 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
68936894
represents a jump backwards, update the frame's line number and
68946895
then call the trace function if we're tracing source lines.
68956896
*/
6896-
initialize_trace_info(&tstate->trace_info, frame);
6897+
if (_PyCode_InitLineArray(frame->f_code)) {
6898+
return -1;
6899+
}
68976900
int entry_point = 0;
68986901
_Py_CODEUNIT *code = _PyCode_CODE(frame->f_code);
68996902
while (_PyOpcode_Deopt[_Py_OPCODE(code[entry_point])] != RESUME) {
@@ -6904,10 +6907,9 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
69046907
lastline = -1;
69056908
}
69066909
else {
6907-
lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &tstate->trace_info.bounds);
6910+
lastline = _PyCode_LineNumberFromArray(frame->f_code, instr_prev);
69086911
}
6909-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6910-
int line = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6912+
int line = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
69116913
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
69126914
if (f == NULL) {
69136915
return -1;

Tools/scripts/deepfreeze.py

+2
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)