Skip to content

feat(major): [sc-23696] replace jemalloc dependency with custom malloc interposer#333

Open
supersonicbyte wants to merge 36 commits into
mainfrom
feature/sc-23696/replace-jemalloc-dependency-with-custom
Open

feat(major): [sc-23696] replace jemalloc dependency with custom malloc interposer#333
supersonicbyte wants to merge 36 commits into
mainfrom
feature/sc-23696/replace-jemalloc-dependency-with-custom

Conversation

@supersonicbyte

Copy link
Copy Markdown
Contributor

Description

This PR replaces jemalloc with a custom malloc interposer. The interposer logic is mostly inspired by the great work done by the swift-nio team.

On macOS/Darwin we ulitise the interposing feature of dydl while on Linux we use LD_PRELOAD in order to interpose.

The interposition logic is written in C and located in the MallocInterposerC package.

For actual interaction with the interposition is done through a wrapper package MallocInterposerSwift, which exposes a simple interface to hook and unhook the interposition logic and also to actually count the statistics we need.

The interposition requirers the the interposer library to be linked dynamically, since that's crucial to the nature of how the interposition works (both on Linux and Darwin). The SwiftPM Command plugin BenchmarkCommandPlugin has a dependency on Benchmark which has a dependency on the interposer libs. This causes a problem for SwiftPM and intitial tiggers of swift package benchmark command will fail.
The issue is described in more detail here.

The current workaround is to trigger the swift package benchmark command once and let it fail. Then to copy the libMallocInterposerC-tool.dylib and libMallocInterposerSwift-tool.dylib and name them without the -tool suffix.

e.g.

cp .build/arm64-apple-macosx/debug/libMallocInterposerC-tool.dylib .build/arm64-apple-macosx/debug/libMallocInterposerC.dylib
cp .build/arm64-apple-macosx/debug/libMallocInterposerSwift-tool.dylib .build/arm64-apple-macosx/debug/libMallocInterposerSwift.dylib

The same applies for Linux but the build location will be different and the dynamic library extension is .so.

Since jemalloc is removed the mallocSmall and mallocLarge metrics are removed. New metrics mallocByteCount and freeTotalCount are introducted.

How Has This Been Tested?

When running the test the executable is being linked statically to the test target. I didn't discover a way yet to dynamically link the interposer in order for it to work inside tests.

Minimal checklist:

  • I have performed a self-review of my own code
  • I have added DocC code-level documentation for any public interfaces exported by the package
  • I have added unit and/or integration tests that prove my fix is effective or that my feature works

@supersonicbyte supersonicbyte requested a review from hassila August 28, 2025 12:55

@hassila hassila left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial feedback, will ping you offline about the initial bootstrap problem...

Comment thread Sources/Benchmark/BenchmarkMetric.swift Outdated
Comment on lines +34 to +37
/// Number of total mallocs
case mallocCountTotal
/// Number of totatl free calls
case freeCountTotal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep the small and large malloc count metrics, I would suggest that we put the threshold for what is large/small to match the page size (and document it as such, so e.g. on macOS/ARM it'd be 16KB, while on Linux x86 it'd be 4K). Removing these metrics would be unnecessarily source breaking.

Comment on lines 127 to 129

let parameterization = (0...5).map { 1 << $0 } // 1, 2, 4, ...
let parameterization = (0...5).map { 1 << $0 } // 1, 2, 4, ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please merge in .swift-format from main and apply swift-format, then we can remove a lot of these diffs with extra spaces.

Comment thread LocalPackages/MallocInterposerC/Sources/MallocInterposerC/include/interposer.h Outdated
@@ -0,0 +1,24 @@
// swift-tools-version: 6.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should move to "swift-tools-version: 5.10" everywhere, as we still support 5.10.


