]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-107674: Lazy load line number to improve performance of tracing (GH-118127)
authorTian Gao <gaogaotiantian@hotmail.com>
Mon, 29 Apr 2024 08:54:52 +0000 (01:54 -0700)
committerGitHub <noreply@github.com>
Mon, 29 Apr 2024 08:54:52 +0000 (09:54 +0100)
Misc/NEWS.d/next/Core and Builtins/2024-04-20-20-30-15.gh-issue-107674.GZPOP7.rst [new file with mode: 0644]
Objects/frameobject.c
Python/instrumentation.c
Python/legacy_tracing.c

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 (file)
index 0000000..29d16bd
--- /dev/null
@@ -0,0 +1 @@
+Lazy load frame line number to improve performance of tracing
index 07b7ef3df46a5c28b5c4b32b32d58926c561b35b..36538b1f6d53fe1a4857c56f39944c8266e729ed 100644 (file)
@@ -41,12 +41,20 @@ int
 PyFrame_GetLineNumber(PyFrameObject *f)
 {
     assert(f != NULL);
-    if (f->f_lineno != 0) {
-        return f->f_lineno;
+    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;
+            return -1;
+        }
     }
-    else {
-        return PyUnstable_InterpreterFrame_GetLine(f->f_frame);
+
+    if (f->f_lineno > 0) {
+        return f->f_lineno;
     }
+    return PyUnstable_InterpreterFrame_GetLine(f->f_frame);
 }
 
 static PyObject *
index 71efeff077633d9e10370c980d3adc07e655b69c..328a3b1733d6042a6deeda0b5c6eec458b3c3ebf 100644 (file)
@@ -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 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);
+        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 == -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.
+                     */
+                    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 == -1) {
+        /* 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 */
index b5a174059318253a3788dce77677b7bbf5e89b50..74118030925e3e0816e7c39fa89c28139bb4cb50 100644 (file)
@@ -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;