Skip to main content

leviath_alloc/
lib.rs

1//! Allocator tuning for the leviath binary.
2//!
3//! This crate holds exactly one capability: telling mimalloc to purge freed
4//! memory at free time instead of deferring it. It exists as a separate
5//! crate because the call is an `unsafe` C FFI invocation and the rest of
6//! the workspace compiles under `unsafe_code = "forbid"`; the policy is that
7//! OS-level unsafe lives in small audited places, and this crate is one.
8//!
9//! ## Why purge at free time
10//!
11//! mimalloc's default purge delay (10ms) is not a timer. It is a minimum age
12//! checked only when the owning thread next touches the allocator. A daemon
13//! worker thread that goes idle right after a burst therefore never returns
14//! its freed pages to the OS: the process sits tens to hundreds of MB above
15//! its idle baseline holding pages that contain nothing. Measured on the
16//! daemon: byte-for-byte flat across 10 idle minutes, then released by the
17//! next moment of allocator activity.
18//!
19//! With a delay of zero the `free` that empties a span performs the OS
20//! handoff itself, so nothing ever waits on future activity. The cost is a
21//! syscall per emptied span (paid at run-reap, off every latency path) and
22//! re-faulting pages when a later burst reallocates (milliseconds per burst
23//! ramp, measured indistinguishable end to end on the benchmark suite). The
24//! win is a daemon whose resident memory actually returns to its baseline
25//! when work finishes.
26//!
27//! A user who exports `MIMALLOC_PURGE_DELAY` themselves has made a choice,
28//! and [`use_purge_at_free_unless_overridden`] leaves it alone.
29//!
30//! ## Audit note (the single unsafe call)
31//!
32//! `mi_option_set(option, value)` writes a `long` into mimalloc's static
33//! options table. It allocates nothing, frees nothing, holds no lock, and is
34//! documented callable at any time; subsequent purge decisions read the
35//! stored value. The option index for `mi_option_purge_delay` is `15` in
36//! the mimalloc v2 header vendored by `libmimalloc-sys` 0.1.49, anchored on
37//! both sides by constants that crate does bind: `mi_option_eager_commit_delay
38//! = 14` and `mi_option_use_numa_nodes = 16` (the crate predates the
39//! `reset_delay` to `purge_delay` rename and simply does not name index 15).
40//! The test below asserts the round trip through `mi_option_get`, so a
41//! vendored-header reordering would fail loudly rather than silently tune
42//! the wrong knob.
43
44/// The environment variable mimalloc itself reads for this option; a user
45/// who set it keeps their value.
46pub const PURGE_DELAY_VAR: &str = "MIMALLOC_PURGE_DELAY";
47
48/// `mi_option_purge_delay` in the vendored mimalloc v2 option enum. See the
49/// module-level audit note for how this index is pinned.
50#[cfg(feature = "mimalloc")]
51const MI_OPTION_PURGE_DELAY: libmimalloc_sys::mi_option_t = 15;
52
53/// Configure mimalloc to purge freed memory at free time, unless the user
54/// chose their own delay via [`PURGE_DELAY_VAR`].
55///
56/// Returns whether the option was applied, so the decision is observable in
57/// tests. Call once, early in `main`; pages freed before the call simply
58/// purge on the pre-existing schedule.
59#[cfg(feature = "mimalloc")]
60pub fn use_purge_at_free_unless_overridden() -> bool {
61    if std::env::var_os(PURGE_DELAY_VAR).is_some() {
62        return false;
63    }
64    // SAFETY: writes a long into mimalloc's in-process options table; no
65    // memory is allocated, freed, or aliased. See the module audit note.
66    unsafe { libmimalloc_sys::mi_option_set(MI_OPTION_PURGE_DELAY, 0) };
67    true
68}
69
70/// Without the mimalloc feature there is nothing to tune: builds on the
71/// system allocator keep their platform defaults.
72#[cfg(not(feature = "mimalloc"))]
73pub fn use_purge_at_free_unless_overridden() -> bool {
74    false
75}
76
77#[cfg(all(test, feature = "mimalloc"))]
78mod tests {
79    use super::*;
80
81    /// The applied value must be readable back through mimalloc itself: this
82    /// is the guard against the option index drifting in a future vendored
83    /// header (the failure mode would be silently tuning the wrong knob).
84    #[test]
85    fn purge_at_free_is_applied_and_round_trips_through_mimalloc() {
86        temp_env::with_var_unset(PURGE_DELAY_VAR, || {
87            assert!(use_purge_at_free_unless_overridden());
88            // SAFETY: reads a long from the options table just written above.
89            let value = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) };
90            assert_eq!(value, 0);
91        });
92    }
93
94    /// An exported MIMALLOC_PURGE_DELAY is the user's decision, whatever the
95    /// value - even an explicit "0" is theirs to own, not ours to rewrite.
96    #[test]
97    fn a_user_exported_delay_is_left_alone() {
98        temp_env::with_var(PURGE_DELAY_VAR, Some("25"), || {
99            assert!(!use_purge_at_free_unless_overridden());
100        });
101        temp_env::with_var(PURGE_DELAY_VAR, Some("0"), || {
102            assert!(!use_purge_at_free_unless_overridden());
103        });
104    }
105}