From c8da760a9fc2880b8045b05bf6eb6fef71b828b6 Mon Sep 17 00:00:00 2001 From: 0xMars42 <195151467+0xMars42@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:33:53 +0100 Subject: [PATCH] feat(malloc_utils): add mimalloc and tcmalloc allocator feature flags Add feature flags for mimalloc and tcmalloc as alternatives to jemalloc, which has been deprecated upstream. Both allocators override jemalloc via cfg guards, matching the existing sysmalloc override pattern. Allocator metrics are stubbed for now and can be fleshed out in a follow-up once a preferred allocator is chosen. Usage: cargo build -p lighthouse --features mimalloc cargo build -p lighthouse --features tcmalloc Closes #8840 --- Cargo.lock | 27 +++++++ common/malloc_utils/Cargo.toml | 9 +++ common/malloc_utils/src/lib.rs | 99 ++++++++++++++++++++--- common/malloc_utils/src/mimalloc_alloc.rs | 8 ++ common/malloc_utils/src/tcmalloc_alloc.rs | 8 ++ lcli/Cargo.toml | 4 + lighthouse/Cargo.toml | 4 + 7 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 common/malloc_utils/src/mimalloc_alloc.rs create mode 100644 common/malloc_utils/src/tcmalloc_alloc.rs diff --git a/Cargo.lock b/Cargo.lock index 8748be726c9..2fccc4fd829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4993,6 +4993,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libp2p" version = "0.56.1" @@ -5682,7 +5692,9 @@ version = "0.1.0" dependencies = [ "libc", "metrics", + "mimalloc", "parking_lot", + "tcmalloc", "tikv-jemalloc-ctl", "tikv-jemallocator", ] @@ -5822,6 +5834,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" @@ -8771,6 +8792,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "tcmalloc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375205113d84a1c5eeed67beaa0ce08e41be1a9d5acc3425ad2381fddd9d819b" + [[package]] name = "tempfile" version = "3.23.0" diff --git a/common/malloc_utils/Cargo.toml b/common/malloc_utils/Cargo.toml index 1052128852a..99d5ba514b9 100644 --- a/common/malloc_utils/Cargo.toml +++ b/common/malloc_utils/Cargo.toml @@ -23,10 +23,15 @@ jemalloc-profiling = ["tikv-jemallocator/profiling"] sysmalloc = [] # Enable jemalloc with unprefixed malloc (recommended for reproducible builds) jemalloc-unprefixed = ["jemalloc", "tikv-jemallocator/unprefixed_malloc_on_supported_platforms"] +# Use mimalloc as the global allocator (alternative to jemalloc). +mimalloc = ["dep:mimalloc"] +# Use tcmalloc (gperftools) as the global allocator (alternative to jemalloc). +tcmalloc = ["dep:tcmalloc"] [dependencies] libc = "0.2.79" metrics = { workspace = true } +mimalloc = { version = "0.1", optional = true, default-features = false } parking_lot = { workspace = true } tikv-jemalloc-ctl = { version = "0.6.0", optional = true, features = ["stats"] } @@ -39,3 +44,7 @@ tikv-jemallocator = { version = "0.6.0", optional = true, features = [ "stats", "background_threads", ] } + +# tcmalloc links to gperftools which requires a UNIX-like system. +[target.'cfg(unix)'.dependencies] +tcmalloc = { version = "0.3", optional = true } diff --git a/common/malloc_utils/src/lib.rs b/common/malloc_utils/src/lib.rs index 9a5ea3c2bae..bb8d0d66337 100644 --- a/common/malloc_utils/src/lib.rs +++ b/common/malloc_utils/src/lib.rs @@ -5,11 +5,16 @@ //! This crate can be compiled with different feature flags to support different allocators: //! //! - Jemalloc, via the `jemalloc` feature. +//! - mimalloc, via the `mimalloc` feature. +//! - tcmalloc, via the `tcmalloc` feature. //! - GNU malloc, if no features are set and the system supports it. //! - The system allocator, if no features are set and the allocator is not GNU malloc. //! -//! It is assumed that if Jemalloc is not in use, and the following two statements are correct then -//! we should expect to configure `glibc`: +//! When multiple allocator features are enabled (e.g. because `jemalloc` is hardcoded in the +//! binary dependencies), the priority order is: mimalloc > tcmalloc > jemalloc > glibc > system. +//! +//! It is assumed that if no allocator feature is in use, and the following two statements are +//! correct then we should expect to configure `glibc`: //! //! - `target_os = linux` //! - `target_env != musl` @@ -24,22 +29,55 @@ //! detecting `glibc` are best-effort. If this crate throws errors about undefined external //! functions, then try to compile with the `not_glibc_interface` module. +// Ensure mimalloc and tcmalloc are not both enabled. +// Note: jemalloc + mimalloc/tcmalloc is allowed because jemalloc is hardcoded in the lighthouse +// and lcli binary dependencies. mimalloc/tcmalloc override jemalloc via cfg guards, matching the +// existing sysmalloc override pattern. +#[cfg(all(feature = "mimalloc", feature = "tcmalloc"))] +compile_error!("Cannot enable both `mimalloc` and `tcmalloc` allocator features"); + +// mimalloc and tcmalloc modules are only compiled on unix. Fail loudly rather than +// silently falling back to the system allocator. +#[cfg(all(not(unix), feature = "mimalloc"))] +compile_error!("`mimalloc` feature is only supported on unix targets"); + +#[cfg(all(not(unix), feature = "tcmalloc"))] +compile_error!("`tcmalloc` feature is only supported on unix targets"); + #[cfg(all( - any(feature = "sysmalloc", not(feature = "jemalloc")), + any( + feature = "sysmalloc", + not(any(feature = "jemalloc", feature = "mimalloc", feature = "tcmalloc")) + ), target_os = "linux", not(target_env = "musl") ))] pub mod glibc; -#[cfg(all(unix, not(feature = "sysmalloc"), feature = "jemalloc"))] +#[cfg(all( + unix, + not(feature = "sysmalloc"), + not(feature = "mimalloc"), + not(feature = "tcmalloc"), + feature = "jemalloc" +))] pub mod jemalloc; +#[cfg(all(unix, not(feature = "sysmalloc"), feature = "mimalloc"))] +pub mod mimalloc_alloc; + +#[cfg(all(unix, not(feature = "sysmalloc"), feature = "tcmalloc"))] +pub mod tcmalloc_alloc; + pub use interface::*; -// Glibc malloc is the default on non-musl Linux if the sysmalloc feature is enabled, or jemalloc -// is disabled. +// Glibc malloc is the default on non-musl Linux when no allocator feature is set, or when +// sysmalloc is explicitly requested. #[cfg(all( - any(feature = "sysmalloc", not(feature = "jemalloc")), + any( + feature = "sysmalloc", + not(any(feature = "jemalloc", feature = "mimalloc", feature = "tcmalloc")) + ), target_os = "linux", not(target_env = "musl") ))] @@ -52,8 +90,15 @@ mod interface { } } -// Jemalloc is the default on UNIX (including musl) unless the sysmalloc feature is enabled. -#[cfg(all(unix, not(feature = "sysmalloc"), feature = "jemalloc"))] +// Jemalloc is the default on UNIX (including musl) unless overridden by sysmalloc, mimalloc, or +// tcmalloc. +#[cfg(all( + unix, + not(feature = "sysmalloc"), + not(feature = "mimalloc"), + not(feature = "tcmalloc"), + feature = "jemalloc" +))] mod interface { #[allow(dead_code)] pub fn configure_memory_allocator() -> Result<(), String> { @@ -70,10 +115,44 @@ mod interface { } } +// mimalloc allocator. +#[cfg(all(unix, not(feature = "sysmalloc"), feature = "mimalloc"))] +mod interface { + #[allow(dead_code)] + pub fn configure_memory_allocator() -> Result<(), String> { + Ok(()) + } + + pub use crate::mimalloc_alloc::scrape_mimalloc_metrics as scrape_allocator_metrics; + + pub fn allocator_name() -> String { + "mimalloc".to_string() + } +} + +// tcmalloc allocator (via gperftools). +#[cfg(all(unix, not(feature = "sysmalloc"), feature = "tcmalloc"))] +mod interface { + #[allow(dead_code)] + pub fn configure_memory_allocator() -> Result<(), String> { + Ok(()) + } + + pub use crate::tcmalloc_alloc::scrape_tcmalloc_metrics as scrape_allocator_metrics; + + pub fn allocator_name() -> String { + "tcmalloc".to_string() + } +} + +// System allocator fallback for platforms where no allocator feature applies. #[cfg(any( not(unix), all( - any(feature = "sysmalloc", not(feature = "jemalloc")), + any( + feature = "sysmalloc", + not(any(feature = "jemalloc", feature = "mimalloc", feature = "tcmalloc")) + ), any(not(target_os = "linux"), target_env = "musl") ) ))] diff --git a/common/malloc_utils/src/mimalloc_alloc.rs b/common/malloc_utils/src/mimalloc_alloc.rs new file mode 100644 index 00000000000..066d623dfb9 --- /dev/null +++ b/common/malloc_utils/src/mimalloc_alloc.rs @@ -0,0 +1,8 @@ +//! Set the allocator to `mimalloc`. + +#[global_allocator] +static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; + +pub fn scrape_mimalloc_metrics() { + // Metrics for mimalloc can be added in a follow-up. +} diff --git a/common/malloc_utils/src/tcmalloc_alloc.rs b/common/malloc_utils/src/tcmalloc_alloc.rs new file mode 100644 index 00000000000..bbcc8a53e1a --- /dev/null +++ b/common/malloc_utils/src/tcmalloc_alloc.rs @@ -0,0 +1,8 @@ +//! Set the allocator to `tcmalloc` (via gperftools). + +#[global_allocator] +static ALLOC: tcmalloc::TCMalloc = tcmalloc::TCMalloc; + +pub fn scrape_tcmalloc_metrics() { + // Metrics for tcmalloc can be added in a follow-up. +} diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 43e361b60df..1d96e0d4477 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -11,6 +11,10 @@ normal = ["malloc_utils"] [features] portable = ["bls/supranational-portable"] fake_crypto = ['bls/fake_crypto'] +# Use mimalloc as the global allocator (alternative to jemalloc). +mimalloc = ["malloc_utils/mimalloc"] +# Use tcmalloc (gperftools) as the global allocator (alternative to jemalloc). +tcmalloc = ["malloc_utils/tcmalloc"] [dependencies] account_utils = { workspace = true } diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index 000c6fd0da4..e5548eac69f 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -37,6 +37,10 @@ beacon-node-redb = ["store/redb"] console-subscriber = ["console-subscriber/default"] # Force the use of the system memory allocator rather than jemalloc. sysmalloc = ["malloc_utils/sysmalloc"] +# Use mimalloc as the global allocator (alternative to jemalloc). +mimalloc = ["malloc_utils/mimalloc"] +# Use tcmalloc (gperftools) as the global allocator (alternative to jemalloc). +tcmalloc = ["malloc_utils/tcmalloc"] [dependencies] account_manager = { "path" = "../account_manager" }