From af1ed33cdea22245402f6310fb68a1ee3d30168b Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 20 Apr 2024 13:05:26 -0700 Subject: [PATCH 1/4] Lazy load f_lineno to improve performance of tracing --- Objects/frameobject.c | 8 ++---- Python/instrumentation.c | 62 ++++++++++++++++++++++++++++++---------- Python/legacy_tracing.c | 1 + 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/Objects/frameobject.c b/Objects/frameobject.c index d55c246d80dd6a..c43c5c9dcf6703 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -41,12 +41,10 @@ int PyFrame_GetLineNumber(PyFrameObject *f) { assert(f != NULL); - if (f->f_lineno != 0) { - return f->f_lineno; - } - else { - return PyUnstable_InterpreterFrame_GetLine(f->f_frame); + if (f->f_lineno == 0) { + f->f_lineno = PyUnstable_InterpreterFrame_GetLine(f->f_frame); } + return f->f_lineno; } static PyObject * diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 71efeff077633d..b93548c82013eb 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -268,14 +268,15 @@ get_events(_Py_GlobalMonitors *m, int tool_id) * 8 bit value. * if line_delta == -128: * line = None # represented as -1 - * elif line_delta == -127: + * elif line_delta == -127 or line_delta == -126: * line = PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT)); * else: * line = first_line + (offset >> OFFSET_SHIFT) + line_delta; */ #define NO_LINE -128 -#define COMPUTED_LINE -127 +#define COMPUTED_LINE_LINENO_CHANGE -127 +#define COMPUTED_LINE -126 #define OFFSET_SHIFT 4 @@ -302,7 +303,7 @@ compute_line(PyCodeObject *code, int offset, int8_t line_delta) return -1; } - assert(line_delta == COMPUTED_LINE); + assert(line_delta == COMPUTED_LINE || line_delta == COMPUTED_LINE_LINENO_CHANGE); /* Look it up */ return PyCode_Addr2Line(code, offset * sizeof(_Py_CODEUNIT)); } @@ -1224,18 +1225,26 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, } PyInterpreterState *interp = tstate->interp; int8_t line_delta = line_data->line_delta; - int line = compute_line(code, i, line_delta); - assert(line >= 0); - assert(prev != NULL); - int prev_index = (int)(prev - _PyCode_CODE(code)); - int prev_line = _Py_Instrumentation_GetLine(code, prev_index); - if (prev_line == line) { - int prev_opcode = _PyCode_CODE(code)[prev_index].op.code; - /* RESUME and INSTRUMENTED_RESUME are needed for the operation of - * instrumentation, so must never be hidden by an INSTRUMENTED_LINE. - */ - if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) { - goto done; + int line = 0; + + if (line_delta == COMPUTED_LINE_LINENO_CHANGE) { + // We know the line number must changed, don't need to calculate the + // line number for now because we might not need it. + line = 0; + } else { + line = compute_line(code, i, line_delta); + assert(line >= 0); + assert(prev != NULL); + int prev_index = (int)(prev - _PyCode_CODE(code)); + int prev_line = _Py_Instrumentation_GetLine(code, prev_index); + if (prev_line == line) { + int prev_opcode = _PyCode_CODE(code)[prev_index].op.code; + /* RESUME and INSTRUMENTED_RESUME are needed for the operation of + * instrumentation, so must never be hidden by an INSTRUMENTED_LINE. + */ + if (prev_opcode != RESUME && prev_opcode != INSTRUMENTED_RESUME) { + goto done; + } } } @@ -1260,6 +1269,12 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, tstate->tracing++; /* Call c_tracefunc directly, having set the line number. */ Py_INCREF(frame_obj); + if (line == 0 && line_delta > COMPUTED_LINE) { + /* Only assign f_lineno if it's easy to calculate, otherwise + * do lazy calculation by setting the f_lineno to 0. + */ + line = compute_line(code, i, line_delta); + } frame_obj->f_lineno = line; int err = tstate->c_tracefunc(tstate->c_traceobj, frame_obj, PyTrace_LINE, Py_None); frame_obj->f_lineno = 0; @@ -1276,6 +1291,11 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, if (tools == 0) { goto done; } + + if (line == 0) { + /* Need to calculate the line number now for monitoring events */ + line = compute_line(code, i, line_delta); + } PyObject *line_obj = PyLong_FromLong(line); if (line_obj == NULL) { return -1; @@ -1477,6 +1497,13 @@ initialize_lines(PyCodeObject *code) */ if (line != current_line && line >= 0) { line_data[i].original_opcode = opcode; + if (line_data[i].line_delta == COMPUTED_LINE) { + /* Label this line as a line with a line number change + * which could help the monitoring callback to quickly + * identify the line number change. + */ + line_data[i].line_delta = COMPUTED_LINE_LINENO_CHANGE; + } } else { line_data[i].original_opcode = 0; @@ -1529,6 +1556,11 @@ initialize_lines(PyCodeObject *code) assert(target >= 0); if (line_data[target].line_delta != NO_LINE) { line_data[target].original_opcode = _Py_GetBaseOpcode(code, target); + if (line_data[target].line_delta == COMPUTED_LINE_LINENO_CHANGE) { + // If the line is a jump target, we are not sure if the line + // number changes, so we set it to COMPUTED_LINE. + line_data[target].line_delta = COMPUTED_LINE; + } } } /* Scan exception table */ diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c index d7aae7d2343ac2..438f13d9085770 100644 --- a/Python/legacy_tracing.c +++ b/Python/legacy_tracing.c @@ -174,6 +174,7 @@ call_trace_func(_PyLegacyEventHandler *self, PyObject *arg) Py_INCREF(frame); int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg); + frame->f_lineno = 0; Py_DECREF(frame); if (err) { return NULL; From 6acff3dc03764742fb744373ad49327f59d893c0 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 20:30:17 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst new file mode 100644 index 00000000000000..29d16bd7dd6581 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst @@ -0,0 +1 @@ +Lazy load frame line number to improve performance of tracing From a6cee96faf8bf079564160e8ba9f573fd131fe8b Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 20 Apr 2024 13:54:18 -0700 Subject: [PATCH 3/4] Fix the lineno cache issue --- Objects/frameobject.c | 13 +++++++++++-- Python/instrumentation.c | 10 +++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Objects/frameobject.c b/Objects/frameobject.c index c43c5c9dcf6703..6d93c214641244 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -41,10 +41,19 @@ int PyFrame_GetLineNumber(PyFrameObject *f) { assert(f != NULL); - if (f->f_lineno == 0) { + if (f->f_lineno == -1) { + // We should calculate it once. If we can't get the line number, + // set f->f_lineno to 0. f->f_lineno = PyUnstable_InterpreterFrame_GetLine(f->f_frame); + if (f->f_lineno < 0) { + f->f_lineno = 0; + } + } + + if (f->f_lineno > 0) { + return f->f_lineno; } - return f->f_lineno; + return PyUnstable_InterpreterFrame_GetLine(f->f_frame); } static PyObject * diff --git a/Python/instrumentation.c b/Python/instrumentation.c index b93548c82013eb..328a3b1733d604 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -1228,9 +1228,9 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, int line = 0; if (line_delta == COMPUTED_LINE_LINENO_CHANGE) { - // We know the line number must changed, don't need to calculate the - // line number for now because we might not need it. - line = 0; + // We know the line number must have changed, don't need to calculate + // the line number for now because we might not need it. + line = -1; } else { line = compute_line(code, i, line_delta); assert(line >= 0); @@ -1269,7 +1269,7 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, tstate->tracing++; /* Call c_tracefunc directly, having set the line number. */ Py_INCREF(frame_obj); - if (line == 0 && line_delta > COMPUTED_LINE) { + if (line == -1 && line_delta > COMPUTED_LINE) { /* Only assign f_lineno if it's easy to calculate, otherwise * do lazy calculation by setting the f_lineno to 0. */ @@ -1292,7 +1292,7 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, goto done; } - if (line == 0) { + if (line == -1) { /* Need to calculate the line number now for monitoring events */ line = compute_line(code, i, line_delta); } From 4fecbd46fff8cddfcec7672b7b703e957813f1cd Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 26 Apr 2024 10:52:50 -0700 Subject: [PATCH 4/4] early return if calculation fails Co-authored-by: Mark Shannon --- Objects/frameobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 6d93c214641244..9917fc5ea9550e 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -47,6 +47,7 @@ PyFrame_GetLineNumber(PyFrameObject *f) f->f_lineno = PyUnstable_InterpreterFrame_GetLine(f->f_frame); if (f->f_lineno < 0) { f->f_lineno = 0; + return -1; } }