why structs are faster than classes

I wanted to solidify in my mind why this is true – I’ve heard structs are faster than classes several times, but wasn’t sure if I understood exactly why. Back at Rutgers, I took an OS class where I learned a ton about how computing actually works, but nowadays, I find myself so abstracted from the details that its easy to get rusty on foundational CS concepts.

So structs are value types, and classes are reference types. What’s the difference between the two? Value types have data stored in its own memory allocation, and reference types store a pointer to the actual data. Simple enough.

One reason that value types are more performant than reference types is because value types are allocated statically, whereas reference types are allocated dynamically. What does this mean and why is static allocation a performance boost?

Recall that when something is allocated statically, its size is fixed when the program is created. Primitive types like int, boolean, char, are allocated statically, i.e. on the stack.

Take a look at this function:


func foo() {
  let x = 2
  let y = 2
  print(x + y)
}

When this function is called, a block is reserved on the top of the stack for local variables and bookkeeping data. In this case, we would allocate some space on the call stack for x and y. When this function finishes executing, the stack is popped, and the memory we allocated for x and y is released.

This is in contrast to dynamic allocation, where the lifetime of allocated memory is managed by the programmer, or in some languages, by a garbage collection mechanism.

class Person {
  let name: String
  var friends: [Person]
  
  func addFriend(_ friend: Person) {
    friends.append(friend)
  }
}

func foo(person: Person) {
  let friend = Person(name: "Adam") 
  person.addFriend(friend) 
}

Here, we allocate memory on the heap for friend, and also for extra space in the friends array if needed. The lifetime of friend is not determined by the function scope, but by its reference count.

With static allocation, we avoid the overhead of calling malloc(), which has to scan for a free block of memory, possibly resize it, mark it used, etc. The OS is able to allocate a fixed amount of space on the stack, then accessing the statically allocated variable is a matter of bumping the stack pointer. The big O complexity of memory allocation is improved by not having to find a chunk of memory to allocate.

So Swift structs, since they are allocated statically, get a performance boost at runtime because memory allocation takes less time.

There’s a second reason that structs can be faster than classes. Operations on the struct are dispatched statically. Meaning, the compiler has insight into exactly what will be executed on a struct. As opposed to dynamic dispatch, where implementations have to be looked up at runtime. Because the compiler knows what’s going to be executed, it can optimize the generated machine code.

For more on static vs dynamic dispatch, check out this and this