High-performance mimalloc V3 global allocator for Rust.
rustfs-mimalloc provides safe, ergonomic Rust bindings to Microsoft’s mimalloc V3 memory allocator (post-v3.5.4 interim, commit 6f45cc52). Drop-in replacement for the system allocator with excellent multi-threaded performance.
Release notes for all three crates are maintained in CHANGELOG.md.
mi_malloc_aligned for all allocations, preventing alignment bugs#[inline(always)] hot path, no intermediate function calls[dependencies]
rustfs-mimalloc = "0.7.0"
use rustfs_mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
fn main() {
let v = vec![1, 2, 3, 4, 5];
println!("{:?}", v);
}
The optional companion rustfs-mimalloc-profiling
provides SamplingAllocator<A: GlobalAlloc> and an exclusive Profiler session.
Its default core is allocator-neutral: applications select either
mimalloc::MiMalloc or rustfs_mimalloc::MiMalloc. An opt-in rustfs-allocator
feature retains SamplingMiMalloc::new(). The base allocator does not depend on
the profiling crate. See the companion README for usage and migration.
| Feature | Default | Description |
|---|---|---|
secure |
Guard pages and encoded/randomized free lists (MI_SECURE=4) | |
debug |
mimalloc debug checks | |
debug_in_debug |
Auto-enable debug in Cargo debug builds |
|
override |
Override system malloc/free on non-Windows targets |
|
local_dynamic_tls |
Use local-dynamic TLS model (fixes polars compatibility) | |
no_thp |
Disable Transparent Huge Pages on Linux/Android | |
no_profile |
Compile out allocation profiling and automatic pprof startup |
All stats, options, heap, and arena APIs are available without any feature flag.
use rustfs_mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
Implements GlobalAlloc with aligned allocation/reallocation and general
mi_free deallocation.
use rustfs_mimalloc::MiMalloc;
use rustfs_mimalloc_sys::mi_option_t;
// Version: 30504 = V3.5.4
let version = MiMalloc::version();
// Stats as JSON
let json = MiMalloc::stats_json();
// Stats in human-readable text
let text = MiMalloc::stats_print();
// Process memory info (struct)
let info = MiMalloc::process_info();
println!("Peak RSS: {} bytes", info.peak_rss);
// Process memory info (text)
let text = MiMalloc::process_info_print();
// Reset stats
MiMalloc::stats_reset();
use rustfs_mimalloc::MiMalloc;
use rustfs_mimalloc_sys::mi_option_t;
// Return memory to OS immediately (default delay: 10ms)
// Configure before starting other threads.
unsafe { MiMalloc::option_set(mi_option_t::mi_option_purge_delay, 0); }
// Read an option
let delay = MiMalloc::option_get(mi_option_t::mi_option_purge_delay);
// Toggle
unsafe {
MiMalloc::option_enable(mi_option_t::mi_option_show_errors);
MiMalloc::option_disable(mi_option_t::mi_option_show_errors);
}
use core::ptr::NonNull;
use rustfs_mimalloc::{MI_SMALL_SIZE_MAX, MiMalloc};
unsafe {
let ptr = NonNull::new(MiMalloc::malloc_csize(64)).expect("allocation failed");
assert!(64 <= MI_SMALL_SIZE_MAX);
MiMalloc::free_csize_nonnull(ptr, 64);
}
Use MiMalloc::malloc_csize, zalloc_csize, wmalloc_small, wzalloc_small,
free_csize, free_csize_nonnull, free_small, and free_small_nonnull only
when the original allocation size contract is known and the pointer is managed
by mimalloc. These mirror mimalloc V3.5.4’s small, word-size, and constant-size
fast paths for language runtimes and other allocation-heavy systems.
Use MiMalloc::free_csize_aligned or free_csize_aligned_nonnull when both
the original size and alignment are known. They preserve the small-free fast
path only when the allocation is not over-aligned, avoiding incorrect routing
for small allocations with a larger alignment.
The submodule pins upstream commit 6f45cc52, after the v3.5.4 interim tag. The source version remains 30504. This pin includes upstream fixes through 2026-10-16, plus new statistics-merge and arena-purge controls.
The 31 commits after the previous 4e64fce6 pin add four public C APIs,
custom tracking hooks, quiescent fork cleanup, natural-alignment improvements
and allocation cache offsets. The source is a development snapshot, not a new
upstream release. The immediate-purge threshold for large ranges is 32 MiB.
See the upstream and performance review for the PR audit,
verification results, and remaining optimization opportunities.
MiMalloc::expand resizes within usable capacity without copying or moving;
failure keeps the allocation valid. Padding-enabled builds can reject it.MiMalloc::rezalloc{,_aligned} and Heap::rezalloc{,_aligned} preserve
existing content and zero growth for blocks originally zero-initialized.
Heap::calloc and recalloc check multiplication overflow.ThreadHeap::{calloc,malloc_csize,zalloc_csize,realloc,rezalloc} extend cached
worker contexts. They remain unsafe raw allocation operations with the same
parent-heap and thread-context lifetime requirements.Audit the binding names and option IDs against the pinned C headers with
python3 scripts/check_upstream_api.py --json target/upstream-api-audit.json.
The inventory records unbound C exports; name parity does not verify the ABI.
python3 scripts/check_upstream_abi.py also compiles C projections of the
Rust extern signatures, callbacks and three exposed repr(C) layouts against
the pinned headers. python3 scripts/test_upstream_abi.py verifies that
intentional ABI mutations fail the gate. The GCC/Clang check uses the host C
ABI and the bindings’ supported C-compatible type subset; it does not check
inline Rust helpers, link every symbol, or establish cross-target/runtime
safety. Runtime integration tests remain required.
MiMalloc::thread_id_of returns a snapshot of a page’s mimalloc owner,
or zero for null/abandoned/detached pages. It is not a Rust ThreadId and
does not establish the preconditions for local-only free operations.Heap::set_debug_fill_preserve atomically adjusts how much of a block is
skipped by debug pattern filling. Free-list metadata can still overwrite the
prefix; this does not permit reading freed Rust objects.ThreadHeap::quiescent_abandon abandons the current worker’s pages
while retaining live blocks and the reusable context. Raw mi_theap_delete
additionally invalidates the context and every cached alias; it has no
wrapper deletion method. Reacquire through Heap::thread_local after deletion.Custom allocation tracking from upstream #1396
uses application-defined mi_track_malloc_hook and mi_track_free_hook symbols:
CFLAGS=-DMI_TRACK_CUSTOM cargo run --release -p rustfs-mimalloc --example custom_tracking
See the example for exact ABI and
metadata. Hooks can run concurrently and during startup; they must not allocate,
panic or unwind. Aligned allocations can report their backing block rather than
the adjusted pointer; heap destruction does not report each freed block. This is
a C build opt-in, not a runtime registration API or a complete leak detector.
Default builds leave it disabled. fork support requires every other thread to
be quiescent outside mimalloc; it does not make unrestricted Rust code safe in
the child of a multithreaded process.
MiMalloc::free_small_local{,_nonnull} exposes the new thread-local free fast
path. These unsafe functions require a small allocation whose page is still
owned by the calling thread. They are unsuitable for general cross-thread
frees and are not used by GlobalAlloc.mi_profiler_snapshot and mi_pprof_profiler_{new,delete} are available as
experimental raw FFI, together with all four mi_option_profile_* controls.
The profiler ABI adds on_snapshot; allocation/free callbacks now receive
*mut mi_heap_t. Update custom mi_profiler_t initializers accordingly.Heap::zalloc_aligned and Heap::realloc_aligned preserve requested alignment.
MiMalloc::good_size, options_print, option_get_clamp, and default option
setters support capacity planning and diagnostics.MiMalloc::arenas_purge() immediately processes pending arena purges for the
main subprocess. It can issue OS memory calls and should be used where the
release of delayed arena memory is worth the added latency. Configure
mi_option_arena_purge_immediate_size to eagerly purge large freed ranges, or
mi_option_stats_merge_threshold to tune thread-heap statistics merging.To collect a built-in heap profile, start the application with
MIMALLOC_PROFILE=/path/to/profile (protobuf), or a name ending in .heap
(text). Sampling is inactive unless enabled.
Interim limitation: the pinned upstream code copies profile_disabled=true
into new thread heaps without clearing it. Dedicated-heap and worker-thread
profiling may therefore remain inactive. The integration test verifies actual
samples on the main heap in a single-threaded process; it does not establish
complete multi-thread profiling coverage. Custom profilers must remain alive
until sampling is stopped, the profiler is detached, all sampled allocations
are freed, and callbacks have finished. There is deliberately no safe RAII
profiler wrapper yet.
Backtraces are configured for macOS, GNU Linux and Windows. musl builds keep
profiling APIs but do not automatically link an external unwinder; their stack
traces may be empty. FreeBSD/DragonFly require libexecinfo and libutil.
The sys crate exposes upstream’s existing mi_register_error,
mi_register_output, and mi_register_deferred_free functions. These are raw,
unsafe APIs: register without concurrent allocator activity, keep callback state
alive until unregistration and completion of in-flight calls, and do not unwind
through C. Prefer MiMalloc::stats_print / options_print when a process-wide
callback is unnecessary.
rustfs_mimalloc::set_current_thread_in_threadpool();
Call this from custom thread-pool worker threads to tell mimalloc that the
current thread can run arbitrary tasks. This is a thin wrapper around mimalloc
V3’s mi_thread_set_in_threadpool() API.
use rustfs_mimalloc::heap::Heap;
// Create a custom heap
let heap = Heap::new().expect("failed to create heap");
// Allocate from it
unsafe {
let ptr = heap.malloc(128);
core::ptr::write_bytes(ptr, 0xAB, 128);
rustfs_mimalloc_sys::mi_free(ptr as *mut core::ffi::c_void);
}
// Delete heap (moves live blocks to main heap)
heap.delete();
use rustfs_mimalloc::heap;
// Reserve a memory arena
let arena = heap::reserve_os_memory(1024 * 1024 * 64, true, true, true)
.expect("failed to reserve arena");
// Create a heap in the arena
let heap = heap::Heap::new_in_arena(arena).expect("failed to create heap");
mimalloc crate| Aspect | rustfs-mimalloc |
mimalloc crate |
|---|---|---|
| mimalloc version | V3 only (post-v3.5.4 interim, commit 6f45cc52) |
V2/V3 (configurable) |
| Alignment | Always aligned | Conditional |
| TLS model | Configurable | Forced initial-exec |
| Stats API | JSON + text + struct | JSON only |
| Options API | Full get/set/enable/disable | Partial |
| Heap API | Full (create/delete/destroy/alloc) | Basic |
| Arena API | Full (reserve/manage) | Basic |
| Cargo MSRV declaration | None; stable and 1.95.0 checked in CI | 1.46.0 |
This crate exclusively targets mimalloc V3. Key improvements over V2:
GlobalAlloc allocation and reallocation methods use mimalloc’s aligned APIs. Previous implementations tried to skip aligned calls for small alignments, causing alignment bugs and crashes. Its cost should be measured for the target workload before considering any fast-path change.
Linux and FreeBSD use initial-exec for performance by default. Enable local_dynamic_tls for dynamic-loading compatibility. macOS retains upstream’s pthread TLS implementation; the feature does not switch it to compiler thread locals.
| OS | Architecture | Status |
|---|---|---|
| Linux | x86_64, aarch64, arm, riscv64 | ✅ Tested in CI |
| macOS | x86_64, aarch64 | ✅ Tested in CI |
| Windows | x86_64, aarch64 | ✅ Tested in CI |
| FreeBSD | x86_64 | ✅ Should work |
| Linux (musl) | x86_64 | ✅ Tested in CI |
For an MSVC binary that must not depend on vcruntime140.dll or ucrtbase.dll,
enable Rust’s static CRT target feature for the final binary. The build script
passes the matching /MT setting to mimalloc through cc; do not add a
conflicting /MD flag manually.
# .cargo/config.toml
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
This is validated in the Windows CI matrix.
Applications that do not need sampled heap profiles can opt out at build time:
[dependencies]
rustfs-mimalloc = { version = "0.7.0", features = ["no_profile"] }
This removes profiling sampling from allocation/free paths and disables
MIMALLOC_PROFILE automatic startup. Allocation alignment, zeroing, statistics,
error/output/deferred-free callbacks, and secure/debug checks remain
available. Debug builds may still sample guarded allocations for debugging.
Use MiMalloc::profiling_enabled() to query the linked allocator’s capability.
Cargo features unify across dependencies: enabling no_profile anywhere disables
allocation profiling for that linked allocator. Leave it off when profiling is
required. It is off by default. Benchmark your workload before enabling it:
local tests improved 64 KiB allocations but slightly slowed 64-byte allocations;
no reliable 4 KiB single-thread gain was established.
Raw profiler control symbols remain available for ABI compatibility. Explicit snapshot callbacks still run, but profiles contain no allocation samples. The feature changes sampling capability; it does not change page-retention, purging, NUMA or thread-local heap ownership policies.
Reproduce default versus no_profile measurements with
python3 scripts/bench_profile_abba.py --offline --output target/profile-abba-run1.
The driver freezes both builds before measuring A1/B1/B2/A2 and records per-workload
drift checks; omit --offline if dependencies are not cached.
When one worker frequently alternates between several heaps, cache a view for that worker to avoid repeating mimalloc’s TLS lookup on each allocation:
use rustfs_mimalloc::heap::Heap;
let heap = Heap::new().expect("heap allocation failed");
let local = heap.thread_local().expect("thread context unavailable");
unsafe {
let ptr = local.zalloc_aligned(4096, 64);
assert!(!ptr.is_null());
// Use the block, then free it normally (also permitted on another thread).
rustfs_mimalloc_sys::mi_free(ptr.cast());
}
ThreadHeap borrows its parent heap and is neither Send nor Sync. Its
malloc, zalloc, malloc_aligned and zalloc_aligned methods return raw
pointers with the same allocation contracts as the parent heap. Keep the view
on its creating thread, create it outside hot loops, and do not use it from
thread-local destructors or after explicit mimalloc thread finalization. Dropping the view does
not free blocks; deleting its parent preserves live allocations in the main
heap. This API does not change GlobalAlloc or global page-retention settings.
The heap_lookup benchmark is a single-heap control; heap_switching
alternates two heaps. Run reproducible comparisons with:
python3 scripts/bench_heap_abba.py --output target/heap-abba-run1
The script records A1/B1/B2/A2 in fresh processes and rejects speedup conclusions
if either baseline drift or candidate drift exceeds 5%. Add --offline when
all Cargo dependencies are already cached. Results are local microbenchmarks,
not a guarantee of production throughput.
The workspace and member manifests intentionally omit rust-version. CI
checks the stable toolchain and compatibility with Rust 1.95.0. A passing
compatibility job does not establish the lowest supported Rust version;
dependencies may have their own requirements.
Licensed under the Apache License, Version 2.0. See LICENSE.
The mimalloc C library is licensed under the MIT License. See rustfs-mimalloc-sys/c_src/mimalloc/LICENSE.