I held off writing this post since Apple’s new Swift language was at version 1.0, and it was clearly rushed to market/probably should’ve stayed in beta for quite awhile longer, but now that 1.1 is out, I can say that things are not looking good from a performance point of view. The reason I was excited about the language from the moment I read the specification (other than wanting to see the end of Objective-C) is that the design takes into account the notion of having a clean high level syntax that provides enough information for the compiler to understand what is, and isn’t, trying to be accomplished in a given block of code, and optimise accordingly. A reading of the spec suggests that Swift is, among other things, a better Java: all the safety and convenience of bounds checking and automatic memory management, but with clever ways to reduce or eliminate the performance penalties that come with it.
There are a number of benchmarks you can find out there on the internet, and they generally don’t look very good, but a lot of the chatter about performance relates to rate limiting steps that rely on calling class libraries, for example sorting. This is not especially interesting, and it’s also a great way to tell an untruth without actually lying: if you make NSArray better at sorting, you can publish a benchmark that is completely and utterly bogus, and claim plausible deniability. I suspect that some of those graphs shown in the September 2014 presentation may be of this nature.
My first foray into Swift was to create the Beer Lab Notebook app from scratch, to get some experience. It worked out quite well, and I was moderately pleased with the result. The app itself does not do any grinding calculations, and the rate limiting steps are primarily to do with API calls, though in spite of this I did notice it to be a little bit more sluggish than what I have come to expect from similar types of functionality from my now fairly extensive experience with Objective-C apps.
Moving on, I proceeded to implement something a bit more cerebral: a chemical substructure matching algorithm that implements a highly typed subgraph search. The algorithm was ported directly from Java, and it was originally optimised for that platform, i.e. limiting to primitive scalar datatypes whenever possible, liberal use of the final keyword, avoiding resizing arrays, and generally minimising use of new to ensure that garbage collection does not become the rate limiting step. The analogous Swift implementation should, in principle, benefit from all of the same optimisations, with some of the must-haves in Java being nice-to-haves in Swift (i.e. same or better). The algorithm itself involves a lot of looping over arrays of integers, looking up arrays by index, a bit of string handling, and a tiny bit of floating point arithmetic, but for the most part it’s a lot of condition evaluations and branches. The kind of thing that can be mapped very directly to assembly language, and so a good low level compiler can make it blaze, while a high level scripting language can be expected to introduce overhead to the tune of orders of magnitude of slothfulness.
I wonder if you can guess which of these two extremes the outcome from the Swift version resembles?
Depressingly it is the latter. The test case for the Java implementation goes by in an eyeblink. On a fast Intel CPU using the simulator, the full debug test case takes about 300 seconds to complete the Swift implementation, while switching on all optimisations (and disabling safety constraints, if I’m not mistaken) reduces it to 95 seconds. At this point there’s no point in giving real numbers for the Java version (less than a second), because the slowdown is more than 100x, and once we start making comparisons on a log scale, there’s not much point in quibbling about the exact timing. Especially for code that is intended to be deployed on a mobile device.
When I originally measured these benchmarks, I was not too worried, because it’s fairly standard that when coding up performance sensitive routines in a new language/framework, there is generally a few false assumptions about performance profiles (e.g. maybe you thought array resizing or measuring string length was O(1) but they turn out to be O(N): these are two of the classics, but there are many more). But after some heavy digging, timing, profiling, etc. it turns out that there isn’t a specific performance problem: everything is slow.
By all means take this article with a grain of salt: I’m not disclosing my algorithm (it’s proprietary), but I’ve been doing this for awhile, and I know what symptoms to look for. I really want Swift, or something more modern than Objective-C, to succeed, because every year I’m becoming a bigger fan of what Apple is doing. But if you’re considering jumping into Swift, and you’re writing code that’s more performance sensitive than a trivial webapp, you might want to run some benchmarks first. Or wait a year. I’m pretty confident that the situation will get better, but I have no way of knowing how long it will take. I would imagine the compiler team is working pretty hard now that the spotlight is shining on them.