Skip to content

fix(mem): restore mimalloc's Linux arena-commit default (#1654) - #1666

Open
DeusData wants to merge 1 commit into
mainfrom
fix/linux-arena-eager-commit
Open

fix(mem): restore mimalloc's Linux arena-commit default (#1654)#1666
DeusData wants to merge 1 commit into
mainfrom
fix/linux-arena-eager-commit

Conversation

@DeusData

@DeusData DeusData commented Aug 16, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Restores mimalloc's own Linux default for arena_eager_commit, which cbm had overridden to 0.

Since #1360 routed ordinary malloc/new through mimalloc on Linux, the arena policy governs every allocation in the process rather than just the bound sqlite/tree-sitter populations. With lazy arena commit, mimalloc commits sub-ranges via mprotect(PROT_READ|PROT_WRITE) over a PROT_NONE reservation, and each partial commit splits the reserved VMA.

Measured on the Go corpus (Linux arm64, shipped binaries):

mimalloc mappings peak RSS
v0.9.0, any worker count 10 13.73 GB
v0.10.5, 1 worker 999 4.11 GB
v0.10.5, 4 workers 8,460 15.99 GB
v0.10.5, 18 workers 11,965 (peak ~22k) 18.87 GB

The count tracks concurrency, not corpus size. That produces both symptoms reported in #1654 from a 96-CPU/376 GB host: the mmap/mprotect churn serialises on the kernel's per-process mmap_lock (they saw 1.2% of extraction in 45 minutes, where v0.9.0 finished the tree in ~13), and the VMA count climbs toward vm.max_map_count, after which mmap fails for any size — hence mimalloc reporting it could not allocate 10 KB while free -g still showed 246 GB available.

mimalloc's default for this option is 2, meaning "eager-commit arenas only on an OS that has overcommit (i.e. linux)" — precisely because commit is free there until pages are touched. Overriding it to 0 opted Linux out of the default written for Linux. Every other platform keeps the lazy setting, where commit is not free and the upfront-memory reason still holds (Windows especially, #581).

Measured effect

Baseline build vs this build, same corpus and host:

mappings wall peak RSS
baseline 22,450 92.4 s 19.14 GB
this PR 17,312 (−23%) 92.6 s 19.22 GB

Scope — this is a mitigation, not a cure

Deliberately narrow. The remaining ~17k mappings are individual 64 KB–3 MB extraction buffers, each taking its own mmap; the worker reserves ~40 GB of address space for ~19 GB of RSS. Pooling those is the durable fix and is not attempted here.

Also measured and rejected for this PR: additionally raising purge_delay cuts mappings to 6,362 (−72%) and wall time by 40%, but raises peak RSS ~16% and OOM-killed a 24 GB host on a corpus the baseline completed. That trade is wrong for exactly the memory-pressured users hitting #1654, and would need a memory-headroom guard.

Verification

  • Linux arm64 full leg (ASan + LeakSanitizer): 176 suites, 0 failing, === All tests passed ===
  • Revert-check: with the fix reverted the new guard goes RED (51 passed, 1 failed); re-applied, green (52 passed). The test binds to the fix.
  • macOS mem suite: 52 passed.
  • Windows: not run locally; the change is #if defined(__linux__) so Windows takes the unchanged branch, but CI is the check.

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally (full Linux leg + macOS mem)
  • Lint passes (make -f Makefile.cbm lint-ci) — cppcheck + clang-format + NOLINT check all clean
  • New behavior is covered by a test (reproduce-first, RED-on-revert proven)

Refs #1654.

Since #1360 routed ordinary malloc/new through mimalloc on Linux, the
arena policy governs every allocation in the process rather than just the
bound sqlite/tree_sitter populations. cbm sets arena_eager_commit=0, so
mimalloc commits sub-ranges with mprotect(PROT_READ|PROT_WRITE) over a
PROT_NONE reservation, and each partial commit SPLITS the reserved VMA.

Measured on the Go corpus, Linux arm64, shipped binaries:

  v0.9.0   10 mappings, at ANY worker count
  v0.10.5  ~22k mappings, peak; the count tracks CONCURRENCY
           (999 at 1 worker, 8460 at 4, 11965 at 18)

Two consequences, both of which #1654 reported from a 96-CPU/376 GB host:
the mmap/mprotect churn serialises on the kernel's per-process mmap_lock,
and the VMA count climbs toward vm.max_map_count, after which mmap fails
for ANY size -- so mimalloc reported it could not allocate 10 KB while
`free -g` still showed 246 GB available.

mimalloc's own default for this option is 2, meaning "eager-commit arenas
only on an OS that overcommits (i.e. linux)", precisely because commit is
free there until pages are touched. Overriding it to 0 opted Linux out of
the default written for Linux. Restore it on Linux only; every other
platform keeps the lazy setting, where commit is NOT free and the
upfront-memory reason still holds (Windows especially, #581).

Measured effect, same corpus and host, baseline build vs this build:

  mappings  22450 -> 17312  (-23%)
  wall       92.4s -> 92.6s (unchanged)
  peak RSS  19.14 -> 19.22 GB (unchanged)

This is a partial mitigation, not a cure: the remaining ~17k mappings are
individual 64 KB-3 MB extraction buffers, each taking its own mmap (the
worker reserves ~40 GB of address space for ~19 GB of RSS). Pooling those
is the durable fix and is deliberately left out of this change.

Guard: mem_arena_eager_commit_follows_platform_commit_cost pins the
platform split so the Linux default cannot be silently opted out again.

Reproduction and controlled 2x2 (only vm.max_map_count varied) are
recorded on #1654.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant