rpmalloc

repository·develop·Indexed 25 days ago

https://github.com/mjansson/rpmalloc

A high-performance, public-domain, cross-platform, lock-free, thread-caching memory allocator implemented in C. It provides 16-byte aligned allocations and is designed to be faster than common allocators like tcmalloc or ptmalloc3. Features include support for huge pages, transparent huge pages (THP), first class heaps for scoped allocations, and the ability to override standard library malloc/free functions.

Tokens
3.1K
Snippets
2
Records
15
Agent score
82%

What's inside rpmalloc

  1. Enable Huge Pages and Transparent Huge Pages (THP)

    develop

    rpmalloc supports huge/large pages on Windows, Linux, and macOS. These modes are mutually exclusive, and enable_huge_pages takes precedence.

    Explicit Huge Pages

    To enable explicit huge pages, pass a non-zero value for enable_huge_pages in the configuration object when calling rpmalloc_initialize_config.

    • Requirement: Requires system configuration (e.g., a preallocated huge page pool on Linux or SeLockMemoryPrivilege on Windows).
    • Fallback: If requested but unavailable, rpmalloc_initialize_config returns a non-zero value and leaves the allocator uninitialized. You can then re-initialize without huge pages.

    Transparent Huge Pages (THP)

    On Linux and Android, you can use transparent huge pages without special system configuration.

    • How to enable: Pass a non-zero value for enable_thp in the configuration object during rpmalloc_initialize_config. This uses madvise(MADV_HUGEPAGE) to advise the kernel.
  2. Understand rpmalloc memory fragmentation and performance characteristics

    develop

    Fragmentation

    • Internal Fragmentation: rpmalloc avoids traditional "holes" because memory pages are split into perfectly aligned blocks for specific size classes. A block freed by rpfree is immediately available for the same size class.
    • Virtual Address Fragmentation: Requests for different size classes will return blocks that are at least one memory page apart in virtual address space. Only blocks of the same size may reside within the same page span.

    Performance Scenarios

    • Best Case: Threads that allocate and free memory within the same thread. This avoids cross-thread deallocation overhead.
    • Producer-Consumer: rpmalloc handles this well. When one thread frees memory allocated by another, the blocks are deferred to the owning thread via an atomic free list and reused there.
    • Worst Case (Memory Overhead): Allocating a few blocks across many different size classes will commit a page for each class. However, physical memory usage is minimized because pages are committed on demand.
    • Worst Case (Thrashing): If allocation patterns cause high/low water marks to fluctuate beyond the free page retention thresholds, the allocator may repeatedly commit and decommit pages. You can mitigate this by setting disable_decommit in the configuration.
  3. How rpmalloc manages memory mapping and custom providers

    develop

    By default, rpmalloc uses OS-specific APIs (VirtualAlloc on Windows, mmap on POSIX) to map virtual memory pages on demand.

    If you need to provide a custom memory mapping provider, you can use __rpmalloc_initialize__ or __rpmalloc_initialize_config__ to pass a memory interface containing function pointers for:

    • map: Reserves the requested number of bytes. The returned address MUST be aligned to either zero or the span size. It can use output parameters to store an alignment offset and the actual mapped size.
    • commit: Controls whether a range of the mapping is backed by physical memory.
    • decommit: Controls whether a range of the mapping is backed by physical memory.
    • unmap: Releases the entire mapped range (using the offset and size provided during the map call).

    Note: If you provide either a map or unmap function, you must provide both; otherwise, the default implementation will be used for the missing function.

    Additionally, you can specify a custom page size (which MUST be a power of two) during initialization via __rpmalloc_initialize_config__. Passing 0 lets rpmalloc determine the system page size automatically.

  4. Understand rpmalloc benchmark performance characteristics

    develop

    According to the allt benchmark suite results, rpmalloc typically performs as follows:

    • Throughput: rpmalloc sits in the leading throughput group, performing competitively with snmalloc and various mimalloc generations, and ahead of jemalloc and tcmalloc.
    • Memory Usage: rpmalloc may trade some peak memory for higher throughput due to its larger page geometry and free page retention.
    • Memory Footprint Control: If a smaller memory footprint is more important than peak throughput, you can manage memory retention. By default, disable_decommit is off, meaning unused pages are returned to the OS.
  5. Build rpmalloc as a static or dynamic library

    develop

    To build the library, use the provided Python configuration script which generates a Ninja build script. Then, use ninja to perform the build.

    # Example build flow
    python configure
    ninja

    The build produces both a static and a dynamic library named rpmalloc.

  6. Integrate rpmalloc into your project

    develop

    The simplest way to use rpmalloc is to add rpmalloc.h and rpmalloc.c directly to your project and compile them along with your source files. The allocator is self-contained and will initialize automatically on the first allocation request.

    If you want to provide a custom memory interface or specific configuration, you MUST call rpmalloc_initialize or rpmalloc_initialize_config before any other calls to the allocator.

  7. Reproduce rpmalloc benchmarks

    develop

    To regenerate the benchmark results and graphs, you must use the mimalloc-bench suite.

    1. Build the allocators and benchmarks within a mimalloc-bench checkout.
    2. Run the benchmark suite using the bench.sh script from the out/bench directory, passing the allocator keys (e.g., rp for rpmalloc, sys for glibc) and the benchmark set (e.g., allt).
    3. Use the provided Python plotting script to regenerate the graphs from the resulting CSV files.

    Note: The plotting script (plot.py) requires matplotlib. The script applies a 4x speed rule (omitting allocators more than 4x slower than rpmalloc) and drops allocators that fail more than two benchmarks for readability.

    ../../bench.sh sys rp mi mi2 mi3 je tc sn sn-sec hd sm tbb lt iso scudo \
        ff gd hm hml lf lp mesh nomesh mng sg fg yal rmalloc allt
    
    python3 benchmark/plot.py
  8. Regenerate benchmark graphs using plot.py

    develop

    To regenerate the benchmark graphs shown in the project documentation, use the plot.py script. This script processes the raw CSV data located in the results/ directory and produces PNG images in the images/ directory.

    Prerequisites

    • Python 3
    • matplotlib library

    Usage Run the script from the benchmark/ directory:

    python3 plot.py

    Note on Data Filtering The raw data in results/rptest-threads.csv and results/mimalloc-bench-allt.csv is unfiltered. The plot.py script applies the following filters for visual clarity in the graphs:

    • Allocators that are more than 4x slower than rpmalloc on a specific benchmark are omitted.
    • Allocators that failed many benchmarks are dropped.
    • In the allt suite, benchmarks where an allocator crashed (recorded as zero/blank elapsed time or zero CPU time) are treated as failures.
  9. Override standard library malloc/free functions

    develop

    To automatically replace the standard library's malloc, free, and related functions, define __ENABLE_OVERRIDE__ to a non-zero value (it defaults to 1).

    Important considerations for overrides:

    • Static Linking: If compiling as a static library, the linker might not include rpmalloc if you only call standard malloc/free functions. To force inclusion, include rpmalloc.h in at least one source file and call rpmalloc_linker_reference().
    • C++ Overrides:
      • Windows: You must #include <rpnew.h> in exactly one source file to override new/delete operators. Including it in multiple translation units will cause duplicate symbol errors.
      • Other Platforms: new/delete are overridden automatically; do not include rpnew.h on these platforms as it will cause conflicts.
    • Dynamic Injection: On Linux and macOS, you can use LD_PRELOAD or DYLD_INSERT_LIBRARIES to inject the dynamic library into a pre-existing binary.
  10. Configure rpmalloc via build definitions

    develop

    You can enable several features and safety checks by defining specific macros during compilation:

    MacroDefaultDescription
    __ENABLE_STATISTICS__0Enables detailed statistics (adds slight runtime overhead).
    __ENABLE_LEAK_DETECTION__0Enables detection of outstanding allocations at rpmalloc_finalize. Requires __ENABLE_STATISTICS to be 1.
    __ENABLE_ASSERTS__0Enables asserts. If __ENABLE_LEAK_DETECTION__ is also enabled, a leak triggers an assert.
    __ENABLE_VALIDATE_ARGS__0Enables integer safety checks on all calls to prevent overflows in size calculations.
    __ENABLE_OVERRIDE__1Includes malloc.c to provide overrides for the standard library malloc family.
    __RPMALLOC_FIRST_CLASS_HEAPS__0Enables support for first class heaps (imposes a slight performance hit in deallocation).
  11. Important caveats and error handling

    develop

    Error Handling

    The library does not try to guard against errors! All entry points assume passed values are valid. For example, passing an invalid pointer to a free function will likely result in a segmentation fault.

    Memory Alignment

    To guarantee fixed span alignment, the implementation oversizes memory mappings.

    • POSIX: The excess address range is immediately unmapped.
    • Windows: The entire mapping is retained because the OS does not support partial release. This increases virtual memory address space usage but does not increase physical memory usage since the extra pages are never touched.