as is well known, a regular untyped Swift Error calls into the Swift runtime when it is thrown, which adds a very small but nonzero overhead to returning by throws when compared to returning by return.
this behavior is potentially very useful for debugging, as the runtime call can be associated with a debugging hook, and enabled and disabled without recompiling applications. on the other hand, it can cause code that naturally throws a lot of errors internally (perhaps in the course of its expected control flow) to become dramatically slower if something is registered to that runtime hook, such as when code is running in a test suite.
anyway, i was never really sure how much those untyped throws cost when a runtime hook is not present, so i did an experiment tonight refactoring the swift-json library to use typed throws and it turns out, the answer is a lot.
the version of the JSON parser that uses typed throws is much faster than the original version, approaching a 2x speedup on aarch64 linux:
| architecture | subject | benchmark duration |
|---|---|---|
| x86_64 | Swift.symbols.json | 1.901695668 seconds |
| aarch64 | Swift.symbols.json | 1.746343872 seconds |
| architecture | subject | benchmark duration |
|---|---|---|
| x86_64 | Swift.symbols.json | 1.180054329 seconds |
| aarch64 | Swift.symbols.json | 0.932082752 seconds |
(i had used a swift symbolgraph-extract symbol dump as a convenient test subject)
this is notable because the swift-json parser was already heavily optimized, so i had long thought i had already maxed out its performance ceiling. but it turns out adopting typed throws unblocks considerable optimization opportunities.
i will say, except for one problem, it was really easy to port the JSON parser to typed throws. function coloring does all the heavy lifting, so it’s as mechanical as just copy-pasting throws(PatternMatchingError) everywhere throws used to be. it’s one of those wonderful instances where you invest very little effort in a refactor and the compiler just makes all your code faster for you.
the problem that i had was a known one - typed throws just do not get along with closures at all - so it’s very tedious to have to expand all the closures so that they don’t use anonymous parameters anymore.
but anyway i’m quite suprised how profitable it is to add typed throws to internal APIs, i shipped the newly-optimized swift-json in v3.0. you won’t see many typed throws in the library’s public API, as i’m still lukewarm on typed throws on public surfaces (it just feels like it defeats the purpose of any Error) but it’s really really great in interiors, and unlocks a lot of powerful declarative control flow patterns. i see a lot of people reaching for macros when what they probably really want is typed throws.