contribute to the swift project

Recently my evenings have been spent focussing not on C++ or compiler sorcery, however on something much more elementary than I thought: Memory management. Whenever I find something I don’t know that is going to be key later on, I go off on a tangent, learn as much as I can, then write about it. It turns out that managing memory is going to be vital later on and after some frustration hunting around I finally found the answers I needed to get a better understanding of Alignment, Stride and Size when working with Swift (although this will be the same for any language).

MemoryLayout

Let me start by saying that originally Swift had all of the concepts mentioned above as standard, however later evolved it’s own wrapper around these concepts using MemoryLayout. You can instantiate your own, unsafe memory allocation using this type and inspect the size, stride and alignment automatically. But what are these properties exactly?

In the simplest terms I can put it, because there are a lot of veeeeery complicated documents out there: whenever you want to perform some kind of operation on something stored in memory, an Int for example, the CPU has to locate the value, get all of the data it needs from that value, perform the operation, then write it back to memory again. Tada! If that is all you needed to know then excellent, else… read on, dear friend.

Once the value is retrieved, it is stored in registers for working on in the CPU. You’ve seen these before: just hit ‘pause’ during your app whilst it’s running in the simulator to see the OpCode, you’ll see the registers, the size of the data such as ‘quadword’ etc and operations pending.

So what is the big deal with managing these things on your own? Well, it turns out that a lot is going on under the hood for you when you make something like a simple enum. Let’s look at an example:


1
2
3
4
struct User {
  let age: Int
  let isPestoLover: Bool
}

Let’s say we have a User Enum which has an age property and a boolean indicating if they are a pesto lover. The size of this enum, stored at a memory level, would be 1 byte for the Boolean, and 8 bytes for the Integer giving us 9 Bytes. This is simple enough, but what if you had a few of these models, would they be stored right next to each other in memory to save space? Well, no. You see, depending on which system you are on, the CPU accesses memory in chunks, snapshots if you will. Let’s say we crammed our 2 models right next to each other in memory, this might seem the most efficient memory-wise and you would be correct in assuming this, but not if the CPU goes to get our model and finds only half of it is in the snapshot.


1
2
3
4
5
6
7
struct User {
MemoryLayout<Int>.size + MemoryLayout<Bool>.size
// 8 + 1 = 9

MemoryLayout<User>.size
// also returns 9
}

Then it would have to store the first snapshot, go back and take another snapshot, combine both bits to make the full snapshot and… you get the picture. So one operation becomes many operations, making everything much slower and in things where speed is of the essence, this would not be ideal.

Well, it turns out while the size is self-explanatory for our struct, the ‘stride’ and ‘alignment’ properties are still a bit hazy, but they will solve our problem. The stride property is the size required to go from one element to the next, in an array for example, and if we inspect it using the Swift MemoryLayout type we can see that it is ’16 bytes’ which seems weird and overkill, right? Well, this is where the ‘alignment’ property comes into play. If we inspect that, we get ‘8 bytes’ which means that the CPU expects to take snapshots of our struct starting cleanly and evenly at 8 bytes at a time. If we remember from earlier, the size was 9 bytes, which means that we will need 16 to store our struct.

WHY?

This is the part that was a headache and this article explains it nicely, but if we packed our 9 byte struct using several instances right next to each other in memory, it wouldn’t take long for them to overlap so that the cpu was taking multiple snapshots, loading them into registers to work with and slowing right down would it. So it inserts empty memory, similar to padding, to ensure that it always aligns at 16 bytes. This might seem like a memory hog, but it speeds up processing and is the default way of doing things.

So essentially the size is the entire size required to load the full data for our object into the registers for operations, the stride is the full size required for moving from one element of it to another which will likely include ‘padding’, and the padding is calculated based on how the CPU wants this data aligned using the Alignment property.

Hopefully that makes some sense.