if mallocStatsRequested {
startMallocStats = MallocStatsProducer.makeMallocStats()
MallocInterposerSwift.hook()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want to hook in this closure, it can be called twice as mentioned in the above comment - it is also unnecessary overhead to hook/unhook for each benchmark iteration, better to do the hooking outside of the loop - see how it's done for ARCStatsProducer which also hooks/unhooks, but does it outside the loop.


if mallocStatsRequested {
stopMallocStats = MallocStatsProducer.makeMallocStats()
MallocInterposerSwift.unhook()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, you want to unhook outside of the loop...

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

Also I see a difference in which metrics are output between main and this branch, e.g. :

Main:

Parameterized (count: 4)
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Instructions *                      │      2996 │      2997 │      2997 │      2997 │      5579 │      5579 │     39480 │     59765 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │     59765 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        11 │        11 │        11 │        11 │        11 │        11 │        11 │     59765 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Throughput (# / s) (K)              │      2667 │      2185 │      2185 │      2179 │      1601 │      1500 │        46 │     59765 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ns) *             │      1541 │      1625 │      1667 │      1708 │      1833 │      2042 │     18333 │     59765 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ns) *            │       375 │       458 │       458 │       459 │       625 │       667 │     21708 │     59765 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

this PR:

Parameterized (count: 4)
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ Free (total)                        │         0 │         0 │         0 │         0 │         0 │         0 │         0 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Instructions *                      │      2998 │      2999 │      2999 │      2999 │      5583 │      5583 │     37135 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total)                │         0 │         0 │         0 │         0 │         0 │         0 │         0 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        12 │        13 │        13 │        13 │        13 │        13 │        13 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Throughput (# / s) (K)              │      2667 │      2398 │      2185 │      2179 │      1716 │      1601 │        41 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (total CPU) (ns) *             │      1375 │      1500 │      1542 │      1583 │      1667 │      1833 │     25750 │    353057 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Time (wall clock) (ns) *            │       375 │       417 │       458 │       459 │       583 │       625 │     24625 │    353057 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

Got some additional malloc related metrics there, would be good to understand root cause.

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

(that was from running swift package benchmark in the Benchmarks subdirectory)

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

Also one issue when testing with package-benchmarks-samples repo with lost capture of allocation:

Main gives:

=============
Miscellaneous
=============

Memory leak 1 allocation of 1K
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (large) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (small) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (allocated resident) (K)     │      9552 │      9552 │      9552 │      9552 │      9552 │      9552 │      9552 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (K)          │      9765 │      9765 │      9765 │      9765 │      9765 │      9765 │      9765 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       420 │       420 │       420 │       420 │       420 │       420 │       420 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

PR:

Memory leak 1 allocation of 1K
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total)                │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        11 │        11 │        11 │        11 │        11 │        11 │        11 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

(Memory (allocated resident) (K) was missing from the PR output too?)

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

This test also misses capture:
Main:

Memory leak 123 allocations of 4K - performAllocationsMutablePointer
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (large) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (small) *                    │       123 │       123 │       123 │       123 │       123 │       123 │       123 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │       123 │       123 │       123 │       123 │       123 │       123 │       123 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ (K) *               │      3817 │      3817 │      3817 │      3817 │      3817 │      3817 │      3817 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (allocated resident) (M)     │        18 │        18 │        18 │        18 │        18 │        18 │        18 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (K)          │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       420 │       420 │       420 │       420 │       420 │       420 │       420 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

PR:

Memory leak 123 allocations of 4K - performAllocationsMutablePointer
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total)                │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        11 │        11 │        11 │        11 │        11 │        11 │        11 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

And this test should show a leak, but does not on the PR:
Main:

Memory transient allocations + 1 large leak
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (large) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (small) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ (K) *               │        34 │        34 │        34 │        34 │        34 │        34 │        34 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (allocated resident) (M)     │        77 │        77 │        77 │        77 │        77 │        77 │        77 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (K)          │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

PR:

Memory transient allocations + 1 large leak
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total) (G)            │        12 │        12 │        12 │        12 │        12 │        12 │        12 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        10 │        10 │        10 │        10 │        10 │        10 │        10 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

@supersonicbyte

Copy link
Copy Markdown
Contributor Author

And this test should show a leak, but does not on the PR: Main:

