Skip to content

Commit 1c194e2

Browse files
committed
gh-131253: free-threaded build support for pystats
Allow the --enable-pystats build option to be used with free-threading. For the free-threaded builds, the stats structure is allocated per-thread and then periodically merged into a global stats structure (on thread exit or when the reporting function is called). Summary of changes: * introduce _Py_tss_stats thread-local variable. This is set when stats are on, replacing the _Py_stats global that's used in the non-free-threaded build. * replace _Py_stats references with _PyStats_GET() * move pystats logic from Python/specialize.c into Python/pystats.c * add some free-threaded specific stat counters
1 parent 1150321 commit 1c194e2

File tree

16 files changed

+958
-469
lines changed

16 files changed

+958
-469
lines changed

Include/cpython/pystats.h

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//
55
// - _Py_INCREF_STAT_INC() and _Py_DECREF_STAT_INC() used by Py_INCREF()
66
// and Py_DECREF().
7-
// - _Py_stats variable
7+
// - _PyStats_GET()
88
//
99
// Functions of the sys module:
1010
//
@@ -14,7 +14,7 @@
1414
// - sys._stats_dump()
1515
//
1616
// Python must be built with ./configure --enable-pystats to define the
17-
// Py_STATS macro.
17+
// _PyStats_GET() function.
1818
//
1919
// Define _PY_INTERPRETER macro to increment interpreter_increfs and
2020
// interpreter_decrefs. Otherwise, increment increfs and decrefs.
@@ -109,6 +109,15 @@ typedef struct _gc_stats {
109109
uint64_t objects_not_transitively_reachable;
110110
} GCStats;
111111

112+
#ifdef Py_GIL_DISABLED
113+
// stats specific to free-threaded build
114+
typedef struct _ft_stats {
115+
uint64_t mutex_sleeps;
116+
uint64_t qsbr_polls;
117+
uint64_t world_stops;
118+
} FTStats;
119+
#endif
120+
112121
typedef struct _uop_stats {
113122
uint64_t execution_count;
114123
uint64_t miss;
@@ -173,22 +182,74 @@ typedef struct _stats {
173182
CallStats call_stats;
174183
ObjectStats object_stats;
175184
OptimizationStats optimization_stats;
185+
#ifdef Py_GIL_DISABLED
186+
FTStats ft_stats;
187+
#endif
176188
RareEventStats rare_event_stats;
177189
GCStats *gc_stats;
178190
} PyStats;
179191

180192

193+
#ifdef Py_GIL_DISABLED
194+
195+
#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
196+
extern _Py_thread_local PyStats *_Py_tss_stats;
197+
#endif
198+
199+
// Export for most shared extensions, used via _PyStats_GET() static
200+
// inline function.
201+
PyAPI_FUNC(PyStats *) _PyStats_GetLocal(void);
202+
203+
#else // !Py_GIL_DISABLED
204+
181205
// Export for shared extensions like 'math'
182206
PyAPI_DATA(PyStats*) _Py_stats;
183207

208+
#endif
209+
210+
// Return pointer to the PyStats structure, NULL if recording is off.
211+
static inline PyStats*
212+
_PyStats_GET(void)
213+
{
214+
#ifdef Py_GIL_DISABLED
215+
216+
#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
217+
return _Py_tss_stats;
218+
#else
219+
return _PyStats_GetLocal();
220+
#endif
221+
222+
#else // !Py_GIL_DISABLED
223+
224+
return _Py_stats;
225+
226+
#endif
227+
}
228+
229+
#define _Py_STATS_EXPR(expr) \
230+
do { \
231+
PyStats *s = _PyStats_GET(); \
232+
if (s != NULL) { \
233+
s->expr; \
234+
} \
235+
} while (0)
236+
237+
#define _Py_STATS_COND_EXPR(cond, expr) \
238+
do { \
239+
PyStats *s = _PyStats_GET(); \
240+
if (s != NULL && cond) { \
241+
s->expr; \
242+
} \
243+
} while (0)
244+
184245
#ifdef _PY_INTERPRETER
185-
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_increfs++; } while (0)
186-
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_decrefs++; } while (0)
187-
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_increfs++; } while (0)
188-
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_decrefs++; } while (0)
246+
# define _Py_INCREF_STAT_INC() _Py_STATS_EXPR(object_stats.interpreter_increfs++)
247+
# define _Py_DECREF_STAT_INC() _Py_STATS_EXPR(object_stats.interpreter_decrefs++)
248+
# define _Py_INCREF_IMMORTAL_STAT_INC() _Py_STATS_EXPR(object_stats.interpreter_immortal_increfs++)
249+
# define _Py_DECREF_IMMORTAL_STAT_INC() _Py_STATS_EXPR(object_stats.interpreter_immortal_decrefs++)
189250
#else
190-
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.increfs++; } while (0)
191-
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.decrefs++; } while (0)
192-
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_increfs++; } while (0)
193-
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_decrefs++; } while (0)
251+
# define _Py_INCREF_STAT_INC() _Py_STATS_EXPR(object_stats.increfs++)
252+
# define _Py_DECREF_STAT_INC() _Py_STATS_EXPR(object_stats.decrefs++)
253+
# define _Py_INCREF_IMMORTAL_STAT_INC() _Py_STATS_EXPR(object_stats.immortal_increfs++)
254+
# define _Py_DECREF_IMMORTAL_STAT_INC() _Py_STATS_EXPR(object_stats.immortal_decrefs++)
194255
#endif

