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" }