Skip to content

Commit ab0e601

Browse files
authored
GH-93516: Speedup line number checks when tracing. (GH-93763)
* Use a lookup table to reduce overhead of getting line numbers during tracing.
1 parent 45e62a2 commit ab0e601

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 */ \
@@ -90,6 +91,7 @@ typedef uint16_t _Py_CODEUNIT;
9091
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
9192
void *_co_code; /* cached co_code object/attribute */ \
9293
int _co_firsttraceable; /* index of first traceable instruction */ \
94+
char *_co_linearray; /* array of line offsets */ \
9395
/* Scratch space for extra data relating to the code object. \
9496
Type is a void* to keep the format private in codeobject.c to force \
9597
people to go through the proper APIs. */ \

Include/internal/pycore_code.h

+29
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,35 @@ adaptive_counter_backoff(uint16_t counter) {
461461
}
462462

463463

464+
/* Line array cache for tracing */
465+
466+
extern int _PyCode_CreateLineArray(PyCodeObject *co);
467+
468+
static inline int
469+
_PyCode_InitLineArray(PyCodeObject *co)
470+
{
471+
if (co->_co_linearray) {
472+
return 0;
473+
}
474+
return _PyCode_CreateLineArray(co);
475+
}
476+
477+
static inline int
478+
_PyCode_LineNumberFromArray(PyCodeObject *co, int index)
479+
{
480+
assert(co->_co_linearray != NULL);
481+
assert(index >= 0);
482+
assert(index < Py_SIZE(co));
483+
if (co->_co_linearray_entry_size == 2) {
484+
return ((int16_t *)co->_co_linearray)[index];
485+
}
486+
else {
487+
assert(co->_co_linearray_entry_size == 4);
488+
return ((int32_t *)co->_co_linearray)[index];
489+
}
490+
}
491+
492+
464493
#ifdef __cplusplus
465494
}
466495
#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
@@ -339,6 +339,8 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
339339
co->_co_code = NULL;
340340

341341
co->co_warmup = QUICKENING_INITIAL_WARMUP_VALUE;
342+
co->_co_linearray_entry_size = 0;
343+
co->_co_linearray = NULL;
342344
memcpy(_PyCode_CODE(co), PyBytes_AS_STRING(con->code),
343345
PyBytes_GET_SIZE(con->code));
344346
int entry_point = 0;
@@ -703,13 +705,60 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
703705
lnotab_notes.txt for the details of the lnotab representation.
704706
*/
705707

708+
int
709+
_PyCode_CreateLineArray(PyCodeObject *co)
710+
{
711+
assert(co->_co_linearray == NULL);
712+
PyCodeAddressRange bounds;
713+
int size;
714+
int max_line = 0;
715+
_PyCode_InitAddressRange(co, &bounds);
716+
while(_PyLineTable_NextAddressRange(&bounds)) {
717+
if (bounds.ar_line > max_line) {
718+
max_line = bounds.ar_line;
719+
}
720+
}
721+
if (max_line < (1 << 15)) {
722+
size = 2;
723+
}
724+
else {
725+
size = 4;
726+
}
727+
co->_co_linearray = PyMem_Malloc(Py_SIZE(co)*size);
728+
if (co->_co_linearray == NULL) {
729+
PyErr_NoMemory();
730+
return -1;
731+
}
732+
co->_co_linearray_entry_size = size;
733+
_PyCode_InitAddressRange(co, &bounds);
734+
while(_PyLineTable_NextAddressRange(&bounds)) {
735+
int start = bounds.ar_start / sizeof(_Py_CODEUNIT);
736+
int end = bounds.ar_end / sizeof(_Py_CODEUNIT);
737+
for (int index = start; index < end; index++) {
738+
assert(index < (int)Py_SIZE(co));
739+
if (size == 2) {
740+
assert(((int16_t)bounds.ar_line) == bounds.ar_line);
741+
((int16_t *)co->_co_linearray)[index] = bounds.ar_line;
742+
}
743+
else {
744+
assert(size == 4);
745+
((int32_t *)co->_co_linearray)[index] = bounds.ar_line;
746+
}
747+
}
748+
}
749+
return 0;
750+
}
751+
706752
int
707753
PyCode_Addr2Line(PyCodeObject *co, int addrq)
708754
{
709755
if (addrq < 0) {
710756
return co->co_firstlineno;
711757
}
712758
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
759+
if (co->_co_linearray) {
760+
return _PyCode_LineNumberFromArray(co, addrq / sizeof(_Py_CODEUNIT));
761+
}
713762
PyCodeAddressRange bounds;
714763
_PyCode_InitAddressRange(co, &bounds);
715764
return _PyCode_CheckLineNumber(addrq, &bounds);
@@ -1549,6 +1598,9 @@ code_dealloc(PyCodeObject *co)
15491598
if (co->co_weakreflist != NULL) {
15501599
PyObject_ClearWeakRefs((PyObject*)co);
15511600
}
1601+
if (co->_co_linearray) {
1602+
PyMem_Free(co->_co_linearray);
1603+
}
15521604
if (co->co_warmup == 0) {
15531605
_Py_QuickenedCount--;
15541606
}
@@ -2106,6 +2158,10 @@ _PyStaticCode_Dealloc(PyCodeObject *co)
21062158
PyObject_ClearWeakRefs((PyObject *)co);
21072159
co->co_weakreflist = NULL;
21082160
}
2161+
if (co->_co_linearray) {
2162+
PyMem_Free(co->_co_linearray);
2163+
co->_co_linearray = NULL;
2164+
}
21092165
}
21102166

