Skip to content

Commit 375c94c

Browse files
gh-107674: Lazy load line number to improve performance of tracing (GH-118127)
1 parent c7e7bfc commit 375c94c

File tree

4 files changed

+61
-19
lines changed

4 files changed

+61
-19
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lazy load frame line number to improve performance of tracing

Objects/frameobject.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,20 @@ int
4141
PyFrame_GetLineNumber(PyFrameObject *f)
4242
{
4343
assert(f != NULL);
44-
if (f->f_lineno != 0) {
45-
return f->f_lineno;
44+
if (f->f_lineno == -1) {
45+
// We should calculate it once. If we can't get the line number,
46+
// set f->f_lineno to 0.
47+
f->f_lineno = PyUnstable_InterpreterFrame_GetLine(f->f_frame);
48+
if (f->f_lineno < 0) {
49+
f->f_lineno = 0;
50+
return -1;
51+
}
4652
}
47-
else {
48-
return PyUnstable_InterpreterFrame_GetLine(f->f_frame);
53+
54+
if (f->f_lineno > 0) {
55+
return f->f_lineno;
4956
}
57+
return PyUnstable_InterpreterFrame_GetLine(f->f_frame);
5058
}
5159

5260
static PyObject *

Python/instrumentation.c

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,15 @@ get_events(_Py_GlobalMonitors *m, int tool_id)
268268
* 8 bit value.
269269
* if line_delta == -128:
270270
* line = None # represented as -1
271-
* elif line_delta == -127:
271+
* elif line_delta == -127 or line_delta == -126:
272272
* line = PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT));
273273
* else:
274274
* line = first_line + (offset >> OFFSET_SHIFT) + line_delta;
275275
*/
276276

277277
#define NO_LINE -128
278-
#define COMPUTED_LINE -127
278+
#define COMPUTED_LINE_LINENO_CHANGE -127
279+
#define COMPUTED_LINE -126
279280

280281
#define OFFSET_SHIFT 4
281282

@@ -302,7 +303,7 @@ compute_line(PyCodeObject *code, int offset, int8_t line_delta)
302303

303304
return -1;
304305
}
305-
assert(line_delta == COMPUTED_LINE);
306+
assert(line_delta == COMPUTED_LINE || line_delta == COMPUTED_LINE_LINENO_CHANGE);
306307
/* Look it up */
307308
return PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT));
308309
}
@@ -1224,18 +1225,26 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
12241225
}
12251226
PyInterpreterState *interp = tstate->interp;
12261227
int8_t line_delta = line_data->line_delta;
1227-
int line = compute_line(code, i, line_delta);
1228-
assert(line >= 0);
1229-
assert(prev != NULL);
1230-
int prev_index = (int)(prev - _PyCode_CODE(code));
1231-
int prev_line = _Py_Instrumentation_GetLine(code, prev_index);
1232-
if (prev_line == line) {
1233-
int prev_opcode = _PyCode_CODE(code)[prev_index].op.code;
1234-
/* RESUME and INSTRUMENTED_RESUME are needed for the operation of
1235-
* instrumentation, so must never be hidden by an INSTRUMENTED_LINE.
1236-
*/
1237-
if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) {
1238-
goto done;
1228+
int line = 0;
1229+
1230+
if (line_delta == COMPUTED_LINE_LINENO_CHANGE) {
1231+
// We know the line number must have changed, don't need to calculate
1232+
// the line number for now because we might not need it.
1233+
line = -1;
1234+
} else {
1235+
line = compute_line(code, i, line_delta);
1236+
assert(line >= 0);
1237+
assert(prev != NULL);
1238+
int prev_index = (int)(prev - _PyCode_CODE(code));
1239+
int prev_line = _Py_Instrumentation_GetLine(code, prev_index);
1240+
if (prev_line == line) {
1241+
int prev_opcode = _PyCode_CODE(code)[prev_index].op.code;
1242+
/* RESUME and INSTRUMENTED_RESUME are needed for the operation of
1243+
* instrumentation, so must never be hidden by an INSTRUMENTED_LINE.
1244+
*/
1245+
if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) {
1246+
goto done;
1247+
}
12391248
}
12401249
}
12411250

@@ -1260,6 +1269,12 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
12601269
tstate->tracing++;
12611270
/* Call c_tracefunc directly, having set the line number. */
12621271
Py_INCREF(frame_obj);
1272+
if (line == -1 && line_delta > COMPUTED_LINE) {
1273+
/* Only assign f_lineno if it's easy to calculate, otherwise
1274+
* do lazy calculation by setting the f_lineno to 0.
1275+
*/
1276+
line = compute_line(code, i, line_delta);
1277+
}
12631278
frame_obj->f_lineno = line;
12641279
int err = tstate->c_tracefunc(tstate->c_traceobj, frame_obj, PyTrace_LINE, Py_None);
12651280
frame_obj->f_lineno = 0;
@@ -1276,6 +1291,11 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
12761291
if (tools == 0) {
12771292
goto done;
12781293
}
1294+
1295+
if (line == -1) {
1296+
/* Need to calculate the line number now for monitoring events */
1297+
line = compute_line(code, i, line_delta);
1298+
}
12791299
PyObject *line_obj = PyLong_FromLong(line);
12801300
if (line_obj == NULL) {
12811301
return -1;
@@ -1477,6 +1497,13 @@ initialize_lines(PyCodeObject *code)
14771497
*/
14781498
if (line != current_line && line >= 0) {
14791499
line_data[i].original_opcode = opcode;
1500+
if (line_data[i].line_delta == COMPUTED_LINE) {
1501+
/* Label this line as a line with a line number change
1502+
* which could help the monitoring callback to quickly
1503+
* identify the line number change.
1504+
*/
1505+
line_data[i].line_delta = COMPUTED_LINE_LINENO_CHANGE;
1506+
}
14801507
}
14811508
else {
14821509
line_data[i].original_opcode = 0;
@@ -1529,6 +1556,11 @@ initialize_lines(PyCodeObject *code)
15291556
assert(target >= 0);
15301557
if (line_data[target].line_delta != NO_LINE) {
15311558
line_data[target].original_opcode = _Py_GetBaseOpcode(code, target);
1559+
if (line_data[target].line_delta == COMPUTED_LINE_LINENO_CHANGE) {
1560+
// If the line is a jump target, we are not sure if the line
1561+
// number changes, so we set it to COMPUTED_LINE.
1562+
line_data[target].line_delta = COMPUTED_LINE;
1563+
}
15321564
}
15331565
}
15341566
/* Scan exception table */

Python/legacy_tracing.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
174174

175175
Py_INCREF(frame);
176176
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg);
177+
frame->f_lineno = 0;
177178
Py_DECREF(frame);
178179
if (err) {
179180
return NULL;

0 commit comments

Comments
 (0)