Porting Graphing Calculator From C++ to Swift
C++ is and always has been an effective language for managing complexity in large projects, so why did I change languages? I was incredibly impressed with Apple’s Augmented Reality technology.
[…]
I’ve worked the last 18 months rewriting everything. Here’s what I’ve learned.
[…]
In the end, the port is vastly more maintainable, readable, and compact. When I ported individual sections of functionality, the Swift source typically measured 30% the size of the corresponding C++ code.
[…]
The biggest challenge of the port was achieving comparable speed. Decades of iterative refinement and low-level optimization on every release set a high bar for performance. Navigating Swift’s myriad Unsafe APIs in performance-critical code was difficult, but effective. The biggest remaining challenge is minimizing ARC retain/release overhead navigating expression trees. Relying on ARC eliminated a great deal of code complexity. The C++ code handled expression memory management manually, which was both extremely fragile but also very fast. The Swift version is smaller, easier to write correct code and reason about, but has performance-critical sections where I know that traversing a tree will not change any reference counts but have no way to communicate to the compiler that the ARC retain/release overhead is unnecessary.
I learned to write Swift in the same way I learn every language: writing a raytracer, and what Ron is saying mostly matches my experience with it. In general it produces code that is Fast Enough, but getting high performance is still harder than C++.
[…]
Unneeded retain/release from ARC in perf critical tree traversal consumes easily >10%, and in some cases >20% of run time in my code - just finding out about the unsafe work arounds was challenging, using it made the code much more unwieldy, and itself created new perf problems.
[…]
I’ve also encountered issues with the performance of generic code, where perf is also difficulty to debug, and much harder to reason about than C++. Part of this is to make it possible to maintain ABI compatibility with generic code (though I’m convinced it should be faster)
Previously:
- Roadmap for Improving Swift Performance Predictability
- On the Road to Swift 6
- Graphing Calculator Started As a Demo for PenMac
- Swift Ownership Manifesto
- The Graphing Calculator Story
Update (2022-07-05): Joe Groff:
One major limitation is the nonuniform representation of unspecialized generics in SIL, which means they lose a host of optimizations in addition to having the indirection overhead
Having done it all in native code, I'm also not convinced that was the right call vs. using a higher-level bytecode and interpreter to represent unspecialized generics more compactly
See also: Hacker News.
1 Comment RSS · Twitter
>In the end, the port is vastly more maintainable, readable, and compact. When I ported individual sections of functionality, the Swift source typically measured 30% the size of the corresponding C++ code.
To be fair, some of that has nothing to do with Swift but with this being a rewrite. You apply the lessons from your initial implementation, and you automatically end up with something "vastly more maintainable, readable, and compact", and probably less code, too.