21112167
int

Python/ceval.c

+9-7
Original file line numberDiff line numberDiff line change
@@ -6799,9 +6799,10 @@ call_trace(Py_tracefunc func, PyObject *obj,
67996799
tstate->tracing_what = what;
68006800
PyThreadState_EnterTracing(tstate);
68016801
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
6802-
initialize_trace_info(&tstate->trace_info, frame);
6803-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6804-
f->f_lineno = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6802+
if (_PyCode_InitLineArray(frame->f_code)) {
6803+
return -1;
6804+
}
6805+
f->f_lineno = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
68056806
result = func(obj, f, what, arg);
68066807
f->f_lineno = 0;
68076808
PyThreadState_LeaveTracing(tstate);
@@ -6838,16 +6839,17 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
68386839
represents a jump backwards, update the frame's line number and
68396840
then call the trace function if we're tracing source lines.
68406841
*/
6841-
initialize_trace_info(&tstate->trace_info, frame);
6842+
if (_PyCode_InitLineArray(frame->f_code)) {
6843+
return -1;
6844+
}
68426845
int lastline;
68436846
if (instr_prev <= frame->f_code->_co_firsttraceable) {
68446847
lastline = -1;
68456848
}
68466849
else {
6847-
lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &tstate->trace_info.bounds);
6850+
lastline = _PyCode_LineNumberFromArray(frame->f_code, instr_prev);
68486851
}
6849-
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
6850-
int line = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
6852+
int line = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
68516853
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
68526854
if (f == NULL) {
68536855
return -1;

Tools/scripts/deepfreeze.py

+2
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
254254
self.write(f".co_exceptiontable = {co_exceptiontable},")
255255
self.field(code, "co_flags")
256256
self.write(".co_warmup = QUICKENING_INITIAL_WARMUP_VALUE,")
257+
self.write("._co_linearray_entry_size = 0,")
257258
self.field(code, "co_argcount")
258259
self.field(code, "co_posonlyargcount")
259260
self.field(code, "co_kwonlyargcount")
@@ -271,6 +272,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
271272
self.write(f".co_name = {co_name},")
272273
self.write(f".co_qualname = {co_qualname},")
273274
self.write(f".co_linetable = {co_linetable},")
275+
self.write("._co_linearray = NULL,")
274276
self.write(f".co_code_adaptive = {co_code_adaptive},")
275277
for i, op in enumerate(code.co_code[::2]):
276278
if op == RESUME:

0 commit comments

Comments
 (0)