Compare commits

...

1 Commits

Author SHA1 Message Date
grajanna
9e221267af fix: Default to jemalloc and skip glibc trimming
Enable jemalloc by default in Conan and CMake and propagate it to Conan
consumers. Compile out glibc malloc trimming and instrumentation when
jemalloc is enabled.

Keep GNU malloc with trimming available as an opt-out and retain the
system allocator for sanitizer configurations. Update allocator tests
and document both build choices.
2026-09-24 21:59:17 +00:00
7 changed files with 63 additions and 25 deletions

View File

@@ -304,6 +304,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| ---------------- | ------------- | ----------------------------------------------------------------------------- |
| `assert` | OFF | Force enabling assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `jemalloc` | ON | Use jemalloc instead of the system allocator; disables glibc malloc trimming. |
| `rust` | OFF | Build the Rust crates and the C++ code that depends on them. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
@@ -317,6 +318,26 @@ memory) since they concatenate sources into fewer translation units. Non-unity
builds may be faster for incremental builds, and can be helpful for detecting
`#include` omissions.
### Memory allocator
Conan and CMake use jemalloc by default. In jemalloc builds, `mallocTrim` does
not call glibc's `malloc_trim` or collect trim metrics; jemalloc manages its own
arenas and memory reclamation.
To use the system allocator instead (GNU/glibc malloc with the existing
`malloc_trim(0)` behavior on Linux/glibc), add `--options '&:jemalloc=False'`
to each `conan install` command and `-Djemalloc=OFF` to the CMake configure
command. Keep the Conan and CMake options consistent. To switch an existing
system-allocator build to jemalloc, rerun Conan with
`--options '&:jemalloc=True'` and configure CMake with `-Djemalloc=ON`, since
existing build directories may cache the old option value.
The `sanitizers` Conan profile disables jemalloc when sanitizer instrumentation
is enabled so that sanitizers can intercept the system allocator. CMake also
defaults to the system allocator when `SANITIZERS` is set; use `-Djemalloc=OFF`
if reusing a build directory previously configured with jemalloc. Platforms
other than Linux/glibc do not support malloc trimming, regardless of this option.
### Rust crates
The Rust crates in `crates/` are only part of the build when `rust` is ON. With

View File

@@ -131,7 +131,17 @@ else()
set(use_lld OFF CACHE BOOL "try lld linker, clang only" FORCE)
endif()
option(jemalloc "Enables jemalloc for heap profiling" OFF)
# Sanitizers need to intercept the system allocator.
if(SANITIZERS_ENABLED)
set(JEMALLOC_DEFAULT OFF)
else()
set(JEMALLOC_DEFAULT ON)
endif()
option(
jemalloc
"Use jemalloc instead of the system allocator"
${JEMALLOC_DEFAULT}
)
option(werr "treat warnings as errors" OFF)
option(
local_protobuf

View File

@@ -98,6 +98,8 @@ tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags"
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{ sanitizers }}", "SANITIZERS_COMPILER_FLAGS": "{{ sanitizer_compiler_flags | join(' ') }}", "SANITIZERS_LINKER_FLAGS": "{{ sanitizer_linker_flags | join(' ') }}"}
[options]
# Let sanitizers intercept the system allocator rather than jemalloc.
&:jemalloc=False
{% if enable_asan %}
# Build Boost.Context with ucontext backend (not fcontext) so that
# ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber)

View File

@@ -52,7 +52,7 @@ class Xrpl(ConanFile):
"benchmark": True,
"coverage": False,
"fPIC": True,
"jemalloc": False,
"jemalloc": True,
"rocksdb": True,
"shared": False,
"static": True,
@@ -228,5 +228,7 @@ class Xrpl(ConanFile):
"xxhash::xxhash",
"zlib::zlib",
]
if self.options.jemalloc:
libxrpl.requires.append("jemalloc::jemalloc")
if self.options.rocksdb:
libxrpl.requires.append("rocksdb::librocksdb")

View File

