From 9981f9819669d4100006464c5caa54916ae67b81 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 18 Sep 2026 07:44:40 +0000 Subject: [PATCH] perf(cli): ship mimalloc in the production feature glibc malloc has no setting that fits a daemon indexing on dozens of worker threads. Measured on beta.41 warming 12 projects: two arenas put 60% of daemon CPU into arena locks; unlimited arenas held 14 GB of freed heap out of 18 GB RSS, and because admission trusts measured RSS the graph never activated again after one peak. Under mimalloc on the same host and corpus the lock share fell to noise and RSS followed live data, 8-13 GB between phases with the same peak, so the resident-memory refusal clears on its own. The production feature now selects alloc-mimalloc, which builds on all four release targets. --- crates/tracedecay-cli/Cargo.toml | 18 +++++++++++++----- crates/tracedecay-cli/src/main.rs | 10 +++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/tracedecay-cli/Cargo.toml b/crates/tracedecay-cli/Cargo.toml index c12e758518..f3dd06146c 100644 --- a/crates/tracedecay-cli/Cargo.toml +++ b/crates/tracedecay-cli/Cargo.toml @@ -74,7 +74,7 @@ hotpath-cpu = [ "hotpath/hotpath-cpu", ] hotpath-mcp = ["hotpath", "hotpath/hotpath-mcp"] -production = ["tracedecay/production"] +production = ["tracedecay/production", "alloc-mimalloc"] # Opt-in tiered graph storage; not part of `production`. graph-disk-tier = ["tracedecay/graph-disk-tier"] token-counting = ["tracedecay/token-counting"] @@ -119,10 +119,18 @@ lang-fsharp = ["tracedecay/lang-fsharp"] lang-quint = ["tracedecay/lang-quint"] lang-toml = ["tracedecay/lang-toml"] lang-lean = ["tracedecay/lang-lean"] -# Opt-in global allocators for the shipped binary (daemon included). The -# default stays glibc malloc; selecting an allocator is a deploy decision. -# When several allocator features are enabled at once (e.g. `--all-features`), -# main.rs resolves them by fixed precedence instead of failing the build: +# Global allocators for the shipped binary (daemon included). `production` +# ships mimalloc: glibc malloc has no setting that fits a daemon indexing on +# dozens of worker threads. Measured on beta.41 on a 96-core host warming 12 +# projects: with MALLOC_ARENA_MAX=2, 60% of daemon CPU was the two arena +# locks; with the cap removed, 747 arenas held 14 GB of freed-but-retained +# heap out of 18 GB RSS, and because admission trusts measured RSS the graph +# could never activate again after one peak. mimalloc has per-thread heaps +# without the lock and purges freed segments, so RSS follows live data (peak +# unchanged, ~18 GB during the warm; back to 8-13 GB between phases) and the +# resident-memory refusal clears on its own. When several allocator features +# are enabled at once (e.g. `--all-features`), main.rs resolves them by fixed +# precedence instead of failing the build: # hotpath-alloc (measurement) > alloc-jemalloc > alloc-mimalloc > system. # # alloc-jemalloc turns jemalloc background purge threads on by default: diff --git a/crates/tracedecay-cli/src/main.rs b/crates/tracedecay-cli/src/main.rs index d87c593144..647c2851fd 100644 --- a/crates/tracedecay-cli/src/main.rs +++ b/crates/tracedecay-cli/src/main.rs @@ -18,12 +18,12 @@ static HOTPATH_ALLOCATOR: hotpath::CountingAllocator = hotpath::CountingAllocato // Opt-in allocator features (see Cargo.toml). Exactly one global allocator // may exist per binary, so overlapping selections resolve by fixed precedence // rather than a compile error: hotpath-alloc's counting allocator wins in -// measurement builds, then jemalloc, then mimalloc. The default build keeps -// the system allocator (glibc malloc on Linux). The installed service unit -// must not cap glibc's arenas: `MALLOC_ARENA_MAX=2` once did, to bound +// measurement builds, then jemalloc, then mimalloc. `production` selects +// mimalloc (see Cargo.toml for the measurements); only a build that opts out +// of it keeps the system allocator, and the installed service unit must not +// cap glibc's arenas either way: `MALLOC_ARENA_MAX=2` once did, to bound // retained memory, and put 60% of a 20-worker daemon's CPU into two arena -// locks (perf on beta.41: every wait was `__lll_lock_wait_private` under -// ordinary Vec/String growth) while RSS still reached 15 GB. +// locks while RSS still reached 15 GB. #[cfg(all(feature = "alloc-jemalloc", not(feature = "hotpath-alloc")))] #[global_allocator] static JEMALLOC_ALLOCATOR: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;