Memory transient allocations + 1 large leak
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (large) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (small) *                    │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ (K) *               │        34 │        34 │        34 │        34 │        34 │        34 │        34 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (allocated resident) (M)     │        77 │        77 │        77 │        77 │        77 │        77 │        77 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (K)          │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │      9847 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

PR:

Memory transient allocations + 1 large leak
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total) (G)            │        12 │        12 │        12 │        12 │        12 │        12 │        12 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         1 │         1 │         1 │         1 │         1 │         1 │         1 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        10 │        10 │        10 │        10 │        10 │        10 │        10 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (G)           │       421 │       421 │       421 │       421 │       421 │       421 │       421 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

Thanks for the feedback! That's strange will look into it..

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

This one failed to find the Malloc/free delta leak on Linux:

Memory transient allocations (1K small, 1001 large, 1M leak)
╒═════════════════════════════════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╤═══════════╕
│ Metric                              │        p0 │       p25 │       p50 │       p75 │       p90 │       p99 │      p100 │   Samples │
╞═════════════════════════════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ (Alloc + Retain) - Release Δ *      │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (bytes total) (M)            │        67 │        67 │        67 │        67 │        67 │        67 │        67 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc (total) *                    │         2 │         2 │         2 │         2 │         2 │         2 │         2 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Malloc / free Δ *                   │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (resident peak) (M)          │        24 │        24 │        24 │        24 │        24 │        24 │        24 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory (virtual peak) (M)           │       254 │       254 │       254 │       254 │       254 │       254 │       254 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Memory Δ (resident peak)            │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Object allocs *                     │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Releases *                          │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
├─────────────────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┼───────────┤
│ Retains *                           │         0 │         0 │         0 │         0 │         0 │         0 │         0 │         1 │
╘═════════════════════════════════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╧═══════════╛

@hassila

hassila commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

For Linux:

cp .build/x86_64-unknown-linux-gnu/debug/libMallocInterposerC-tool.so .build/x86_64-unknown-linux-gnu/debug/libMallocInterposerC.so
cp .build/x86_64-unknown-linux-gnu/debug/libMallocInterposerSwift-tool.so .build/x86_64-unknown-linux-gnu/debug/libMallocInterposerSwift.so

(need to rebuild in between too, so build, first copy, build, second copy, ...)

@codecov

codecov Bot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.25397% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.76%. Comparing base (873f6fd) to head (2a3efb3).

Files with missing lines Patch % Lines
Sources/Benchmark/BenchmarkMetric.swift 53.62% 32 Missing ⚠️
Sources/Benchmark/BenchmarkMetric+Defaults.swift 52.94% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #333      +/-   ##
==========================================
- Coverage   68.85%   68.76%   -0.08%     
==========================================
  Files          33       33              
  Lines        3152     3147       -5     
==========================================
- Hits         2170     2164       -6     
- Misses        982      983       +1     
Files with missing lines Coverage Δ
...urces/Benchmark/BenchmarkExecutor+Extensions.swift 77.14% <100.00%> (+6.83%) ⬆️
Sources/Benchmark/BenchmarkExecutor.swift 87.67% <100.00%> (+0.34%) ⬆️
Sources/Benchmark/BenchmarkRunner.swift 59.62% <100.00%> (+0.26%) ⬆️
Tests/BenchmarkTests/BenchmarkMetricsTests.swift 98.77% <100.00%> (ø)
...BenchmarkTests/OperatingSystemAndMallocTests.swift 96.77% <ø> (-0.31%) ⬇️
Sources/Benchmark/BenchmarkMetric+Defaults.swift 40.48% <52.94%> (+1.94%) ⬆️
Sources/Benchmark/BenchmarkMetric.swift 60.85% <53.62%> (-0.94%) ⬇️

... and 2 files with indirect coverage changes