@@ -13,11 +13,11 @@ namespace xrpl {
// -----------------------------------------------------------------------------
// Allocator interaction note:
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
// ptmalloc return free heap pages to the OS.
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
// on the *active* heap. The call is harmless but may not reclaim memory
// because those allocators manage their own arenas.
// ptmalloc return free heap pages to the OS, unless built with jemalloc.
// - Builds with jemalloc disable trimming and its instrumentation entirely.
// - Other linked or preloaded allocators (LD_PRELOAD) are not detected. Calling
// glibc's malloc_trim typically has no effect on their heaps because those
// allocators manage their own arenas.
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
// allocations are usually returned to the OS on free regardless of trimming.
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
@@ -47,18 +47,16 @@ struct MallocTrimReport
* @brief Attempt to return freed memory to the operating system.
*
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
* a different allocator is in use, this function is a no-op and the report will
* indicate that trimming is unsupported or had no effect.
* free space from ptmalloc arenas back to the kernel. On other platforms, or
* when built with jemalloc, this function performs no trimming or instrumentation
* and returns a default report with supported=false.
*
* @param tag Identifier for logging/debugging purposes.
* @param journal Journal for diagnostic logging.
* @return Report containing before/after metrics and the trim result.
*
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
* calling glibc's malloc_trim may have no effect on the active heap. The
* call is harmless but typically does not reclaim memory under those
* allocators.
* @note Other linked or preloaded allocators are not detected at runtime.
* Calling glibc's malloc_trim may have no effect on their heaps.
*
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
* Large allocations satisfied via mmap are usually returned on free

View File

@@ -7,7 +7,8 @@
#include <string_view>
#if defined(__GLIBC__) && BOOST_OS_LINUX
// jemalloc manages its own arenas; glibc trimming and metrics do not apply.
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
#include <sys/resource.h>
#include <malloc.h>
@@ -43,7 +44,7 @@ namespace detail {
// cSpell:ignore statm
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
inline int
mallocTrimWithPad(std::size_t padBytes)
@@ -69,7 +70,7 @@ parseStatmRSSkB(std::string const& statm)
return (resident * pageSize) / 1024;
}
#endif // __GLIBC__ && BOOST_OS_LINUX
#endif // __GLIBC__ && BOOST_OS_LINUX && !PROFILE_JEMALLOC
} // namespace detail
@@ -80,8 +81,9 @@ mallocTrim(std::string_view tag, beast::Journal journal)
MallocTrimReport report;
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")";
#if !(defined(__GLIBC__) && BOOST_OS_LINUX) || defined(PROFILE_JEMALLOC)
JLOG(journal.debug()) << "malloc_trim not supported by this build's platform or allocator (tag="
<< tag << ")";
#else
// Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB
// showed no clear, consistent benefit from custom padding—0 provided the best overall balance

View File

@@ -12,7 +12,7 @@ using namespace xrpl;
// cSpell:ignore statm
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
namespace xrpl::detail {
long
parseStatmRSSkB(std::string const& statm);
@@ -48,7 +48,7 @@ TEST(MallocTrimReport, structure)
EXPECT_EQ(report.deltaKB(), 0);
}
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
TEST(ParseStatmRSSkB, standard_format)
{
using xrpl::detail::parseStatmRSSkB;
@@ -127,7 +127,7 @@ TEST(MallocTrim, without_debug_logging)
MallocTrimReport const report = mallocTrim("without_debug", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
@@ -149,7 +149,7 @@ TEST(MallocTrim, empty_tag)
beast::Journal const journal{beast::Journal::getNullSink()};
MallocTrimReport const report = mallocTrim("", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else
@@ -179,7 +179,7 @@ TEST(MallocTrim, with_debug_logging)
MallocTrimReport const report = mallocTrim("debug_test", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
EXPECT_GE(report.durationUs.count(), 0);
@@ -188,9 +188,12 @@ TEST(MallocTrim, with_debug_logging)
#else
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.rssBeforeKB, -1);
EXPECT_EQ(report.rssAfterKB, -1);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
EXPECT_EQ(report.minfltDelta, -1);
EXPECT_EQ(report.majfltDelta, -1);
EXPECT_EQ(report.deltaKB(), 0);
#endif
}
@@ -203,7 +206,7 @@ TEST(MallocTrim, repeated_calls)
{
MallocTrimReport const report = mallocTrim("iteration_" + std::to_string(i), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
#if defined(__GLIBC__) && BOOST_OS_LINUX && !defined(PROFILE_JEMALLOC)
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else