Include/internal/pycore_stats.h

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,56 @@ extern "C" {
1515

1616
#include "pycore_bitutils.h" // _Py_bit_length
1717

18-
#define STAT_INC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name++; } while (0)
19-
#define STAT_DEC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name--; } while (0)
20-
#define OPCODE_EXE_INC(opname) do { if (_Py_stats) _Py_stats->opcode_stats[opname].execution_count++; } while (0)
21-
#define CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.name++; } while (0)
22-
#define OBJECT_STAT_INC(name) do { if (_Py_stats) _Py_stats->object_stats.name++; } while (0)
23-
#define OBJECT_STAT_INC_COND(name, cond) \
24-
do { if (_Py_stats && cond) _Py_stats->object_stats.name++; } while (0)
25-
#define EVAL_CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.eval_calls[name]++; } while (0)
26-
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
27-
do { if (_Py_stats && PyFunction_Check(callable)) _Py_stats->call_stats.eval_calls[name]++; } while (0)
28-
#define GC_STAT_ADD(gen, name, n) do { if (_Py_stats) _Py_stats->gc_stats[(gen)].name += (n); } while (0)
29-
#define OPT_STAT_INC(name) do { if (_Py_stats) _Py_stats->optimization_stats.name++; } while (0)
30-
#define OPT_STAT_ADD(name, n) do { if (_Py_stats) _Py_stats->optimization_stats.name += (n); } while (0)
31-
#define UOP_STAT_INC(opname, name) do { if (_Py_stats) { assert(opname < 512); _Py_stats->optimization_stats.opcode[opname].name++; } } while (0)
32-
#define UOP_PAIR_INC(uopcode, lastuop) \
33-
do { \
34-
if (lastuop && _Py_stats) { \
35-
_Py_stats->optimization_stats.opcode[lastuop].pair_count[uopcode]++; \
36-
} \
37-
lastuop = uopcode; \
18+
#define STAT_INC(opname, name) _Py_STATS_EXPR(opcode_stats[opname].specialization.name++)
19+
#define STAT_DEC(opname, name) _Py_STATS_EXPR(opcode_stats[opname].specialization.name--)
20+
#define OPCODE_EXE_INC(opname) _Py_STATS_EXPR(opcode_stats[opname].execution_count++)
21+
#define CALL_STAT_INC(name) _Py_STATS_EXPR(call_stats.name++)
22+
#define OBJECT_STAT_INC(name) _Py_STATS_EXPR(object_stats.name++)
23+
#define OBJECT_STAT_INC_COND(name, cond) _Py_STATS_COND_EXPR(cond, object_stats.name++)
24+
#define EVAL_CALL_STAT_INC(name) _Py_STATS_EXPR(call_stats.eval_calls[name]++)
25+
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) _Py_STATS_COND_EXPR(PyFunction_Check(callable), call_stats.eval_calls[name]++)
26+
#define GC_STAT_ADD(gen, name, n) _Py_STATS_EXPR(gc_stats[(gen)].name += (n))
27+
#define OPT_STAT_INC(name) _Py_STATS_EXPR(optimization_stats.name++)
28+
#define OPT_STAT_ADD(name, n) _Py_STATS_EXPR(optimization_stats.name += (n))
29+
#define UOP_STAT_INC(opname, name) \
30+
do { \
31+
PyStats *s = _PyStats_GET(); \
32+
if (s) { \
33+
assert(opname < 512); \
34+
s->optimization_stats.opcode[opname].name++; \
35+
} \
36+
} while (0)
37+
#define UOP_PAIR_INC(uopcode, lastuop) \
38+
do { \
39+
PyStats *s = _PyStats_GET(); \
40+
if (lastuop && s) { \
41+
s->optimization_stats.opcode[lastuop].pair_count[uopcode]++; \
42+
} \
43+
lastuop = uopcode; \
3844
} while (0)
39-
#define OPT_UNSUPPORTED_OPCODE(opname) do { if (_Py_stats) _Py_stats->optimization_stats.unsupported_opcode[opname]++; } while (0)
40-
#define OPT_ERROR_IN_OPCODE(opname) do { if (_Py_stats) _Py_stats->optimization_stats.error_in_opcode[opname]++; } while (0)
45+
#define OPT_UNSUPPORTED_OPCODE(opname) _Py_STATS_EXPR(optimization_stats.unsupported_opcode[opname]++)
46+
#define OPT_ERROR_IN_OPCODE(opname) _Py_STATS_EXPR(optimization_stats.error_in_opcode[opname]++)
4147
#define OPT_HIST(length, name) \
4248
do { \
43-
if (_Py_stats) { \
49+
PyStats *s = _PyStats_GET(); \
50+
if (s) { \
4451
int bucket = _Py_bit_length(length >= 1 ? length - 1 : 0); \
4552
bucket = (bucket >= _Py_UOP_HIST_SIZE) ? _Py_UOP_HIST_SIZE - 1 : bucket; \
46-
_Py_stats->optimization_stats.name[bucket]++; \
53+
s->optimization_stats.name[bucket]++; \
4754
} \
4855
} while (0)
49-
#define RARE_EVENT_STAT_INC(name) do { if (_Py_stats) _Py_stats->rare_event_stats.name++; } while (0)
50-
#define OPCODE_DEFERRED_INC(opname) do { if (_Py_stats && opcode == opname) _Py_stats->opcode_stats[opname].specialization.deferred++; } while (0)
56+
#define RARE_EVENT_STAT_INC(name) _Py_STATS_EXPR(rare_event_stats.name++)
57+
#define OPCODE_DEFERRED_INC(opname) _Py_STATS_COND_EXPR(opcode==opname, opcode_stats[opname].specialization.deferred++)
58+
59+
#ifdef Py_GIL_DISABLED
60+
#define FT_STAT_MUTEX_SLEEP_INC() _Py_STATS_EXPR(ft_stats.mutex_sleeps++)
61+
#define FT_STAT_QSBR_POLL_INC() _Py_STATS_EXPR(ft_stats.qsbr_polls++)
62+
#define FT_STAT_WORLD_STOP_INC() _Py_STATS_EXPR(ft_stats.world_stops++)
63+
#else
64+
#define FT_STAT_MUTEX_SLEEP_INC()
65+
#define FT_STAT_QSBR_POLL_INC()
66+
#define FT_STAT_WORLD_STOP_INC()
67+
#endif
5168

5269
// Export for '_opcode' shared extension
5370
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
@@ -71,6 +88,9 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
7188
#define OPT_HIST(length, name) ((void)0)
7289
#define RARE_EVENT_STAT_INC(name) ((void)0)
7390
#define OPCODE_DEFERRED_INC(opname) ((void)0)
91+
#define FT_STAT_MUTEX_SLEEP_INC()
92+
#define FT_STAT_QSBR_POLL_INC()
93+
#define FT_STAT_WORLD_STOP_INC()
7494
#endif // !Py_STATS
7595

7696

@@ -90,6 +110,10 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
90110
RARE_EVENT_INTERP_INC(interp, name); \
91111
} while (0); \
92112

113+
bool _PyStats_ThreadInit(_PyThreadStateImpl *);
114+
void _PyStats_ThreadFini(_PyThreadStateImpl *);
115+
void _PyStats_Attach(_PyThreadStateImpl *);
116+
void _PyStats_Detach(_PyThreadStateImpl *);
93117

94118
#ifdef __cplusplus
95119
}

