Skip to content

Commit f013a30

Browse files
committed
gh-149584: Reuse profiler result objects
Cache the last FrameInfo tuple per code object/instruction offset, reuse cached thread id objects, and append cached parent frames directly on full frame-cache hits. This cuts Python allocation churn in the steady-state profiler path.
1 parent ab2cd32 commit f013a30

6 files changed

Lines changed: 41 additions & 20 deletions

File tree

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ typedef struct {
215215
PyObject *file_name;
216216
int first_lineno;
217217
PyObject *linetable; // bytes
218+
PyObject *last_frame_info;
219+
ptrdiff_t last_addrq;
218220
uintptr_t addr_code_adaptive;
219221
} CachedCodeMetadata;
220222

@@ -227,6 +229,7 @@ typedef struct {
227229
uintptr_t thread_state_addr;
228230
uintptr_t addrs[FRAME_CACHE_MAX_FRAMES];
229231
Py_ssize_t num_addrs;
232+
PyObject *thread_id_obj; // owned reference, NULL if empty
230233
PyObject *frame_list; // owned reference, NULL if empty
231234
} FrameCacheEntry;
232235

Modules/_remote_debugging/code_objects.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ parse_code_object(RemoteUnwinderObject *unwinder,
405405
meta->func_name = func;
406406
meta->file_name = file;
407407
meta->linetable = linetable;
408+
meta->last_frame_info = NULL;
409+
meta->last_addrq = -1;
408410
meta->first_lineno = GET_MEMBER(int, code_object, unwinder->debug_offsets.code_object.firstlineno);
409411
meta->addr_code_adaptive = real_address + (uintptr_t)unwinder->debug_offsets.code_object.co_code_adaptive;
410412

@@ -482,6 +484,12 @@ parse_code_object(RemoteUnwinderObject *unwinder,
482484
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
483485
#endif
484486
; // Empty statement to avoid C23 extension warning
487+
488+
if (!unwinder->opcodes && meta->last_frame_info != NULL && meta->last_addrq == addrq) {
489+
*result = Py_NewRef(meta->last_frame_info);
490+
return 0;
491+
}
492+
485493
LocationInfo info = {0};
486494
bool ok = parse_linetable(addrq, PyBytes_AS_STRING(meta->linetable),
487495
PyBytes_GET_SIZE(meta->linetable),
@@ -529,6 +537,11 @@ parse_code_object(RemoteUnwinderObject *unwinder,
529537
goto error;
530538
}
531539

540+
if (!unwinder->opcodes) {
541+
Py_XSETREF(meta->last_frame_info, Py_NewRef(tuple));
542+
meta->last_addrq = addrq;
543+
}
544+
532545
*result = tuple;
533546
return 0;
534547

Modules/_remote_debugging/frame_cache.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ frame_cache_cleanup(RemoteUnwinderObject *unwinder)
3030
return;
3131
}
3232
for (int i = 0; i < FRAME_CACHE_MAX_THREADS; i++) {
33+
Py_CLEAR(unwinder->frame_cache[i].thread_id_obj);
3334
Py_CLEAR(unwinder->frame_cache[i].frame_list);
3435
}
3536
PyMem_Free(unwinder->frame_cache);
@@ -142,6 +143,7 @@ frame_cache_invalidate_stale(RemoteUnwinderObject *unwinder, PyObject *result)
142143
}
143144
if (!found) {
144145
// Clear this entry
146+
Py_CLEAR(unwinder->frame_cache[i].thread_id_obj);
145147
Py_CLEAR(unwinder->frame_cache[i].frame_list);
146148
unwinder->frame_cache[i].thread_id = 0;
147149
unwinder->frame_cache[i].thread_state_addr = 0;
@@ -275,6 +277,12 @@ frame_cache_store(
275277
}
276278
entry->thread_id = thread_id;
277279
entry->thread_state_addr = thread_state_addr;
280+
if (entry->thread_id_obj == NULL) {
281+
entry->thread_id_obj = PyLong_FromUnsignedLongLong(thread_id);
282+
if (entry->thread_id_obj == NULL) {
283+
return -1;
284+
}
285+
}
278286
memcpy(entry->addrs, addrs, num_addrs * sizeof(uintptr_t));
279287
entry->num_addrs = num_addrs;
280288
assert(entry->num_addrs == num_addrs);

Modules/_remote_debugging/frames.c

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -543,35 +543,23 @@ try_full_cache_hit(
543543
return -1;
544544
}
545545

546-
Py_ssize_t cached_size = PyList_GET_SIZE(entry->frame_list);
547-
PyObject *parent_slice = NULL;
548-
if (cached_size > 1) {
549-
parent_slice = PyList_GetSlice(entry->frame_list, 1, cached_size);
550-
if (!parent_slice) {
551-
Py_XDECREF(current_frame);
552-
return -1;
553-
}
554-
}
555-
556546
if (current_frame != NULL) {
557547
if (PyList_Append(ctx->frame_info, current_frame) < 0) {
558548
Py_DECREF(current_frame);
559-
Py_XDECREF(parent_slice);
560549
return -1;
561550
}
562551
Py_DECREF(current_frame);
563552
STATS_ADD(unwinder, frames_read_from_memory, 1);
564553
}
565554

566-
if (parent_slice) {
567-
Py_ssize_t cur_size = PyList_GET_SIZE(ctx->frame_info);
568-
int result = PyList_SetSlice(ctx->frame_info, cur_size, cur_size, parent_slice);
569-
Py_DECREF(parent_slice);
570-
if (result < 0) {
555+
Py_ssize_t cached_size = PyList_GET_SIZE(entry->frame_list);
556+
for (Py_ssize_t i = 1; i < cached_size; i++) {
557+
PyObject *cached_frame = PyList_GET_ITEM(entry->frame_list, i);
558+
if (PyList_Append(ctx->frame_info, cached_frame) < 0) {
571559
return -1;
572560
}
573-
STATS_ADD(unwinder, frames_read_from_cache, cached_size - 1);
574561
}
562+
STATS_ADD(unwinder, frames_read_from_cache, cached_size > 1 ? cached_size - 1 : 0);
575563

576564
STATS_INC(unwinder, frame_cache_hits);
577565
return 1;

Modules/_remote_debugging/module.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ cached_code_metadata_destroy(void *ptr)
166166
Py_DECREF(meta->func_name);
167167
Py_DECREF(meta->file_name);
168168
Py_DECREF(meta->linetable);
169+
Py_XDECREF(meta->last_frame_info);
169170
PyMem_RawFree(meta);
170171
}
171172

Modules/_remote_debugging/threads.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,10 +554,18 @@ unwind_stack_for_thread(
554554

555555
*current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next);
556556

557-
thread_id = PyLong_FromLongLong(tid);
557+
if (unwinder->cache_frames) {
558+
FrameCacheEntry *entry = frame_cache_find(unwinder, (uint64_t)tid);
559+
if (entry && entry->thread_id_obj) {
560+
thread_id = Py_NewRef(entry->thread_id_obj);
561+
}
562+
}
558563
if (thread_id == NULL) {
559-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
560-
goto error;
564+
thread_id = PyLong_FromLongLong(tid);
565+
if (thread_id == NULL) {
566+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
567+
goto error;
568+
}
561569
}
562570

563571
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);

0 commit comments

Comments
 (0)