Skip to content

Commit 7841879

Browse files
diegorussorennsax
authored andcommitted
pythonGH-128842: Collect JIT memory stats (pythonGH-128941)
trampoline size is not traced. Signed-off-by: Bojun Ren <[email protected]>
1 parent 6280bb5 commit 7841879

File tree

5 files changed

+111
-8
lines changed

5 files changed

+111
-8
lines changed

Include/cpython/pystats.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ typedef struct _optimization_stats {
135135
uint64_t remove_globals_builtins_changed;
136136
uint64_t remove_globals_incorrect_keys;
137137
uint64_t error_in_opcode[PYSTATS_MAX_UOP_ID + 1];
138+
// JIT memory stats
139+
uint64_t jit_total_memory_size;
140+
uint64_t jit_code_size;
141+
uint64_t jit_data_size;
142+
uint64_t jit_padding_size;
143+
uint64_t jit_freed_memory_size;
144+
uint64_t trace_total_memory_hist[_Py_UOP_HIST_SIZE];
138145
} OptimizationStats;
139146

140147
typedef struct _rare_event_stats {

Include/internal/pycore_code.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ extern void _Py_Specialize_ContainsOp(PyObject *value, _Py_CODEUNIT *instr);
361361
do { if (_Py_stats && PyFunction_Check(callable)) _Py_stats->call_stats.eval_calls[name]++; } while (0)
362362
#define GC_STAT_ADD(gen, name, n) do { if (_Py_stats) _Py_stats->gc_stats[(gen)].name += (n); } while (0)
363363
#define OPT_STAT_INC(name) do { if (_Py_stats) _Py_stats->optimization_stats.name++; } while (0)
364+
#define OPT_STAT_ADD(name, n) do { if (_Py_stats) _Py_stats->optimization_stats.name += (n); } while (0)
364365
#define UOP_STAT_INC(opname, name) do { if (_Py_stats) { assert(opname < 512); _Py_stats->optimization_stats.opcode[opname].name++; } } while (0)
365366
#define UOP_PAIR_INC(uopcode, lastuop) \
366367
do { \
@@ -395,6 +396,7 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
395396
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) ((void)0)
396397
#define GC_STAT_ADD(gen, name, n) ((void)0)
397398
#define OPT_STAT_INC(name) ((void)0)
399+
#define OPT_STAT_ADD(name, n) ((void)0)
398400
#define UOP_STAT_INC(opname, name) ((void)0)
399401
#define UOP_PAIR_INC(uopcode, lastuop) ((void)0)
400402
#define OPT_UNSUPPORTED_OPCODE(opname) ((void)0)

Python/jit.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ jit_free(unsigned char *memory, size_t size)
8181
jit_error("unable to free memory");
8282
return -1;
8383
}
84+
OPT_STAT_ADD(jit_freed_memory_size, size);
8485
return 0;
8586
}
8687

@@ -423,6 +424,15 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
423424
if (memory == NULL) {
424425
return -1;
425426
}
427+
#ifdef MAP_JIT
428+
pthread_jit_write_protect_np(0);
429+
#endif
430+
// Collect memory stats
431+
OPT_STAT_ADD(jit_total_memory_size, total_size);
432+
OPT_STAT_ADD(jit_code_size, code_size);
433+
OPT_STAT_ADD(jit_data_size, data_size);
434+
OPT_STAT_ADD(jit_padding_size, padding);
435+
OPT_HIST(total_size, trace_total_memory_hist);
426436
// Update the offsets of each instruction:
427437
for (size_t i = 0; i < length; i++) {
428438
instruction_starts[i] += (uintptr_t)memory;

Python/specialize.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,13 @@ print_optimization_stats(FILE *out, OptimizationStats *stats)
291291
);
292292
}
293293
}
294+
fprintf(out, "JIT total memory size: %" PRIu64 "\n", stats->jit_total_memory_size);
295+
fprintf(out, "JIT code size: %" PRIu64 "\n", stats->jit_code_size);
296+
fprintf(out, "JIT data size: %" PRIu64 "\n", stats->jit_data_size);
297+
fprintf(out, "JIT padding size: %" PRIu64 "\n", stats->jit_padding_size);
298+
fprintf(out, "JIT freed memory size: %" PRIu64 "\n", stats->jit_freed_memory_size);
299+
300+
print_histogram(out, "Trace total memory size", stats->trace_total_memory_hist);
294301
}
295302
#endif
296303

Tools/scripts/summarize_stats.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,36 @@ def get_optimizer_stats(self) -> dict[str, tuple[int, int | None]]:
539539
): (incorrect_keys, attempts),
540540
}
541541

