@@ -233,6 +233,23 @@ typedef struct {
233233 PyObject * frame_list ; // owned reference, NULL if empty
234234} FrameCacheEntry ;
235235
236+ #define INTERPRETER_THREAD_CACHE_SIZE 32
237+ #if (INTERPRETER_THREAD_CACHE_SIZE & (INTERPRETER_THREAD_CACHE_SIZE - 1 )) != 0
238+ # error "INTERPRETER_THREAD_CACHE_SIZE must be a power of two"
239+ #endif
240+
241+ typedef struct {
242+ uintptr_t interpreter_addr ;
243+ uintptr_t thread_state_addr ;
244+ } InterpreterThreadCacheEntry ;
245+
246+ typedef struct {
247+ const char * tstate ;
248+ uintptr_t tstate_addr ;
249+ const char * frame ;
250+ uintptr_t frame_addr ;
251+ } RemoteReadPrefetch ;
252+
236253/* Statistics for profiling performance analysis */
237254typedef struct {
238255 uint64_t total_samples ; // Total number of get_stack_trace calls
@@ -246,6 +263,11 @@ typedef struct {
246263 uint64_t code_object_cache_hits ; // Code object cache hits
247264 uint64_t code_object_cache_misses ; // Code object cache misses
248265 uint64_t stale_cache_invalidations ; // Times stale entries were cleared
266+ uint64_t batched_read_attempts ; // Batched remote-read attempts
267+ uint64_t batched_read_successes ; // Attempts that read all requested segments
268+ uint64_t batched_read_misses ; // Attempts that fell back or partially read
269+ uint64_t batched_read_segments_requested ; // Segments requested by batched reads
270+ uint64_t batched_read_segments_completed ; // Segments completed by batched reads
249271} UnwinderStats ;
250272
251273/* Stats tracking macros - no-op when stats collection is disabled */
@@ -255,6 +277,46 @@ typedef struct {
255277#define STATS_ADD (unwinder , field , val ) \
256278 do { if ((unwinder)->collect_stats) (unwinder)->stats.field += (val); } while(0)
257279
280+ #define STATS_BATCHED_READ (unwinder , requested , completed ) \
281+ do { \
282+ if ((unwinder)->collect_stats) { \
283+ (unwinder)->stats.batched_read_attempts++; \
284+ (unwinder)->stats.batched_read_segments_requested += (uint64_t)(requested); \
285+ (unwinder)->stats.batched_read_segments_completed += (uint64_t)(completed); \
286+ if ((completed) == (requested)) { \
287+ (unwinder)->stats.batched_read_successes++; \
288+ } \
289+ else { \
290+ (unwinder)->stats.batched_read_misses++; \
291+ } \
292+ } \
293+ } while(0)
294+
295+ static inline int
296+ _Py_RemoteDebug_CountCompletedSegments (
297+ const _Py_RemoteReadSegment * segments ,
298+ int nsegs ,
299+ Py_ssize_t nread )
300+ {
301+ if (nread < 0 ) {
302+ return 0 ;
303+ }
304+
305+ int completed = 0 ;
306+ Py_ssize_t bytes_needed = 0 ;
307+ for (int i = 0 ; i < nsegs ; i ++ ) {
308+ if (segments [i ].size > (size_t )(PY_SSIZE_T_MAX - bytes_needed )) {
309+ break ;
310+ }
311+ bytes_needed += (Py_ssize_t )segments [i ].size ;
312+ if (nread < bytes_needed ) {
313+ break ;
314+ }
315+ completed ++ ;
316+ }
317+ return completed ;
318+ }
319+
258320typedef struct {
259321 PyTypeObject * RemoteDebugging_Type ;
260322 PyTypeObject * TaskInfo_Type ;
@@ -306,7 +368,7 @@ typedef struct {
306368 int cache_frames ;
307369 int collect_stats ; // whether to collect statistics
308370 uint32_t stale_invalidation_counter ; // counter for throttling frame_cache_invalidate_stale
309- uintptr_t cached_tstate_addr ; // predicted first thread for batched reads
371+ InterpreterThreadCacheEntry cached_tstates [ INTERPRETER_THREAD_CACHE_SIZE ];
310372 RemoteDebuggingState * cached_state ;
311373 FrameCacheEntry * frame_cache ; // preallocated array of FRAME_CACHE_MAX_THREADS entries
312374 UnwinderStats stats ; // statistics for performance analysis
@@ -372,8 +434,7 @@ typedef struct {
372434 uintptr_t last_profiled_frame ; // Last cached frame (0 if no cache)
373435 StackChunkList * chunks ; // Pre-copied stack chunks
374436 int skip_first_frame ; // Skip frame_addr itself (continue from its caller)
375- const char * prefetched_frame ; // Optional already-read frame buffer
376- uintptr_t prefetched_frame_addr ; // Remote address for prefetched_frame
437+ RemoteReadPrefetch prefetch ; // Optional already-read thread/frame buffers
377438
378439 /* Outputs */
379440 PyObject * frame_info ; // List to append FrameInfo objects
@@ -616,10 +677,7 @@ extern PyObject* unwind_stack_for_thread(
616677 uintptr_t gil_holder_tstate ,
617678 uintptr_t gc_frame ,
618679 uintptr_t main_thread_tstate ,
619- const char * prefetched_tstate ,
620- uintptr_t prefetched_tstate_addr ,
621- const char * prefetched_frame ,
622- uintptr_t prefetched_frame_addr
680+ const RemoteReadPrefetch * prefetch
623681);
624682
625683/* Thread stopping functions (for blocking mode) */
0 commit comments