Memory: no transparent huge pages #358

Closed
kayg wants to merge 2 commits from issue/44-thp into dev
Owner

Part of #44 (idle RSS under 100 MB at 100k, system allocator). On top of #314: merge that first; this PR's own change is the last commit (ca95279).

Found by sampling the 100k server's memory map every 20 s on the perf VM (all of #44's open PRs combined, after the harness, while idle). RSS kept growing while idle: 126 → 155 MB over two minutes. Over the same time AnonHugePages went 31 → 84 MB.

The VM runs transparent huge pages on always, Debian's default (khugepaged/max_ptes_none = 511). The kernel's background thread merges heap regions into whole 2 MB pages even when nearly all of a region is free. A heap that malloc_trim has just thinned is exactly that. The merged pages count fully toward RSS, and trimming can't return them.

Change: calternal_memory::no_huge_pages() calls prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) once at server start. That is the second unsafe call in calternal-memory, which is still the only crate allowed one; it needs your nod, like #314's. It has no effect off Linux.

Test: huge_pages_go_off checks THP_enabled: 0 in /proc/self/status. cargo test -p calternal-memory and clippy (memory, calternal) are clean.

Perf-VM numbers with and without it (round 15) will be posted on #44.

🤖 Generated with Claude Code

Part of #44 (idle RSS under 100 MB at 100k, system allocator). **On top of #314**: merge that first; this PR's own change is the last commit (ca95279). **Found** by sampling the 100k server's memory map every 20 s on the perf VM (all of #44's open PRs combined, after the harness, while idle). RSS kept *growing while idle*: 126 → 155 MB over two minutes. Over the same time `AnonHugePages` went 31 → 84 MB. The VM runs transparent huge pages on `always`, Debian's default (`khugepaged/max_ptes_none` = 511). The kernel's background thread merges heap regions into whole 2 MB pages even when nearly all of a region is free. A heap that `malloc_trim` has just thinned is exactly that. The merged pages count fully toward RSS, and trimming can't return them. **Change:** `calternal_memory::no_huge_pages()` calls `prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)` once at server start. That is the second `unsafe` call in calternal-memory, which is still the only crate allowed one; **it needs your nod, like #314's**. It has no effect off Linux. Test: `huge_pages_go_off` checks `THP_enabled: 0` in `/proc/self/status`. `cargo test -p calternal-memory` and clippy (memory, calternal) are clean. Perf-VM numbers with and without it (round 15) will be posted on #44. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Memory: hand freed memory back after bursts
Some checks failed
CI / server (pull_request) Successful in 25s
CI / rust (pull_request) Successful in 2m18s
CI / web (pull_request) Failing after 3m3s
fdbaeee3f3
glibc keeps what a burst of work freed (the startup scan, the list store
catching up, a search index build or catch-up, the whole-Home list)
inside its heaps, a heap per thread, and rarely returns it: at 100,000
notes the server sat at 150 MB or more with about 30 MB in use (#44).

A new small crate, `calternal-memory`, calls glibc's `malloc_trim` two
seconds after the last burst (a burst arms one wait on a thread of its
own: nothing runs on a timer, an idle server never wakes up for it). It
is the one crate allowed an `unsafe` call, for that one call; the
workspace still forbids `unsafe` everywhere else. Hooked after the
startup scan, big list refreshes, search catch-up and updates, and the
whole-Home list.

Local, 100k notes, system allocator: settled 82 -> 60 MB, after searches
152-160 -> 84 MB.

Part of #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Memory: no transparent huge pages
Some checks failed
CI / server (pull_request) Successful in 22s
CI / rust (pull_request) Successful in 1m38s
CI / web (pull_request) Failing after 2m19s
ca952790c3
With transparent huge pages on `always` (Debian's default, and the perf
VM's), the kernel's background thread merges heap regions into whole
2 MB pages even when most of the region is free. A trimmed heap is
mostly free pages, so an idle server at 100,000 notes grew from 126 to
155 MB while doing nothing, 84 MB of it in huge pages that trimming
can't hand back.

The server now asks the kernel at start (`prctl(PR_SET_THP_DISABLE)`)
to keep it on normal pages, in calternal-memory, the one crate allowed
an `unsafe` call. (#44)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Superseded by #427 (#398): the same prctl(PR_SET_THP_DISABLE) commit, rebased onto #417. THP turned out to be the biggest idle factor for every allocator tried (table on #427). Closing in favour of #427.

Superseded by #427 (#398): the same `prctl(PR_SET_THP_DISABLE)` commit, rebased onto #417. THP turned out to be the biggest idle factor for every allocator tried (table on #427). Closing in favour of #427.
kayg closed this pull request 2026-10-15 10:02:26 +00:00
Some checks failed
CI / server (pull_request) Successful in 22s
CI / rust (pull_request) Successful in 1m38s
CI / web (pull_request) Failing after 2m19s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!358
No description provided.