WIP: improve MallocUsage and test in CI #36549

pull sipa wants to merge 3 commits into bitcoin:master from sipa:202610_mallocusage changing 4 files +356 −12
  1. sipa commented at 4:27 AM on October 16, 2026: member

    This is a WIP to improve the MallocUsage function, with logic to measure actual usage in the unit test code.

  2. DrahtBot commented at 4:27 AM on October 16, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. sipa force-pushed on Oct 16, 2026
  4. DrahtBot added the label CI failed on Oct 16, 2026
  5. DrahtBot commented at 4:39 AM on October 16, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task macOS-cross to arm64: https://github.com/bitcoin/bitcoin/actions/runs/38850721587/job/116811104368</sub> <sub>LLM reason (✨ experimental): CI failed during compilation because RandomSizes in memusage_tests.cpp is unused, and Clang treats that warning as an error.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  6. sipa force-pushed on Oct 16, 2026
  7. sipa force-pushed on Oct 16, 2026
  8. memusage: model real malloc implementations in MallocUsage
    The previous formula was measured on glibc 2.19 and overestimates the
    size of glibc chunks by 16 bytes for half of all request sizes, ignores
    glibc's minimum chunk size, and has no notion of mmapped allocations.
    
    Model the allocators actually used by Linux, macOS and Windows builds:
    - glibc (ptmalloc2, all Linux release builds): chunks of
      max(MINSIZE, round_up(size + sizeof(size_t), MALLOC_ALIGNMENT)) bytes,
      with chunks of 128 KiB or more mmapped as whole pages. This matches
      glibc 2.43 exactly on x86_64, i386, aarch64, armhf, armel, riscv64,
      ppc64, ppc64le, ppc, s390x, loongarch64, alpha, hppa, m68k, sh4 and
      sparc64 (over 300000 request sizes each).
    - musl (mallocng, used by the Alpine CI job): a table of the steady-state
      cost per slot for each of its 48 size classes, including group headers
      and out-of-band metadata, which matches RSS measurements of musl 1.2.5
      within 0.2%.
    - macOS (libmalloc): no in-band headers, with requests rounded up to 16
      bytes up to 1008 bytes (tiny/nano), to 512 bytes up to 32 KiB (small),
      to 32 KiB up to 8 MiB (medium), and to whole pages beyond. This matches
      the CI measurements on macOS 15 (arm64) for every size the previous
      estimate got wrong (1-112, 1025-7200 and 32769-493424); the other sizes
      up to 4 MiB were within the test's tolerance. The 8 MiB limit of the
      medium range is libmalloc's default (from its source), not measured.
    - Windows x64 (the Windows heap, used by the UCRT's malloc): blocks of
      round_up(size + 8, 16) bytes, except that sizes up to 16 KiB come from
      its low-fragmentation heap, in buckets of 16 to 512 bytes granularity.
      This matches the CI measurements on Windows Server 2022 up to 512 KiB
      (beyond that, addresses no longer reflect usage). Blocks of over
      0xFF000 bytes get their own VirtualAlloc() region, and are counted as
      their committed pages (not measured).
    
    Other platforms (BSDs) keep the previous estimate until they are
    measured.
    
    Also count the hash that libc++ stores in every std::unordered_map and
    std::unordered_set node, and the second list pointer of MSVC's STL. The
    previous MallocUsage() overestimated small allocations enough to hide
    this, but with the accurate models, pool_tests/memusage_test fails with
    libc++ (it finds that the pool allocator uses more memory than
    std::allocator, as the std_map nodes are counted as 24 instead of 32
    bytes), and would with MSVC.
    
    That test also assumes that malloc has a per-allocation overhead, which
    the pool allocator avoids. macOS's allocator has none for small blocks,
    so there the pool really uses slightly more memory (for the list of its
    chunks). Only check that the pool saves memory where malloc's overhead
    for the nodes is significant.
    8b5b4927f4
  9. test: compare MallocUsage against measured allocator behaviour
    Add a unit test that measures how much memory malloc() actually consumes
    and fails if MallocUsage() is more than a factor 1+1/16 below, or more
    than a factor 1+1/8 above, the measured usage. The upper bound is looser
    because MallocUsage() may include out-of-band allocator metadata that
    the measurement does not see (up to 1/8 for musl's smallest size class).
    
    By default, a random set of sizes up to 4 MiB (512 KiB on Windows, see
    below) is tested: for every power of two 2^b, 16 >> (b / 4) (but at
    least one) sizes in [2^b, 2^(b+1)), so small sizes are tested more
    densely than large ones. Every mismatching size is reported on its own
    line, with its measured usage.
    
    When the ALL_SIZES constant is set to true, the test instead determines
    the usage of every size up to 4 MiB (searching for the boundaries
    between size classes, assuming usage does not decrease with size),
    reports mismatching size ranges, and prints the results in a form meant
    for writing MallocUsage() code, e.g. for glibc on x86_64:
    
      1-24: 32
      25-131048: round_up(size + 8, 16)
      131049-4194280: round_up(size + 24, 4096)
    
    This takes about 25 seconds with glibc, and 5 with musl. In this mode,
    glibc's mmap threshold is pinned to its default, as glibc otherwise
    raises it whenever an mmapped block is freed, which would make the
    results depend on the order of the measurements.
    
    The usage of a size is measured as the smallest distance between
    neighbouring blocks (in address order), among up to 16384 live
    allocations of that size, that accounts for at least 10% of all pairs.
    This is robust against a fragmented heap (the test binary's heap holds
    over a thousand regularly spaced free holes at startup, which dominate
    smaller samples) and against allocators that place blocks at varying
    offsets within their slots (musl).
    
    Large blocks are often given page-granular memory, with the block at an
    offset within it that varies from block to block (musl cycles it), which
    makes neighbouring blocks slightly closer than the memory they use. Such
    an allocator returns a different address, less than a page away, when a
    block is freed and allocated again. If that happens (for distances of a
    page or more), the test also looks at the longest run of neighbours
    whose distances are within a page of that distance. If all of them lie
    within less than a page of a grid whose spacing is a multiple of the
    page size, that spacing is the usage. Blocks that are really spaced by
    something else (e.g. glibc's 4080-byte chunks) drift away from any such
    grid, though not necessarily within a short run (such as within one
    Windows heap segment), which is why this is only done when the offsets
    are seen to vary. Without this, musl's output in the ALL_SIZES mode has
    two lines per page above its mmap threshold, one of them 16 bytes too
    low; with it, the output is:
    
      1-12: 16
      13-156: round_up(size + 4, 16)
      157-188: 192
      ... (one line per size class) ...
      104829-131036: 131072
      131037-131051: 131056
      131052-131052: 131072
      131053-4194284: round_up(size + 20, 4096)
    
    On Windows, only sizes up to 512 KiB are tested: larger blocks often get
    their own heap segment or VirtualAlloc() region, so the distances
    between them reflect reserved address space rather than usage.
    
    Sanitizer builds are skipped, as MallocUsage() does not model their
    allocators.
    04f59b28a0
  10. test: measure every allocation size in memusage_tests
    Enable ALL_SIZES, so that the malloc_usage test measures the usage of
    every allocation size up to 4 MiB (instead of a random sample) and prints
    the results in a form meant for writing MallocUsage() code. This takes
    about 25 seconds with glibc, and 5 with musl.
    60524a8d93
  11. sipa force-pushed on Oct 16, 2026
Contributors
Labels

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-16 12:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me