From 6f6b284647e127dcab1b1838a77c75d373306fe4 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Date: Wed, 30 Apr 2025 09:42:18 -0600 Subject: [PATCH 1/4] feat: add generalized "mutating" `blackHole` --- Sources/Benchmark/Blackhole.swift | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Sources/Benchmark/Blackhole.swift b/Sources/Benchmark/Blackhole.swift index bd6bae62..4124e84c 100644 --- a/Sources/Benchmark/Blackhole.swift +++ b/Sources/Benchmark/Blackhole.swift @@ -36,3 +36,49 @@ public func blackHole(_: some Any) {} public func identity(_ value: T) -> T { value } + +/// A more generalized variant of `blackHole` -- forces the compiler to assume that the argument is not only used, but also mutated. +/// Foils compiler optimizations like const-folding, loop-invariant code motion, and common-subexpression elimination. +/// For example, the `blackHole` does not always suffice for the following benchmark. +/// ```swift +/// Benchmark("Const-folded?", +/// configuration: .init( +/// metrics: [.wallClock, .mallocTotal], +/// scalingFactor: .mega +/// ) { benchmark in +/// let arguments = Arguments() // set up +/// benchmark.startMeasurement() +/// for _ in benchmark.scaledIterations { +/// blackHole(benchmarkee(arguments)) +/// } +/// ``` +/// If `benchmarkee` is a pure function, i.e. has no side-effects, the above code will get subjected to e.g. loop-invariant code motion. +/// ```swift +/// Benchmark("Const-folded?", +/// configuration: .init( +/// metrics: [.wallClock, .mallocTotal], +/// scalingFactor: .mega +/// ) { benchmark in +/// let arguments = Arguments() // set up +/// benchmark.startMeasurement() +/// let _result = benchmarkee(arguments) +/// for _ in benchmark.scaledIterations { +/// blackHole(_result) // no longer benchmarking `benchmarkee`! +/// } +/// ``` +/// The correct way to implement this benchmark would then be +/// ```swift +/// Benchmark("Const-folded?", +/// configuration: .init( +/// metrics: [.wallClock, .mallocTotal], +/// scalingFactor: .mega +/// ) { benchmark in +/// var arguments = Arguments() // set up +/// benchmark.startMeasurement() +/// for _ in benchmark.scaledIterations { +/// clobber(&arguments) +/// blackHole(benchmarkee(arguments)) +/// } +/// ``` +@_optimize(none) +public func clobber(_: UnsafeMutableRawPointer) {} From 89506ec113cda2c39abfafe869d2ebc288398be1 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Date: Thu, 15 May 2025 22:17:37 +0200 Subject: [PATCH 2/4] refactor: rename `clobber` -> `blackHoleMutating` https://github.com/ordo-one/package-benchmark/pull/323#issuecomment-2848464599 --- Sources/Benchmark/Blackhole.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Benchmark/Blackhole.swift b/Sources/Benchmark/Blackhole.swift index 4124e84c..b33cc3ec 100644 --- a/Sources/Benchmark/Blackhole.swift +++ b/Sources/Benchmark/Blackhole.swift @@ -76,9 +76,9 @@ public func identity(_ value: T) -> T { /// var arguments = Arguments() // set up /// benchmark.startMeasurement() /// for _ in benchmark.scaledIterations { -/// clobber(&arguments) +/// blackHoleMutating(&arguments) /// blackHole(benchmarkee(arguments)) /// } /// ``` @_optimize(none) -public func clobber(_: UnsafeMutableRawPointer) {} +public func blackHoleMutating(_: UnsafeMutableRawPointer) {} From c8278d0fa8044cf4161fa616f89b6c547f0bab1e Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Date: Thu, 15 May 2025 23:04:29 +0200 Subject: [PATCH 3/4] test: basic example benchmarks using `blackHoleMutating` --- .../Basic/BenchmarkRunner+Basic.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift b/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift index 65bc5b4c..f62e06e2 100644 --- a/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift +++ b/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift @@ -81,6 +81,27 @@ let benchmarks: @Sendable () -> Void = { benchmark.measurement(CustomMetrics.one, Int.random(in: 1 ... 1_000)) } + Benchmark("Amortized throughput benchmark using blackHoleMutating", + configuration: .init(metrics: .all + [CustomMetrics.two, CustomMetrics.one, CustomMetrics.three], + scalingFactor: .mega)) { benchmark in + var x = 3.1415926e104 // constant value + for _ in benchmark.scaledIterations { + blackHoleMutating(&x) // `x` assumed to be mutated, prevents loop-invariant code motion + blackHole(sqrt(x)) + } + } + + Benchmark("Amortized latency benchmark using blackHoleMutating", + configuration: .init(metrics: .all + [CustomMetrics.two, CustomMetrics.one, CustomMetrics.three], + scalingFactor: .mega)) { benchmark in + var x = 3.1415926e104 // constant value + blackHoleMutating(&x) + for _ in benchmark.scaledIterations { + x = sqrt(x) + } + blackHole(x) + } + Benchmark("All metrics", configuration: .init(metrics: .all, skip: true)) { _ in } From bd5f48ae6daed87f812256efc454204204be7639 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Date: Thu, 15 May 2025 23:16:12 +0200 Subject: [PATCH 4/4] chore: lint --- .../Benchmarks/Basic/BenchmarkRunner+Basic.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift b/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift index f62e06e2..b5a31653 100644 --- a/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift +++ b/Benchmarks/Benchmarks/Basic/BenchmarkRunner+Basic.swift @@ -84,20 +84,20 @@ let benchmarks: @Sendable () -> Void = { Benchmark("Amortized throughput benchmark using blackHoleMutating", configuration: .init(metrics: .all + [CustomMetrics.two, CustomMetrics.one, CustomMetrics.three], scalingFactor: .mega)) { benchmark in - var x = 3.1415926e104 // constant value + var arg = 3.1415926e104 // constant value for _ in benchmark.scaledIterations { - blackHoleMutating(&x) // `x` assumed to be mutated, prevents loop-invariant code motion - blackHole(sqrt(x)) + blackHoleMutating(&arg) // `x` assumed to be mutated, prevents loop-invariant code motion + blackHole(sqrt(arg)) } } Benchmark("Amortized latency benchmark using blackHoleMutating", configuration: .init(metrics: .all + [CustomMetrics.two, CustomMetrics.one, CustomMetrics.three], scalingFactor: .mega)) { benchmark in - var x = 3.1415926e104 // constant value + var arg = 3.1415926e104 // constant value blackHoleMutating(&x) for _ in benchmark.scaledIterations { - x = sqrt(x) + arg = sqrt(arg) } blackHole(x) }