Skip to content

v3.6.0

Latest

Choose a tag to compare

@amsokol amsokol released this 25 Oct 10:05
v3.6.0
05014c9

Updates to mimalloc v3.6.0 (upstream release of 2026-10-23, 223 commits after v3.5.3) and adapts mimalloc::Option to it.

[dependencies]
mimalloc = { git = "https://github.com/amsokol/mimalloc", tag = "v3.6.0" }

Before you upgrade: what can affect your program

  1. Breaking change in this crate: three mimalloc::Option variants were renamed, following upstream. Update the names if you use them:

    v3.5.3+1 v3.6.0 MIMALLOC_<NAME>
    Option::GuardedPrecise Option::UnsafeGuardedPrecise unsafe_guarded_precise
    Option::MinimalPurgeSize Option::ArenaPurgeMinSize arena_purge_min_size
    Option::CollectMergesStats Option::StatsMergeOnCollect stats_merge_on_collect

    Option::name() returns the new names. Of the old environment variables, only MIMALLOC_MINIMAL_PURGE_SIZE is still accepted (mimalloc keeps it as a legacy name); MIMALLOC_GUARDED_PRECISE and MIMALLOC_COLLECT_MERGES_STATS are silently ignored now, so rename them in your deployment. Option is #[non_exhaustive], and Option::ALL grew from 39 to 45 entries.

  2. Cross-thread page reclaim is now on by default (page_reclaim_on_free: 0 → 1). A thread that frees a block into an abandoned page can now take that page over, even when the page came from another thread. This is upstream's change to improve reuse and speed in multi-threaded programs; it can also change how much memory a program with many short-lived or pooled threads holds.

    • Since 3.6.0 the option is only a default for new heaps: each heap copies it when it is created. The heap behind the Rust global allocator is created before main, so Option::PageReclaimOnFree.set(..) from main does not change it. To get the 3.5 behaviour, start the process with MIMALLOC_PAGE_RECLAIM_ON_FREE=0.
  3. Large freed ranges are now returned to the OS immediately. A freed range of at least arena_purge_immediate_size (new option, 32 MiB by default, in KiB) is purged as it is freed, whatever purge_delay × arena_purge_mult says. A program that frees blocks of 32 MiB or more will see its RSS drop at once instead of after about 4 s, and may pay more page faults when it allocates such blocks again. A negative purge_delay still never purges. Option::ArenaPurgeImmediateSize.set(..) works from main (read on every free). collect(force) is unchanged.

  4. Memory layout of small blocks changed. Upstream now gives blocks a per-page, hashed cache offset for the first block, to spread small allocations over the CPU cache sets (upstream issue #1121), and moves 64 KiB blocks into large pages. The goal is fewer cache conflicts; the side effect is that the usable layout and the RSS of a program can differ somewhat from 3.5.x. Measure with process_info().current_rss if memory matters to you.

  5. Transparent huge pages (Linux). Upstream reworked how the huge page size is detected (it now reads hpage_pmd_size). If the kernel's huge page size is over 8 MiB (for example arm64 with 64 KiB pages), mimalloc now treats the system as having no THP and marks its mappings MADV_NOHUGEPAGE. On the usual 2 MiB x86-64 setup nothing changes: allow_thp still defaults to 2, no_thp still builds with MI_DEFAULT_ALLOW_THP=0, and MIMALLOC_ALLOW_THP=0 still overrides it.

  6. A heap profiler is now compiled in and always available. Upstream built a profiler into mimalloc (MI_PROFILE, on by default). Upstream says it adds overhead only once it is activated with MIMALLOC_PROFILE=<basename>, which writes pprof snapshots (I did not measure the cost of having it compiled in). Its settings are the new Option::Profile* variants (MIMALLOC_PROFILE_ALLOC_INTERVAL, _INUSE_INTERVAL, _TIME_INTERVAL, _SAMPLE_RATE); the profiler reads them once at process start, so set them with environment variables, not with Option::set from main. Upstream also keeps detailed statistics per size bin always on. Limitation of this crate: upstream's CMake build detects execinfo.h/libunwind and defines MI_HAS_EXECINFOH; this crate's build.rs does not, so on Unix the profiler records samples without call stacks (its backtrace function is a stub returning 0 frames). On Windows it uses CaptureStackBackTrace. I checked on Linux that MIMALLOC_PROFILE writes snapshot files; they contain no stacks.

Changes in this crate

  • c_src/mimalloc is v3.6.0. build.rs needed no change: src/static.c still includes the sources, which upstream moved into src/alloc, arena, heap, prim, profile and util (so options.c is now src/util/options.c).

  • mimalloc::Option follows mi_option_t of 3.6.0: the three renames above and six new variants:

    Variant MIMALLOC_<NAME> Default Notes
    ProfileAllocInterval profile_alloc_interval 1 GiB bytes allocated between profiler snapshots; 0 disables
    ProfileInuseInterval profile_inuse_interval 0 (off) snapshot when in-use bytes grew by this much
    ProfileTimeInterval profile_time_interval 0 (off) seconds between snapshots
    ProfileSampleRate profile_sample_rate 512 KiB profiler sampling rate in bytes
    StatsMergeThreshold stats_merge_threshold 512 KiB merge thread-heap statistics into the heap after this many bytes; 0 disables
    ArenaPurgeImmediateSize arena_purge_immediate_size 32 MiB (in KiB) see point 3; get_size() returns bytes

    The four Profile* options only matter when MIMALLOC_PROFILE is set, and are read once at process start (use the environment variables). StatsMergeThreshold and ArenaPurgeImmediateSize are read on every use.

  • Documentation updated to the 3.6.0 behaviour: PurgeDelay, ArenaPurgeMult, ArenaPurgeMinSize and collect now mention the immediate purge of large ranges; PageReclaimOnFree states the new default and that set from main has no effect on the global allocator's heap; AllowThp notes the over-8 MiB huge page case; the README tuning table has rows for ArenaPurgeImmediateSize and PageReclaimOnFree.

  • Version: 3.6.0. The +1 revision suffix of v3.5.3+1 is dropped, because the upstream version moved. The Rust API otherwise matches v3.5.3+1: MiMalloc, usable_size, process_info, collect, and the secure, override and no_thp features.

  • cc: Cargo.toml still requires cc = "1.8" (the minimum, so consumers are not forced to a newer cc); Cargo.lock now pins cc 1.10.0 (was 1.9.0).

  • process_info() and collect() are unchanged. I compared the C implementations of mi_process_info and mi_collect with 3.5.3; they are identical on Unix, macOS and WASI, and the Windows version differs only in a string literal. The table of sources in the ProcessInfo rustdoc still holds.

  • Tested on Linux x86-64 in debug and release, and with the secure, override and no_thp features (option names/discriminants against mi_options_print_out, set/get round trip, process_info, and RSS going back down after freeing 400 MiB), plus cargo clippy and cargo doc. Not tested on Windows, macOS, 32-bit or other targets in this release.

What changed in mimalloc 3.5.3 → 3.6.0 (upstream)

Upstream summary (its readme): "(v3) add initial profiling hooks and default profiler, "always on" detailed statistics per size-bin, and always-on profiling. Initial rust bindings. Better support for large OS page sizes, support a quiescent state based reclamation interface (for CPython), better cache line aware allocation, fix heap visitor where some pages might be skipped, improve fork support, avoid over-allocation for naturally aligned allocations, fix stderr console output for XBox, fix huge allocation rollback, refactor the source and include files, immediately purge very large allocations, fix bcrypt dependency on windows, fix heap-destroy bug when a fresh heap was allocated at the same address (giving a segfault), page_reclaim_on_free is now on by default."

Full list: microsoft/mimalloc@v3.5.3...v3.6.0. The parts below are grouped by what they mean for a user. Items marked (C API) are in mimalloc.h but not wrapped by this crate; items marked (CMake) are upstream build options that this crate's build.rs does not use.

Behaviour and defaults

  • page_reclaim_on_free is on by default (cross-thread reclaim on free); the option is now a per-heap creation default. See point 2 above.
  • Freed ranges of at least 32 MiB are purged immediately (arena_purge_immediate_size); an explicit mi_arenas_purge() (C API) was added (upstream issue #1422). Purging is also allowed in arena_free (PR #1448).
  • Options renamed (guarded_precise → unsafe_guarded_precise, minimal_purge_size → arena_purge_min_size, collect_merges_stats → stats_merge_on_collect), new options for the profiler, stats_merge_threshold and arena_purge_immediate_size. guarded_sample_rate now means "guard an object about every N requested bytes" (was "1 out of N allocations"); its default stays 0 and it only matters with MI_GUARDED, which this crate does not enable.
  • Small blocks get a cache offset based on a hash of the page; large allocations get a cache offset as well; 64 KiB blocks move into large pages (issues #1121; PR #1339).
  • page_full_retain adapts when cross-thread frees revive full pages. Stat merging between thread heaps and the heap was reworked (a regression from it was fixed).
  • No over-allocation for naturally aligned requests (PR #1408); the "naturally aligned" condition was relaxed and is checked in debug builds.
  • On Windows, stderr output no longer attaches to the parent console.

Profiling and statistics

  • Built-in pprof heap profiler (protobuf and text formats), activated by MIMALLOC_PROFILE, with allocation/in-use/time interval snapshots, a custom allocation-hook API (mimalloc-profile.h), mapped-library output for Windows and macOS, libunwind support on riscv and Alpine, a concurrent profiler hashtable, and fixes for profile timestamps, recursive profiler invocation, allocation failures, new heaps not being sampled, and a sample-rate reset for no-sample allocations. See point 6 for what this crate's build includes.
  • Detailed per-size-bin statistics are always collected; the stats output uses a huge bin and was cleaned up.
  • Custom allocation tracking hooks with full allocation metadata (CMake: MI_TRACK=CUSTOM); the tracking functions moved from mimalloc.h to mimalloc/track.h.

New features

  • Fork support: threads must be quiescent (not inside mimalloc) at the fork. test-fork was added.
  • Quiescent-state-based reclamation (QSBR) primitives, written for CPython (mimalloc/qsbr.h).
  • (C API) mi_thread_id_of, mi_theap_delete, mi_theap_quiescent_abandon, mi_free_small_local / mi_free_small_local_nonnull, mi_heap_set_debug_fill_preserve, mi_arenas_purge; an arena wrapper; heap visitors now document that they assume no concurrent heap access.
  • Tests were added for reallocarr growth, zero count and overflow, and for arenas, bitmaps, fork, pprof and QSBR.
  • mi_malloc_size and mi_malloc_usable_size are defined without MI_MALLOC_OVERRIDE; mi_free_size size-mismatch checking for small allocations (issue #1423), and -DMI_OPT_FREE_SMALL=OFF (CMake) keeps mi_free_size from using mi_free_small.
  • Upstream also added its own Rust crate and split it into a -sys crate (issues #1405, PR #1449). This repository does not use it.

Bug fixes

  • mi_heap_visit_blocks could skip pages; owned OS pages are now visited, page_count_os is maintained, and area->used is more precise during a heap walk.
  • Heap delete/destroy with OS-owned pages; a segfault in mi_heap_destroy when a fresh heap was allocated at the address of a destroyed one; the theap cache clears its heap field on destroy; blocks freed by other threads are collected before a page's used count is reset.
  • Huge-allocation rollback: the failing chunk is republished and the chunkmap bit is restored if bits were cleared temporarily.
  • A data race on the thread-done pthread key at process exit.
  • Forced exit on Windows where threads are killed while they may hold a lock (issue #1377).
  • Assertion failures with pinned pages and guards, with an empty theap in _mi_page_update_sample_countdown, and a spurious warning for mi_free_size in guarded builds; a missing page abandon for QSBR pages.
  • Linux huge pages: the interaction of PMD-sized and transparent huge pages (PR #1431) and MADV_NOHUGEPAGE for oversized huge pages; a build error with MI_NO_THP after that change; a -Wsign-compare warning when reading hpage_pmd_size.
  • mi_free_block_mt atomics: a relaxed CAS was tried and then rolled back to a single cas_weak_acq_rel; the self pointer read in mi_free is relaxed because a pointer handoff between threads already establishes happens-before; a false positive in TSAN.

Performance (code generation)

  • mi_free and mi_malloc fast paths: restored and simplified several variants, fields loaded early, tail calls improved for MSVC, an x64 path removed, a compiler barrier restricted to arm64, mi_prefetch definitions, and better code for x64, arm32 and riscv. The layout of mi_page_t changed to avoid ldp/stp on arm64 (issue #1433). AVX2 is only used on 64-bit. Upstream does not give a combined benchmark for 3.6.0, so measure your own workload.

Platforms and build

  • Windows: ProcessPrng on Windows 8+ and a statically linked BCryptGenRandom on UWP/Xbox (issue #1426); fixed a load error on Windows 7; UWP compilation; no more bcrypt.h include; __ImageBase declared extern in C builds; C11 atomics when clang compiles mimalloc as C for an MSVC target; MSVC C compilation fixes; mingw fixes.
  • Other: arm64_32 support (PR #1444), Emscripten allocates its OS memory from the built-in allocator, MI_ARENA_SLICE_SHIFT choice on WASI, MSan awareness of memory written by raw syscalls, no syscalls on Android (CPython change), the weak std::get_new_handler can be left out of C builds, and the aligned-allocation fallback warning can be disabled; macOS exports the C++ operators so overriding new/delete works when built as C; module maps for FreeBSD; arm32 and 32-bit test fixes.
  • Source reorganisation: src/ split into alloc, arena, heap, prim, profile and util, alloc-generic.c split off page.c, arena.c and bitmap.c refactored, headers reorganised under include/mimalloc/, readme files added to source directories, and debug builds compile independently while release builds use static.c.
  • Workflows and CI were hardened; the MI_OPT_FREE_SMALL CMake option's description changed; -Wpedantic was dropped from the CMake warnings.

Full Changelog: v3.5.3+1...v3.6.0