Skip to content

Conversation

@supersonicbyte
Copy link

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
Copy link
Contributor

@hassila hassila left a comment

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 on lines 34 to 37
/// Number of total mallocs
case mallocCountTotal
/// Number of totatl free calls
case freeCountTotal
Copy link
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
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.

@@ -0,0 +1,88 @@
#ifndef INTERPOSER_H
Copy link
Contributor

Choose a reason for hiding this comment

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

In general for all new source files, add Apache license header:

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//

@@ -0,0 +1,24 @@
// swift-tools-version: 6.1
Copy link
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
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
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
Copy link
Contributor

hassila commented Sep 1, 2025

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
Copy link
Contributor

hassila commented Sep 1, 2025

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

@hassila
Copy link
Contributor

hassila commented Sep 1, 2025

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
Copy link
Contributor

hassila commented Sep 1, 2025

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

@hassila
Copy link
Contributor

hassila commented Sep 1, 2025

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
Copy link
Contributor

hassila commented Sep 1, 2025

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
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
Copy link
Contributor

hassila commented Sep 1, 2025

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
Copy link
Contributor

hassila commented Sep 1, 2025

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
Copy link

codecov bot commented Sep 2, 2025

Codecov Report

❌ Patch coverage is 64.13793% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.80%. Comparing base (4cad6ee) to head (35924ef).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
Sources/Benchmark/BenchmarkMetric.swift 50.72% 34 Missing ⚠️
Sources/Benchmark/BenchmarkMetric+Defaults.swift 52.94% 8 Missing ⚠️
.../Benchmark/Benchmark+ConvenienceInitializers.swift 0.00% 4 Missing ⚠️
Sources/Benchmark/BenchmarkExecutor.swift 93.94% 2 Missing ⚠️
Sources/Benchmark/BenchmarkResult.swift 33.33% 2 Missing ⚠️
Sources/Benchmark/MallocStats/MallocStats.swift 0.00% 1 Missing ⚠️
...stemStats/OperatingSystemStatsProducer+Linux.swift 75.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #333      +/-   ##
==========================================
- Coverage   69.41%   68.80%   -0.62%     
==========================================
  Files          33       33              
  Lines        4051     3147     -904     
==========================================
- Hits         2812     2165     -647     
+ Misses       1239      982     -257     
Files with missing lines Coverage Δ
Sources/Benchmark/Benchmark.swift 61.78% <100.00%> (-2.43%) ⬇️
Sources/Benchmark/BenchmarkClock.swift 32.00% <ø> (+1.86%) ⬆️
...urces/Benchmark/BenchmarkExecutor+Extensions.swift 75.00% <100.00%> (+1.39%) ⬆️
Sources/Benchmark/BenchmarkRunner.swift 59.35% <100.00%> (-1.39%) ⬇️
Sources/Benchmark/Blackhole.swift 33.33% <ø> (+8.33%) ⬆️
Sources/Benchmark/Progress/ProgressElements.swift 91.49% <100.00%> (-1.05%) ⬇️
Sources/Benchmark/Statistics.swift 63.00% <100.00%> (-3.67%) ⬇️
Tests/BenchmarkTests/BenchmarkMetricsTests.swift 98.77% <100.00%> (+1.01%) ⬆️
Tests/BenchmarkTests/BenchmarkRunnerTests.swift 100.00% <100.00%> (+7.32%) ⬆️
...BenchmarkTests/OperatingSystemAndMallocTests.swift 97.75% <ø> (-0.80%) ⬇️
... and 7 more

... and 14 files with indirect coverage changes

Files with missing lines Coverage Δ
Sources/Benchmark/Benchmark.swift 61.78% <100.00%> (-2.43%) ⬇️
Sources/Benchmark/BenchmarkClock.swift 32.00% <ø> (+1.86%) ⬆️
...urces/Benchmark/BenchmarkExecutor+Extensions.swift 75.00% <100.00%> (+1.39%) ⬆️
Sources/Benchmark/BenchmarkRunner.swift 59.35% <100.00%> (-1.39%) ⬇️
Sources/Benchmark/Blackhole.swift 33.33% <ø> (+8.33%) ⬆️
Sources/Benchmark/Progress/ProgressElements.swift 91.49% <100.00%> (-1.05%) ⬇️
Sources/Benchmark/Statistics.swift 63.00% <100.00%> (-3.67%) ⬇️
Tests/BenchmarkTests/BenchmarkMetricsTests.swift 98.77% <100.00%> (+1.01%) ⬆️
Tests/BenchmarkTests/BenchmarkRunnerTests.swift 100.00% <100.00%> (+7.32%) ⬆️
...BenchmarkTests/OperatingSystemAndMallocTests.swift 97.75% <ø> (-0.80%) ⬇️
... and 7 more

... and 14 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 5dcae6e...35924ef. Read the comment docs.

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

@supersonicbyte
Copy link
Author

Fixes we need before merge:
swiftlang/swift-package-manager#9091
swiftlang/swift-package-manager#9121

@supersonicbyte
Copy link
Author

The fixes got merged! We can now:

  • pull latest swift package manager
  • build from source swift build
  • then execute ~/path-to-swift-package-manager/.build/arm64-apple-macosx/debug/swift-package plugin benchmark

and it will work as expected.

I reached out to Owen from SPM team and asked him to help us to get the fixed cherry picked in a future release.

@supersonicbyte
Copy link
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

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

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

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.

3 participants