mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 08:13:37 +08:00
fix(mem): charge the larger of tracked-live and allocator commit
cbm_mem_charged() trusted mimalloc's committed counter. In a release build that counter never decreases on purge: prim.c only tracks decommit under `#if !MI_DEBUG && MI_SECURE<=2`, so after a large spill the budget still believed memory it had already returned was held. On the linux kernel corpus with a 15 GB budget that read 19,787 MB charged against 15,000 allowed — the run spilled and swept repeatedly while the OS had the memory back. Charging max(OS charge, tracked live bytes) gives 14,379 MB peak charged, sweep down from 14,990 MB to 5,067 MB, and no budget-pressure warnings. The reasoning and the measured numbers are at the call site, because the next reader will otherwise re-derive "the allocator says X" from scratch. Also adds cbm_mem_allocator_stats_log() behind CBM_MEM_ALLOCATOR_STATS: mimalloc's own MIMALLOC_SHOW_STATS never prints for the worker, which exits through _Exit, and mi_stats_merge does not exist in the vendored v3. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
+53
-8
@@ -6,7 +6,9 @@
|
||||
* RSS queries (task_info on macOS, /proc/self/statm on Linux,
|
||||
* GetProcessMemoryInfo on Windows).
|
||||
*/
|
||||
#include "foundation/mem_events.h"
|
||||
#include "mem.h"
|
||||
#include "mem_core.h" /* cbm_mem_tracked_live_bytes */
|
||||
#include "platform.h"
|
||||
#include "log.h"
|
||||
#include "compat_fs.h"
|
||||
@@ -526,12 +528,22 @@ size_t cbm_mem_allocator_committed(void) {
|
||||
|
||||
static _Atomic size_t g_peak_charged;
|
||||
size_t cbm_mem_charged(void) {
|
||||
/* The OS number (phys_footprint on macOS, RSS elsewhere) is the charge
|
||||
* for everything the process maps; the allocator's committed bytes are
|
||||
* the floor for the memory we hold through mimalloc. macOS was measured
|
||||
* under-reporting the former after MADV_FREE_REUSABLE cycles (kernel
|
||||
* extraction: 4.1 GB charged, 15.6 GB committed, 13.4 GB tracked live),
|
||||
* so the larger of the two is the honest reading. */
|
||||
/* The OS number (phys_footprint on macOS, RSS elsewhere) is the charge for
|
||||
* everything the process maps, and the memory core's own live bytes are its
|
||||
* floor: macOS was measured under-reporting the OS number after
|
||||
* MADV_FREE_REUSABLE cycles (kernel extraction: 4.1 GB charged, 13.4 GB
|
||||
* tracked live), so the larger of the two is the honest reading.
|
||||
*
|
||||
* NOT the allocator's committed bytes. mimalloc only decrements that
|
||||
* counter when a decommit will need a matching recommit, and in a RELEASE
|
||||
* build it never will (prim/unix/prim.c: `#if !MI_DEBUG && MI_SECURE<=2`
|
||||
* sets needs_recommit=false), so a purge hands the pages back to the OS and
|
||||
* leaves the counter where it was. Measured on the kernel, 2026-09-17: 77 GB
|
||||
* purged, the counter flat at 14.8 GB, macOS phys_footprint 5.2 GB. Reading
|
||||
* it as the charge pinned the budget at its limit -- the spill sweep freed
|
||||
* 10 GB and the number did not move, so the semantic pass ran with zero
|
||||
* headroom and the run reported a 32 % budget overshoot that never happened.
|
||||
* It stays in the logs (commit_mb) as a diagnostic. */
|
||||
#if defined(__APPLE__)
|
||||
size_t os_charge = cbm_mem_footprint();
|
||||
if (os_charge == 0) {
|
||||
@@ -540,8 +552,8 @@ size_t cbm_mem_charged(void) {
|
||||
#else
|
||||
size_t os_charge = cbm_mem_rss();
|
||||
#endif
|
||||
size_t committed = cbm_mem_allocator_committed();
|
||||
size_t charged = committed > os_charge ? committed : os_charge;
|
||||
size_t tracked = cbm_mem_tracked_live_bytes();
|
||||
size_t charged = tracked > os_charge ? tracked : os_charge;
|
||||
/* High-water mark of the charge itself, at the granularity of the gate
|
||||
* that reads it (every file pull, every phase mark). RSS high-water
|
||||
* counts pages already purged to the OS but not yet reclaimed
|
||||
@@ -822,7 +834,40 @@ bool cbm_mem_phases_enabled(void) {
|
||||
return mem_phase_enabled();
|
||||
}
|
||||
|
||||
/* One line of mimalloc's stats table, logged as it comes. */
|
||||
static void mem_allocator_stats_line(const char *msg, void *arg) {
|
||||
if (!msg || !msg[0]) {
|
||||
return;
|
||||
}
|
||||
char line[512];
|
||||
size_t n = 0;
|
||||
for (const char *p = msg; *p && n + 1 < sizeof(line); p++) {
|
||||
line[n++] = (*p == '\n' || *p == '\r' || *p == '\t') ? ' ' : *p;
|
||||
}
|
||||
line[n] = '\0';
|
||||
/* trailing blanks make the log unreadable; trim */
|
||||
while (n > 0 && line[n - 1] == ' ') {
|
||||
line[--n] = '\0';
|
||||
}
|
||||
if (line[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
cbm_log_info("mem.allocator.stats", "tag", (const char *)arg, "line", line);
|
||||
}
|
||||
|
||||
void cbm_mem_allocator_stats_log(const char *tag) {
|
||||
char enabled[CBM_SZ_16];
|
||||
if (cbm_safe_getenv("CBM_MEM_ALLOCATOR_STATS", enabled, sizeof(enabled), NULL) == NULL) {
|
||||
return;
|
||||
}
|
||||
if (enabled[0] == '0' && enabled[1] == '\0') {
|
||||
return;
|
||||
}
|
||||
mi_stats_print_out(mem_allocator_stats_line, (void *)(tag ? tag : "-"));
|
||||
}
|
||||
|
||||
void cbm_mem_phase_mark(const char *label) {
|
||||
cbm_memev_phase(label); /* waste sanitizer: dormant unless CBM_MEMWASTE=1 */
|
||||
if (!mem_phase_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,12 @@ void cbm_mem_phase_reset(void);
|
||||
* Instruments that walk large structures (the result census) gate on it. */
|
||||
bool cbm_mem_phases_enabled(void);
|
||||
|
||||
/* CBM_MEM_ALLOCATOR_STATS=1: write the allocator's own reserved / committed /
|
||||
* purged table into the log at `tag`. The worker leaves through _Exit, so
|
||||
* mimalloc's at-exit statistics never appear; this is the way to see them.
|
||||
* Inert unless the variable is set. */
|
||||
void cbm_mem_allocator_stats_log(const char *tag);
|
||||
|
||||
/* Write the phase table as a JSON array of {label, bytes, hits}, biggest total
|
||||
* first. Returns bytes written (0 when disabled or empty). */
|
||||
int cbm_mem_phase_report_json(char *out, size_t size);
|
||||
|
||||
Reference in New Issue
Block a user