Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Remove _Py_tss_stats thread local.
Add pystats pointer to PyThreadState and use from there instead.  This
is slightly slower but shouldn't matter in practice.  This simplifies
the attach/detach logic as well.
  • Loading branch information
nascheme committed Aug 6, 2025
commit e77af2c703999ee20ebffa8380931a806a10e9d0
19 changes: 19 additions & 0 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ struct _ts {
*/
PyObject *threading_local_sentinel;
_PyRemoteDebuggerSupport remote_debugger_support;

#ifdef Py_STATS
PyStats *pystats; // pointer PyStats structure, NULL if recording is off
#endif
};

/* other API */
Expand All @@ -230,6 +234,21 @@ PyAPI_FUNC(void) PyThreadState_EnterTracing(PyThreadState *tstate);
// function is set, otherwise disable them.
PyAPI_FUNC(void) PyThreadState_LeaveTracing(PyThreadState *tstate);

#ifdef Py_STATS
#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
extern _Py_thread_local PyThreadState* _Py_tss_tstate;

static inline PyStats*
_PyThreadState_GetStatsFast(void)
{
if (_Py_tss_tstate == NULL) {
return NULL; // no attached thread state
}
return _Py_tss_tstate->pystats;
}
#endif
#endif // Py_STATS

/* PyGILState */

/* Helper/diagnostic function - return 1 if the current thread
Expand Down
24 changes: 9 additions & 15 deletions Include/cpython/pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// - sys._stats_dump()
//
// Python must be built with ./configure --enable-pystats to define the
// _PyStats_GET() function.
// _PyStats_GET() macro.
//
// Define _PY_INTERPRETER macro to increment interpreter_increfs and
// interpreter_decrefs. Otherwise, increment increfs and decrefs.
Expand Down Expand Up @@ -112,8 +112,11 @@ typedef struct _gc_stats {
#ifdef Py_GIL_DISABLED
// stats specific to free-threaded build
typedef struct _ft_stats {
// number of times interpreter had to spin or park when trying to acquire a mutex
uint64_t mutex_sleeps;
// number of times that the QSBR mechanism polled (compute read sequence value)
uint64_t qsbr_polls;
// number of times stop-the-world mechanism was used
uint64_t world_stops;
Copy link
Contributor

@kumaraditya303 kumaraditya303 Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be useful to have the total duration of stop-the-world pause too

} FTStats;
#endif
Expand Down Expand Up @@ -189,25 +192,16 @@ typedef struct _stats {
GCStats gc_stats[3]; // must match NUM_GENERATIONS
} PyStats;


#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
extern _Py_thread_local PyStats *_Py_tss_stats;
#endif

// Export for most shared extensions, used via _PyStats_GET() static
// inline function.
// Export for most shared extensions
PyAPI_FUNC(PyStats *) _PyStats_GetLocal(void);

// Return pointer to the PyStats structure, NULL if recording is off.
static inline PyStats*
_PyStats_GET(void)
{
#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
return _Py_tss_stats;
// use inline function version defined in cpython/pystate.h
static inline PyStats* _PyThreadState_GetStatsFast(void);
#define _PyStats_GET _PyThreadState_GetStatsFast
#else
return _PyStats_GetLocal();
#define _PyStats_GET _PyStats_GetLocal
#endif
}

#define _Py_STATS_EXPR(expr) \
do { \
Expand Down
11 changes: 4 additions & 7 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,13 @@ typedef struct _PyThreadStateImpl {
// When >1, code objects do not immortalize their non-string constants.
int suppress_co_const_immortalization;

#endif // Py_GIL_DISABLED

#ifdef Py_STATS
#ifdef Py_GIL_DISABLED
// per-thread stats, will be merged into interp->pystats_struct
PyStats *pystats_struct; // allocated by _PyStats_ThreadInit()
#endif
PyStats **pystats_tss; // pointer to tss variable
// per-thread stats, will be merged into interp->pystats_struct
PyStats *pystats_struct; // allocated by _PyStats_ThreadInit()
#endif

#endif // Py_GIL_DISABLED

#if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED)
Py_ssize_t reftotal; // this thread's total refcount operations
#endif
Expand Down
35 changes: 16 additions & 19 deletions Python/pystats.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
#include "pycore_pyatomic_ft_wrappers.h"
#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock()
#include "pycore_tstate.h"
#include "pycore_uop_metadata.h" // _PyOpcode_uop_name
#include "pycore_uop_ids.h" // MAX_UOP_ID
#include "pycore_pystate.h" // _PyThreadState_GET()
Expand All @@ -12,13 +13,14 @@

#ifdef Py_STATS

// Pointer to Thread-local stats structure, null if recording is off.
_Py_thread_local PyStats *_Py_tss_stats;

PyStats *
_PyStats_GetLocal(void)
{
return _Py_tss_stats;
PyThreadState *tstate = _PyThreadState_GET();
if (tstate) {
return tstate->pystats;
}
return NULL;
}

#ifdef Py_GIL_DISABLED
Expand Down Expand Up @@ -582,20 +584,18 @@ stats_toggle_on_off(PyThreadState *tstate, int on)
if (!ts->_status.active) {
continue;
}
_PyThreadStateImpl *ts_impl = (_PyThreadStateImpl *)ts;
PyStats *s;
if (interp->pystats_enabled) {
#ifdef Py_GIL_DISABLED
s = ts_impl->pystats_struct;
s = ((_PyThreadStateImpl *)ts)->pystats_struct;
#else
s = ((PyThreadState *)tstate)->interp->pystats_struct;
#endif
}
else {
s = NULL;
}
// write to the tss variable for the 'ts' thread
*ts_impl->pystats_tss = s;
ts->pystats = s;
}
_PyEval_StartTheWorld(interp);
return 0;
Expand Down Expand Up @@ -759,31 +759,28 @@ _PyStats_ThreadFini(_PyThreadStateImpl *tstate)
}

void
_PyStats_Attach(_PyThreadStateImpl *tstate)
_PyStats_Attach(_PyThreadStateImpl *tstate_impl)
{
PyStats *s;
PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
PyThreadState *tstate = (PyThreadState *)tstate_impl;
PyInterpreterState *interp = tstate->interp;
if (FT_ATOMIC_LOAD_INT_RELAXED(interp->pystats_enabled)) {
#ifdef Py_GIL_DISABLED
s = tstate->pystats_struct;
s = ((_PyThreadStateImpl *)tstate)->pystats_struct;
#else
s = ((PyThreadState *)tstate)->interp->pystats_struct;
s = tstate->interp->pystats_struct;
#endif
}
else {
s = NULL;
}
// use correct TSS variable for thread
tstate->pystats_tss = &_Py_tss_stats;
// write to the tss variable for the 'ts' thread
_Py_tss_stats = s;
tstate->pystats = s;
}

void
_PyStats_Detach(_PyThreadStateImpl *tstate)
_PyStats_Detach(_PyThreadStateImpl *tstate_impl)
{
tstate->pystats_tss = NULL;
_Py_tss_stats = NULL;
((PyThreadState *)tstate_impl)->pystats = NULL;
}

#endif // Py_STATS
1 change: 0 additions & 1 deletion Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ Python/pyfpe.c - PyFPE_counter -
Python/import.c - pkgcontext -
Python/pystate.c - _Py_tss_tstate -
Python/pystate.c - _Py_tss_gilstate -
Python/pystats.c - _Py_tss_stats -

##-----------------------
## should be const
Expand Down