Files with missing lines Coverage Δ
...urces/Benchmark/BenchmarkExecutor+Extensions.swift 77.14% <100.00%> (+6.83%) ⬆️
Sources/Benchmark/BenchmarkExecutor.swift 87.67% <100.00%> (+0.34%) ⬆️
Sources/Benchmark/BenchmarkRunner.swift 59.62% <100.00%> (+0.26%) ⬆️
Tests/BenchmarkTests/BenchmarkMetricsTests.swift 98.77% <100.00%> (ø)
...BenchmarkTests/OperatingSystemAndMallocTests.swift 96.77% <ø> (-0.31%) ⬇️
Sources/Benchmark/BenchmarkMetric+Defaults.swift 40.48% <52.94%> (+1.94%) ⬆️
Sources/Benchmark/BenchmarkMetric.swift 60.85% <53.62%> (-0.94%) ⬇️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 873f6fd...2a3efb3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread Benchmarks/Benchmarks/Histogram/Histogram.swift
@supersonicbyte

Copy link
Copy Markdown
Contributor Author

I will take the time to cherry pick these into a future release. The cherry pick could be a bit tricky because we depend on a lot of changes which are not cherry picked.

Comment on lines +239 to +245
void *replacement_reallocf(void *ptr, size_t size) {
void *new_ptr = replacement_realloc(ptr, size);
if (!new_ptr) {
replacement_free(new_ptr);
}
return new_ptr;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical bug in replacement_reallocf: When realloc fails, it should free the original pointer (ptr), not new_ptr (which is NULL). The function reallocf is designed to free the original memory on failure to prevent memory leaks.

void *replacement_reallocf(void *ptr, size_t size) {
    void *new_ptr = replacement_realloc(ptr, size);
    if (!new_ptr) {
        replacement_free(ptr);  // Should free ptr, not new_ptr
    }
    return new_ptr;
}
Suggested change
void *replacement_reallocf(void *ptr, size_t size) {
void *new_ptr = replacement_realloc(ptr, size);
if (!new_ptr) {
replacement_free(new_ptr);
}
return new_ptr;
}
void *replacement_reallocf(void *ptr, size_t size) {
void *new_ptr = replacement_realloc(ptr, size);
if (!new_ptr) {
replacement_free(ptr);
}
return new_ptr;
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +20 to +22
linkerSettings: [
.linkedLibrary("dl")
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dl library is Linux-specific and doesn't exist on macOS. This will cause build failures on macOS. The linker setting should be conditional:

linkerSettings: [
    .linkedLibrary("dl", .when(platforms: [.linux]))
]
Suggested change
linkerSettings: [
.linkedLibrary("dl")
])
linkerSettings: [
.linkedLibrary("dl", .when(platforms: [.linux]))
])

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@supersonicbyte

Copy link
Copy Markdown
Contributor Author

Great news is that all the dependencies we need are merged in for the next release of swift package manager (6.3.0) which should hopefully come out in March!

We can test it by:

  • pull latest swift package manager and checkout to release/6.3.0 branch
  • build from source with swift build
  • then execute ~/path-to-swift-package-manager/.build/arm64-apple-macosx/debug/swift-package plugin benchmark

@supersonicbyte

Copy link
Copy Markdown
Contributor Author

Currently blocked by this: swiftlang/swift#87696 (comment)

Comment on lines +272 to +276
void *replacement_calloc(size_t count, size_t size) {
void *ptr = replacement_malloc(count * size);
memset(ptr, 0, count * size);
return ptr;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical bug: NULL pointer dereference. If replacement_malloc fails and returns NULL, memset will be called on a NULL pointer causing a segmentation fault.

void *replacement_calloc(size_t count, size_t size) {
    void *ptr = replacement_malloc(count * size);
    if (ptr) {  // Fix: check for NULL before using
        memset(ptr, 0, count * size);
    }
    return ptr;
}
Suggested change
void *replacement_calloc(size_t count, size_t size) {
void *ptr = replacement_malloc(count * size);
memset(ptr, 0, count * size);
return ptr;
}
void *replacement_calloc(size_t count, size_t size) {
void *ptr = replacement_malloc(count * size);
if (ptr) {
memset(ptr, 0, count * size);
}
return ptr;
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@github-actions

github-actions Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants