Context: While fuzzing my encrypted messenger Kalego, I noticed a single core pinned at 100% for over an hour while the other nine sat idle. No crash, no error. I sampled the thread with the macOS sample tool and found the culprit: a global runtime hook intercepting every single throw.
Root Cause: The Swift runtime exposes a writable pointer _swift_willThrow. Apple's test frameworks install their own observers into this slot. On every throw, XCTest bridges the Swift error to an autoreleased NSError, captures the call stack, and accumulates it in memory until the end of the test method.
The Numbers (Measured on M5, Swift 6.3.3, Release):
Plain Executable: 20.5 ns / iteration Swift Testing: 295 ns / iteration (14x slower) XCTest: 749 ns / iteration (36x slower) XCTest under xcodebuild/CI: ~1100 ns (54x slower) The Real Defect (Memory Leak): It's not just slow. Under XCTest, each observed throw leaks ~2.1 KB of memory. 2,000,000 throws in a single test method cost +4,031 MB. At fuzzing scale, this turns a slow test into an instant Out-Of-Memory crash.
The Fixes:
Move the hot loop to a standalone executable (best for fuzzing). Use return nil instead of throw on hot paths. Wrap the loop body in autoreleasepool { } to stop the memory leak. (Advanced/Situational) Safely disable the observer using dlsym and defer (code provided in the full report). Full Report & Reproducible Code: I wrote a complete 12-page report with 21 experiments, the assembly disassembly, and all the reproduction code. You can check the numbers on your own machine here: https://github.com/MagicYassin/xctest-throw-cost
Appreciate any technical feedback👨🏻💻☁️☁!