Hello and welcome to Understand Swift Performance. I'm Kyle. Arnold and I are so excited to be here today to talk to you guys about Swift.

As developers, Swift offers us a broad and powerful design space to explore. Swift has a variety of first class types and various mechanisms for code reuse and dynamism.

All of these language features can be combined in interesting, emergent ways. So, how do we go about narrowing this design space and picking the right tool for the job? Well, first and foremost, you want to take into account the modeling implications of Swift's different abstraction mechanisms. Are value or reference semantics more appropriate? How dynamic do you need this abstraction to be? Well, Arnold and I also want to empower you today to use performance to narrow the design space. In my experience, taking performance implications into account often helps guide me to a more idiomatic solution. So, we're going to be focusing primarily on performance. We'll touch a bit on modeling. But we had some great talks last year and we have another great talk this year on powerful techniques for modeling your program in Swift. If you want to get the most out of this talk, I strongly recommend watching at least one of these talks.

All right. So, we want to use performance to narrow the design space. Well, the best way to understand the performance implications of Swift's abstraction mechanisms is to understand their underlying implementation. So, that's what we're going to do today. We're going to begin by identifying the different dimensions you want to take into account when evaluating your different abstraction mechanism options. For each of these, we're going to trace through some code using structs and classes to deepen our mental model for the overhead involved. And then we're going to look at how we can apply what we've learned to clean up and speed up some Swift code.

In the second half of this talk, we're going to evaluate the performance of protocol oriented programming. We're going to look at the implementation of advanced Swift features like protocols and generics to get a better understanding of their modeling and performance implications. Quick disclaimer: We're going to be looking at memory representations and generated code representations of what Swift compiles and executes on your behalf. These are inevitably going to be simplifications, but Arnold and I think we've struck a really good balance between seeing simplicity and accuracy. And this is a really good mental model to reason about your code with.

All right. Let's get started by identifying the different dimensions of performance.

So, when you're building an abstraction and choosing an abstraction mechanism, you should be asking yourself, "Is my instance going to be allocated on the stack or the heap? When I pass this instance around, how much reference counting overhead am I going to incur? When I call a method on this instance, is it going to be statically or dynamically dispatched?" If we want to write fast Swift code, we're going to need to avoid paying for dynamism and runtime that we're not taking advantage of.

And we're going to need to learn when and how we can trade between these different dimensions for better performance.

All right. We're going to go through each of these dimensions one at a time beginning with allocation.

Swift automatically allocates and deallocates memory on your behalf. Some of that memory it allocates on the stack.

The stack is a really simple data structure. You can push onto the end of the stack and you can pop off the end of the stack. Because you can only ever add or remove to the end of the stack, we can implement the stack -- or implement push and pop just by keeping a pointer to the end of the stack. And this means, when we call into a function -- or, rather -- that pointer at the end of the stack is called the stack pointer. And when we call into a function, we can allocate that memory that we need just by trivially decrementing the stack pointer to make space. And when we've finished executing our function, we can trivially deallocate that memory just by incrementing the stack pointer back up to where it was before we called this function. Now, if you're not that familiar with the stack or stack pointer, what I want you to take away from this slide is just how fast stack allocation is. It's literally the cost of assigning an integer.

So, this is in contrast to the heap, which is more dynamic, but less efficient than the stack. The heap lets you do things the stack can't like allocate memory with a dynamic lifetime.

Untitled

But that requires a more advanced data structure. So, if you're going to allocate memory on the heap, you actually (1) have to search the heap data structure to find an unused block of the appropriate size. And then when you're done with it, to (2) deallocate it, you have to reinsert that memory back into the appropriate position.

So, clearly, there's more involved here than just assigning an integer like we had with the stack. But these aren't even necessarily the main costs involved with heap allocation.

Because multiple threads can be allocating memory on the heap at the same time, the heap needs to protect its integrity using locking or other synchronization mechanisms. This is a pretty large cost. If you're not paying attention today to when and where in your program you're allocating memory on the heap, just by being a little more deliberate, you can likely dramatically improve your performance.

Untitled

All right. Let's trace through some code and see what Swift is doing on our behalf. Here we have a point struct with an x and y stored property. It also has the draw method on it. We're going to construct the point at (0, 0), assign point1 to point2 making a copy, and assign a value of five to point2.x. Then, we're going to use our point1 and use our point2. So, let's trace through this.