Skip to content

Commit 48be46e

Browse files
authored
bpo-46072: Add some object layout and allocation stats (GH-31051)
1 parent 913e340 commit 48be46e

File tree

5 files changed

+51
-1
lines changed

5 files changed

+51
-1
lines changed

Include/internal/pycore_code.h

+12
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,19 @@ typedef struct _call_stats {
305305
uint64_t pyeval_calls;
306306
} CallStats;
307307

308+
typedef struct _object_stats {
309+
uint64_t allocations;
310+
uint64_t frees;
311+
uint64_t new_values;
312+
uint64_t dict_materialized_on_request;
313+
uint64_t dict_materialized_new_key;
314+
uint64_t dict_materialized_too_big;
315+
} ObjectStats;
316+
308317
typedef struct _stats {
309318
OpcodeStats opcode_stats[256];
310319
CallStats call_stats;
320+
ObjectStats object_stats;
311321
} PyStats;
312322

313323
extern PyStats _py_stats;
@@ -316,6 +326,7 @@ extern PyStats _py_stats;
316326
#define STAT_DEC(opname, name) _py_stats.opcode_stats[opname].specialization.name--
317327
#define OPCODE_EXE_INC(opname) _py_stats.opcode_stats[opname].execution_count++
318328
#define CALL_STAT_INC(name) _py_stats.call_stats.name++
329+
#define OBJECT_STAT_INC(name) _py_stats.object_stats.name++
319330

320331
void _Py_PrintSpecializationStats(int to_file);
321332

@@ -326,6 +337,7 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
326337
#define STAT_DEC(opname, name) ((void)0)
327338
#define OPCODE_EXE_INC(opname) ((void)0)
328339
#define CALL_STAT_INC(name) ((void)0)
340+
#define OBJECT_STAT_INC(name) ((void)0)
329341
#endif
330342

331343

Objects/dictobject.c

+12
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ As a consequence of this, split keys have a maximum size of 16.
114114
#include "Python.h"
115115
#include "pycore_bitutils.h" // _Py_bit_length
116116
#include "pycore_call.h" // _PyObject_CallNoArgs()
117+
#include "pycore_code.h" // stats
117118
#include "pycore_dict.h" // PyDictKeysObject
118119
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
119120
#include "pycore_object.h" // _PyObject_GC_TRACK()
@@ -4990,6 +4991,7 @@ _PyObject_InitializeDict(PyObject *obj)
49904991
return 0;
49914992
}
49924993
if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
4994+
OBJECT_STAT_INC(new_values);
49934995
return init_inline_values(obj, tp);
49944996
}
49954997
PyObject *dict;
@@ -5033,6 +5035,7 @@ _PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values)
50335035
{
50345036
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
50355037
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
5038+
OBJECT_STAT_INC(dict_materialized_on_request);
50365039
return make_dict_from_instance_attributes(keys, values);
50375040
}
50385041

@@ -5051,6 +5054,14 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
50515054
PyErr_SetObject(PyExc_AttributeError, name);
50525055
return -1;
50535056
}
5057+
#ifdef Py_STATS
5058+
if (shared_keys_usable_size(keys) > 14) {
5059+
OBJECT_STAT_INC(dict_materialized_too_big);
5060+
}
5061+
else {
5062+
OBJECT_STAT_INC(dict_materialized_new_key);
5063+
}
5064+
#endif
50545065
PyObject *dict = make_dict_from_instance_attributes(keys, values);
50555066
if (dict == NULL) {
50565067
return -1;
@@ -5183,6 +5194,7 @@ PyObject_GenericGetDict(PyObject *obj, void *context)
51835194
PyObject **dictptr = _PyObject_ManagedDictPointer(obj);
51845195
if (*values_ptr) {
51855196
assert(*dictptr == NULL);
5197+
OBJECT_STAT_INC(dict_materialized_on_request);
51865198
*dictptr = dict = make_dict_from_instance_attributes(CACHED_KEYS(tp), *values_ptr);
51875199
if (dict != NULL) {
51885200
*values_ptr = NULL;

Objects/obmalloc.c

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Python.h"
22
#include "pycore_pymem.h" // _PyTraceMalloc_Config
3+
#include "pycore_code.h" // stats
34

45
#include <stdbool.h>
56
#include <stdlib.h> // malloc()
@@ -695,6 +696,7 @@ PyObject_Malloc(size_t size)
695696
/* see PyMem_RawMalloc() */
696697
if (size > (size_t)PY_SSIZE_T_MAX)
697698
return NULL;
699+
OBJECT_STAT_INC(allocations);
698700
return _PyObject.malloc(_PyObject.ctx, size);
699701
}
700702

@@ -704,6 +706,7 @@ PyObject_Calloc(size_t nelem, size_t elsize)
704706
/* see PyMem_RawMalloc() */
705707
if (elsize != 0 && nelem > (size_t)PY_SSIZE_T_MAX / elsize)
706708
return NULL;
709+
OBJECT_STAT_INC(allocations);
707710
return _PyObject.calloc(_PyObject.ctx, nelem, elsize);
708711
}
709712

@@ -719,6 +722,7 @@ PyObject_Realloc(void *ptr, size_t new_size)
719722
void
720723
PyObject_Free(void *ptr)
721724
{
725+
OBJECT_STAT_INC(frees);
722726
_PyObject.free(_PyObject.ctx, ptr);
723727
}
724728

Python/specialize.c

+12
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,22 @@ print_call_stats(FILE *out, CallStats *stats)
171171
fprintf(out, "Calls to Python functions inlined: %" PRIu64 "\n", stats->inlined_py_calls);
172172
}
173173

174+
static void
175+
print_object_stats(FILE *out, ObjectStats *stats)
176+
{
177+
fprintf(out, "Object allocations: %" PRIu64 "\n", stats->allocations);
178+
fprintf(out, "Object frees: %" PRIu64 "\n", stats->frees);
179+
fprintf(out, "Object new values: %" PRIu64 "\n", stats->new_values);
180+
fprintf(out, "Object materialize dict (on request): %" PRIu64 "\n", stats->dict_materialized_on_request);
181+
fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key);
182+
fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big);
183+
}
184+
174185
static void
175186
print_stats(FILE *out, PyStats *stats) {
176187
print_spec_stats(out, stats->opcode_stats);
177188
print_call_stats(out, &stats->call_stats);
189+
print_object_stats(out, &stats->object_stats);
178190
}
179191

180192
void

Tools/scripts/summarize_stats.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,17 @@ def main():
105105
total += value
106106
for key, value in stats.items():
107107
if "Calls to" in key:
108-
print(f"{key}: {value} {100*value/total:0.1f}%")
108+
print(f" {key}: {value} {100*value/total:0.1f}%")
109+
print("Object stats:")
110+
total = stats.get("Object new values")
111+
for key, value in stats.items():
112+
if key.startswith("Object"):
113+
if "materialize" in key:
114+
print(f" {key}: {value} {100*value/total:0.1f}%")
115+
else:
116+
print(f" {key}: {value}")
117+
total = 0
118+
109119

110120
if __name__ == "__main__":
111121
main()

0 commit comments

Comments
 (0)