Why Swift is swift
How internal compiler optimization makes Swift swifter
Hi, my name is Gwendolyn Weston, I'm a developer at a company called PlanGrid. Today, I'm here to talk to you about why Swift is swift, but more specifically, why is it swifter than Objective-C. I wish that I could say that while researching this talk, I found like a proverbial Buzzfeed knowledge sound bite, the one cool trick that makes Swift so fast. But that wasn't the case. In fact, there wasn't really a single answer as to why Swift is swift. It was more that the language made a bunch of design choices that the compiler can take advantage of to make these little optimizations that can eventually compound into overall code execution. So instead of going into all the little things, the details that add up, I decided to just deep dive into one of these topics, and that is dynamic dispatch.
...I decided to just deep dive into one of these topics, and that is dynamic dispatch.
01:04: So what is dynamic dispatch, first of all? Well, when you have a language that allows for multiple implementations of a method, for example, through inheritance, when that method is run, you need to know which implementation of that method to use. And when that lookup is done at compile time, it's called static dispatch. When it's done at runtime, it's called dynamic dispatch. So you might be thinking, "Why do the method look-up at runtime instead of compile time?" Right? That sounds like extra overhead while your code is running. You're running this thing, and then it needs to actually go somewhere else. It's just this indirection to be like, "What am I implementing? Which method of implementation am I using?" And the reason is, while there's this trade-off of losing speed, you gain greater code flexibility because then the compiler doesn't need to know what type and object is at compile time.
...when that lookup is done at compile time, it's called static dispatch. When it's done at runtime, it's called dynamic dispatch.
So to further elucidate why that is something really significant to have, I'm gonna go into this code sample. Here, we have three classes. The top one is a trendsetter with a method called makeCool, and then there's two sub-classes under that, Taylor Swift, and another class called Gwendolyn Weston. They both have their own implementation of makeCool separate from the parent and separate from each other. Then finally, at the bottom, we instantiate a trendsetter object, and then we check, is this trendsetter a pop star? If so, it's probably Taylor Swift. If not, then it's me.
02:48: Finally, at the bottom, we ask the trendsetter to make bow ties cool. So when running this code, which implementation of makeCool are we going to use, right? It's not clear at compile time. Maybe there's some network response that you have to wait for in order to know whether or not the trendsetter is a pop star. This is where dynamic dispatch comes into play. This is because we have to do the look-up at runtime. So first, in order to explain why or how Swift does this faster than Objective-C, let's go into how to do it in Objective-C. And what happens in Objective-C when we're doing dynamic dispatch is, you call this method objc_msgSend, which takes two parameters, the instance- that is calling the method, and the selector. What happens in objc_msgSend is it goes to the class of the instance, where the class has this metadata, and in that metadata is a hash table of function implementations, where the key is the selector string.
How does Swift speed this up?
So to look up the selector, see if there's a corresponding implementation, and if not, it'll just keep bubbling up by checking the parent class, and keep going and going until it either finds it or it sends you an error. It will be like, "unrecognised selector sent message." So this is how we do it in Objective-C. How does Swift speed this up? Well, it does something pretty similar. It also goes to the class instance. But instead of having this hash table in the class metadata of these Swift classes, we have something called a vtable. So what is a vtable? A vtable is an array of function pointers, really simply put. And that sounds a bit backwards, right? Why use an array over a hash table? How can look-up, in an array, be faster than a hash table? And that is because it's not just some arbitrarily constructed array; it's constructed in a very specific manner. To kind of explain that structure, I'm instead just going to go back to our code sample, where we have trendsetter, Taylor Swift, and me, and go over what the vtable of this is going to look like. So notice that there's also a new method in the trendsetter class. There's a method called makeUncool, but only I'm implemented. Taylor Swift does not; she cannot make anything uncool.
05:17: So what do the vtables of these three classes look like? So here we have three arrays, where each row is supposed to be an index of the array. Notice that at the value of each index is a function pointer, pointing to where the implementation of this method is at. If the sub-class doesn't override the parent, notice that it copies the address of the function pointer. So for example, Taylor Swift does not implement makeUncool; she just cannot. So that vtable copies the address from the original parent class, whereas in the case where the sub-classes do have their own implementation of the method in the value of the vtable, they point to a different address because it's a different implementation. So for example, Taylor Swift and I, we both have our own implementations of makeCool, so we both have different function pointers than the original trendsetter class.
Why does this make looking up an array faster than looking up something in a hash table? The reason is, because of Swift-type safety...
But still, again, why is this significant? Why does this make looking up an array faster than looking up something in a hash table? The reason is, because of Swift-type safety, when you instantiate something, you know that it's either going to be that class or it's going to be a subclass of that class, meaning that everything that the method is being called on has to implement all the same methods as the parent subclass, meaning the vtables have to all have entries for every method in the parent. So what a vtable does is that it maps the function to the same index for every vtable between a parent and its subclass, things that are inheriting from it. So see, makeCool is mapped to zero, the zero index for all trendsetter, Taylor Swift and me, and makeUncool is mapped to the first index for, again, those three classes. So going back to our code sample where we have this trendsetter, which is making bow-ties cool, we actually don't really care what type trendsetter is 'cause we know that makeCool is at the zero index of the vtable for all of these classes.
07:41: Furthermore, this calculation of mapping functions to indices is done at compile time, and the only thing that's happening at runtime is it just needs to know which vtable to look up at; it doesn't need to know which index to look at for the function pointer. So we're offloading this calculation from runtime to compile time.
By using final and private on methods or instead of using classes, just using the structs, you can actually completely skip dynamic dispatch...
But there's one more thing: By using final and private on methods or instead of using classes, just using the structs, you can actually completely skip dynamic dispatch because at compile time, the Swift compiler can know, can prove, that these methods aren't going to be subclass, there's only going to be one unique implementation, so it just in-lines that code when it's compiling it.
08:33: So to recap, Swift can speed up dynamic dispatch from Objective-C by using vtables because it has this type safety where when you instantiate something, it's either a parent or a subclass of that. And in certain situations, it doesn't even use dynamic dispatch; it just does static dispatch instead. So we're offloading a lot of these calculations into compile time instead of runtime. But again, this is just one of many myriad reasons as to why Swift can be faster than Objective-C. So if any of you have more insight as to other ways, I'd love to hear from you after the talk. Thank you.