Include/internal/pycore_tstate.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,15 @@ typedef struct _PyThreadStateImpl {
7070

7171
// When >1, code objects do not immortalize their non-string constants.
7272
int suppress_co_const_immortalization;
73+
74+
#ifdef Py_STATS
75+
// per-thread stats, will be merged into the _Py_stats_struct global
76+
PyStats *pystats_struct; // allocated by _PyStats_ThreadInit()
77+
PyStats **pystats_tss; // pointer to tss variable
7378
#endif
7479

80+
#endif // Py_GIL_DISABLED
81+
7582
#if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED)
7683
Py_ssize_t reftotal; // this thread's total refcount operations
7784
#endif

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ PYTHON_OBJS= \
484484
Python/pylifecycle.o \
485485
Python/pymath.o \
486486
Python/pystate.o \
487+
Python/pystats.o \
487488
Python/pythonrun.o \
488489
Python/pytime.o \
489490
Python/qsbr.o \

Modules/_xxtestfuzz/fuzzer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
1111
See the source code for LLVMFuzzerTestOneInput for details. */
1212

13-
#ifndef Py_BUILD_CORE
14-
# define Py_BUILD_CORE 1
13+
#ifndef Py_BUILD_CORE_MODULE
14+
# define Py_BUILD_CORE_MODULE 1
1515
#endif
1616

1717
#include <Python.h>

PCbuild/_freeze_module.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@
255255
<ClCompile Include="..\Python\pylifecycle.c" />
256256
<ClCompile Include="..\Python\pymath.c" />
257257
<ClCompile Include="..\Python\pystate.c" />
258+
<ClCompile Include="..\Python\pystats.c" />
258259
<ClCompile Include="..\Python\pystrcmp.c" />
259260
<ClCompile Include="..\Python\pystrhex.c" />
260261
<ClCompile Include="..\Python\pystrtod.c" />

PCbuild/_freeze_module.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@
370370
<ClCompile Include="..\Python\pystate.c">
371371
<Filter>Source Files</Filter>
372372
</ClCompile>
373+
<ClCompile Include="..\Python\pystats.c">
374+
<Filter>Source Files</Filter>
375+
</ClCompile>
373376
<ClCompile Include="..\Python\pystrcmp.c">
374377
<Filter>Source Files</Filter>
375378
</ClCompile>

PCbuild/pythoncore.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@
655655
<ClCompile Include="..\Python\pymath.c" />
656656
<ClCompile Include="..\Python\pytime.c" />
657657
<ClCompile Include="..\Python\pystate.c" />
658+
<ClCompile Include="..\Python\pystats.c" />
658659
<ClCompile Include="..\Python\pystrcmp.c" />
659660
<ClCompile Include="..\Python\pystrhex.c" />
660661
<ClCompile Include="..\Python\pystrtod.c" />

Python/ceval_macros.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@
6262
#ifdef Py_STATS
6363
#define INSTRUCTION_STATS(op) \
6464
do { \
65+
PyStats *s = _PyStats_GET(); \
6566
OPCODE_EXE_INC(op); \
66-
if (_Py_stats) _Py_stats->opcode_stats[lastopcode].pair_count[op]++; \
67+
if (s) s->opcode_stats[lastopcode].pair_count[op]++; \
6768
lastopcode = op; \
6869
} while (0)
6970
#else

Python/gc.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,10 +2062,11 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
20622062
_PyErr_SetRaisedException(tstate, exc);
20632063
GC_STAT_ADD(generation, objects_collected, stats.collected);
20642064
#ifdef Py_STATS
2065-
if (_Py_stats) {
2065+
PyStats *s = _PyStats_GET();
2066+
if (s) {
20662067
GC_STAT_ADD(generation, object_visits,
2067-
_Py_stats->object_stats.object_visits);
2068-
_Py_stats->object_stats.object_visits = 0;
2068+
s->object_stats.object_visits);
2069+
s->object_stats.object_visits = 0;
20692070
}
20702071
#endif
20712072
validate_spaces(gcstate);

0 commit comments

Comments
 (0)