542+
def get_jit_memory_stats(self) -> dict[Doc, tuple[int, int | None]]:
543+
jit_total_memory_size = self._data["JIT total memory size"]
544+
jit_code_size = self._data["JIT code size"]
545+
jit_data_size = self._data["JIT data size"]
546+
jit_padding_size = self._data["JIT padding size"]
547+
jit_freed_memory_size = self._data["JIT freed memory size"]
548+
549+
return {
550+
Doc(
551+
"Total memory size",
552+
"The total size of the memory allocated for the JIT traces",
553+
): (jit_total_memory_size, None),
554+
Doc(
555+
"Code size",
556+
"The size of the memory allocated for the code of the JIT traces",
557+
): (jit_code_size, jit_total_memory_size),
558+
Doc(
559+
"Data size",
560+
"The size of the memory allocated for the data of the JIT traces",
561+
): (jit_data_size, jit_total_memory_size),
562+
Doc(
563+
"Padding size",
564+
"The size of the memory allocated for the padding of the JIT traces",
565+
): (jit_padding_size, jit_total_memory_size),
566+
Doc(
567+
"Freed memory size",
568+
"The size of the memory freed from the JIT traces",
569+
): (jit_freed_memory_size, jit_total_memory_size),
570+
}
571+
542572
def get_histogram(self, prefix: str) -> list[tuple[int, int]]:
543573
rows = []
544574
for k, v in self._data.items():
@@ -1152,26 +1182,51 @@ def calc_optimizer_table(stats: Stats) -> Rows:
11521182
for label, (value, den) in optimizer_stats.items()
11531183
]
11541184

1155-
def calc_histogram_table(key: str, den: str) -> RowCalculator:
1185+
def calc_jit_memory_table(stats: Stats) -> Rows:
1186+
jit_memory_stats = stats.get_jit_memory_stats()
1187+
1188+
return [
1189+
(
1190+
label,
1191+
Count(value),
1192+
Ratio(value, den, percentage=label != "Total memory size"),
1193+
)
1194+
for label, (value, den) in jit_memory_stats.items()
1195+
]
1196+
1197+
def calc_histogram_table(key: str, den: str | None = None) -> RowCalculator:
11561198
def calc(stats: Stats) -> Rows:
11571199
histogram = stats.get_histogram(key)
1158-
denominator = stats.get(den)
1200+
1201+
if den:
1202+
denominator = stats.get(den)
1203+
else:
1204+
denominator = 0
1205+
for _, v in histogram:
1206+
denominator += v
11591207

11601208
rows: Rows = []
1161-
last_non_zero = 0
11621209
for k, v in histogram:
1163-
if v != 0:
1164-
last_non_zero = len(rows)
11651210
rows.append(
11661211
(
11671212
f"<= {k:,d}",
11681213
Count(v),
11691214
Ratio(v, denominator),
11701215
)
11711216
)
1172-
# Don't include any zero entries at the end
1173-
rows = rows[: last_non_zero + 1]
1174-
return rows
1217+
# Don't include any leading and trailing zero entries
1218+
start = 0
1219+
end = len(rows) - 1
1220+
1221+
while start <= end:
1222+
if rows[start][1] == 0:
1223+
start += 1
1224+
elif rows[end][1] == 0:
1225+
end -= 1
1226+
else:
1227+
break
1228+
1229+
return rows[start:end+1]
11751230

11761231
return calc
11771232

@@ -1205,6 +1260,28 @@ def iter_optimization_tables(base_stats: Stats, head_stats: Stats | None = None)
12051260

12061261
yield Table(("", "Count:", "Ratio:"), calc_optimization_table, JoinMode.CHANGE)
12071262
yield Table(("", "Count:", "Ratio:"), calc_optimizer_table, JoinMode.CHANGE)
1263+
yield Section(
1264+
"JIT memory stats",
1265+
"JIT memory stats",
1266+
[
1267+
Table(
1268+
("", "Size (bytes):", "Ratio:"),
1269+
calc_jit_memory_table,
1270+
JoinMode.CHANGE
1271+
)
1272+
],
1273+
)
1274+
yield Section(
1275+
"JIT trace total memory histogram",
1276+
"JIT trace total memory histogram",
1277+
[
1278+
Table(
1279+
("Size (bytes)", "Count", "Ratio:"),
1280+
calc_histogram_table("Trace total memory size"),
1281+
JoinMode.CHANGE_NO_SORT,
1282+
)
1283+
],
1284+
)
12081285
for name, den in [
12091286
("Trace length", "Optimization traces created"),
12101287
("Optimized trace length", "Optimization traces created"),

0 commit comments

Comments
 (0)