Understand the differences in performance between struct and class in Swift

If you were asked what the difference between struct and Swift in class would be, what would you say?
I think most people’s first reaction should be that struct is a value type and class is a reference type, and they are different in semantics. Think of other may be the same point between them, for example, can be used to define the type, can have property, can have method, so they didn’t understand the grammar from what meaning, actually from a different semantics can be extended to a lot, from the initial mode, memory management, method of payment the way is different, this is not our focus of this discussion, this difference mainly discusses the performance of struct and class in different situations

We compare performance differences between struct and class primarily from three performance dimensions

  • memory allocation
  • Reference count
  • Method distribution

memory allocation

Memory allocation is divided into two types: stack memory allocation and heap memory allocation

Stack memory storage structure is relatively simple, you can simply understand the push to the bottom of the stack, pop out so simple, but to do is to move the stack needle to allocate and destroy memory

Heap memory is more than the stack storage structure is complex, his assignment way you can understand the idle block of memory to find suitable size in the heap allocation of memory, the memory block is re inserted into the heap to destroy a memory, of course, this is just the heap memory compared to stack memory performance consumption, more important the heap memory support for multi-threaded operation, corresponding to ensure thread safety through synchronization etc..

Here we look at the performance differences between struc and class in memory allocation from a code point of view

Struct Point x y: Double {VaR, func}} {(draw) point1 = let... Point (x: 0, y: 0) var point2 = point1 point2.x = 5 / / use / / use `point2` `point1`

Define a struct Point containing property, x, y, method, draw

First, before the code is executed, the compiler allocates a memory of 4 word sizes on the stack, and the allocation process is the mobile stack pin I mentioned above

Understand the differences in performance between struct and class in Swift

Using (0,0) to initialize point1 and assign point1 to point2, you can understand that value is copied to the allocated stack memory, because struct is a value type, so point1 and point2 are two separate instance

Understand the differences in performance between struct and class in Swift


修改point2.x不会影响point1

Understand the differences in performance between struct and class in Swift

Finally, the use point1 use point2 leaves the scope, moves the stack pointer, and destroys the stack memory

Next, let’s take a look at class

Class Point x y: Double {VaR, func}} {(draw) point1 = let... Point (x: 0, y: 0) let point2 = point1 point2.x = 5 / / use / / use `point2` `point1`

Just put the struct into other places first in the class invariant
code execution before the compiler will allocate two word size on the stack memory, but this time is not used to store Point property

Understand the differences in performance between struct and class in Swift


代码执行初始化point1在堆上寻找合适大小的内存块分配

Understand the differences in performance between struct and class in Swift


然后将value拷贝到堆内存存储,并在栈上存储一个指向这块堆内存的指针

Understand the differences in performance between struct and class in Swift


可以看到堆上分配的是4个word大小的内存块,剩余的两个蓝色格子存放的是关于class生命周期相关函数表的指针

Let point2 = point1执行的是引用语言,只是在point2的栈内存存储了一个指向point1堆内存的指针

Understand the differences in performance between struct and class in Swift


修改point2.x也会影响到point1

Understand the differences in performance between struct and class in Swift


最后use point1 use point2 离开作用域,堆内存销毁(查找并把内存块重新插入到栈内存中),栈内存销毁(移动栈针)

当然栈内存在分配的过程中还要保证线程安全(这也是一笔很大的开销)

How do you think the Point in the above case is represented by struc, much faster than class in memory allocation?

Here’s an optimization scheme for practical applications

Enum Color {case blue, green gray, Orientation case left} enum {right} Tail {case, enum, none, tail, bubble} var cache = [String: UIImage] (func) makeBalloon (_ color: Color, orientation: Orientation, tail: Tail -> UIImage) {let = "/ key / ((color): orientation): / (tail)" if let image cache[key] return = {image}}...

Application of this code is our common iMessage chat interface bubble production can be function, three enum represents a different bubble, such as blue to the left tail, because the user may often slide chat list to view messages makeBalloon this method call is very frequent, in order to avoid the repeated generation of UIImage, we use Dictionary to as a cache, with different conditions of enum serialization of a String to cache as key.

Think about what’s wrong with this writing? The
problem is in key above, with String as key, first in a type safe storage above cannot guarantee certain is the type of a bubble, because it is String so you can store worthless and so on. Second, String’s character is stored on top of the heap, so each time you call makeBalloon, though it hits cache, you still have to design, heap, allocate memory, and destroy it

How to solve?

Struct Attributes: Hashable, {VaR, color:, Color, VaR, orientation:, VaR, tail:, Tail, Orientation,}, let, key = Attributes (color:, color, orientation:, orientation, tail:, tail)

You should see the optimization code Swift has the original see light suddenly, we are ready for the better type struct and he can be used as Dictionary key, which does not involve any heap memory allocation destroyed, and that Attributes can ensure type safety

Reference count

How does Swift know that heap memory is safe and can be released? The answer is that reference counting principle is the insertion of a refCount inside the instance property to manage reference counting, new reference refCount+1, citing the destruction of refCount-1, refCount 0, Swift will know the memory of instance is safe to release.

Performance consumption in reference counting is of course not as simple as adding minus one, first of all, a pair of indirect calls to retain and release, followed by reference counting to support multiple threads, so it’s also necessary to ensure thread safety

Let’s look at class first, and here’s a block of pseudo code that explains the principles of reference counting

Class Point refCount: Int var {var x, y: Double func (draw) {...}} let point1 = Point (x: 0, y: 0) let point2 = point1 retain (point2) point2.x = 5 / / use / / use `point1` release (point1) `point2` release (point2)

The amount of memory allocated no longer, let point1 = Point (x: 0, y: 0) the memory of the refCount 1
retain (point2) refCount to 2 and then use point1 left the scope of release (point1) refCount reduced to 1release (point2) refCount reduced to 0 and destruction of memory

From the previous memory allocation section, the memory allocation of struct Point is stack memory allocation, and it does not involve any reference counting.

What if there is a reference type of struct in property?

Struct Label var text: String var font: {UIFont func (draw)}} {... Let label1 = Label (text: Hi, font: font) let label2 = label1 use / / `label1` / / use `label2`

The above code defines a struct Lable containing a String value type but character stored in the heap, UIFont is a class
storage on the heap (text: = let label1 Label Hi, font: font) to initialize the label1 look at what will happen to

Understand the differences in performance between struct and class in Swift


Let label2 = label1lable1赋值给label2

Understand the differences in performance between struct and class in Swift


看到了吧,一共有四次引用计数操作,也就是会有六个调用,我们看一下这个过程的伪代码

Struct Label var text: String var font: {UIFont func (draw)}} {... Let label1 = Label (text: Hi, font: font) let label2 = label1 retain (label2.text._storage) retain (label2.font) / / use label1 release (label1.text._storage) release (label1.font) / / use label2 release (label2.text._storage) release (label2.font)

What do you think in the reference counting dimension? Conclusion
is
in the reference count of this dimension does not contain references to struct performance attributes of the consumption minimum, because he does not involve reference counting, containing a reference to the performance of struct type consumption is equal to class, containing two or more reference type property struct in reference to the consumption of performance counting is an integer multiple of class

Here’s an optimization scheme for practical applications

Struct Attachment fileURL: URL let uuid: {let String let mineType: String init (fileURL: URL, uuid:? String, mimeType: String) {guard mineType.isMineType} else {return nil self.fileURL = fileURL self.uuid = UUID self.mineType = mimeType}}

This code is a model we give message new, said the file sent when we chat, it contains a URL type fileURL indicates the location of the file, a String type UUID convenient for us in different equipment positioning this document, because it is not possible to support all file formats, so there is a type of String the mineType is used to filter the file format is not supported

Think about the performance issues of creating model, or do you have any optimized solutions?
, look at the storage of Attachment,

Understand the differences in performance between struct and class in Swift


因为URL为引用类型,String的character存储在堆上,所以Attachment在引用计数的层面消耗有点大,感觉可以优化,那怎么优化?

First of all, is it really good to declare UUID as a String type? The answer is bad. First, you can’t limit the security of UUID on a type. Two, reference counting is involved. How do you optimize that? UUID was added to Swift Fundation in 16 years. This type is a value type that does not involve reference counts, and can also satisfy us on type safety. Let’s take a look at mineType

Extension String var isMimeType: Bool {switch {self {case "image/jpeg" return true case "image/png" return true case "image/gif" true default: return false return}}}

We are through the String to expand the way of filtering we support the file format, see here you must think of Swift as we provide better representations of the abstract type enum variety of situation, it is a value type, does not involve the reference count (enum can be used as a reference type, in individual cases will not be discussed here)

Enum MimeType: String, {case, JPEG = image/jpeg, case = image/png, case GIF = image/gif, PNG}

Optimized model:

Struct Attachment fileURL: URL let uuid: {let UUID let mimeType: MimeType init (fileURL: URL, uuid:? UUID, mimeType: String) {guard let mimeType = MimeType (rawValue: mimeType) {return} else nil self.fileURL = fileURL self.uuid = UUID self.mimeType = mimeType}}
Understand the differences in performance between struct and class in Swift

The optimized model in the best state, read this example after you write my own code to look at, whether this can optimize the place

Method distribution

Static distribution
compiler, the function address is encoded directly in the assembly, when the call, according to address jump directly to the implementation, the compiler can be inline and other optimization (struct are static distribution)

The lookup table function of dynamic distribution of
running, find and then jump to realize, of course, the dynamic distribution of only one more a look-up table link is not his slow reason, the real reason is the way he stopped the compiler can do inline optimization (class special estrus condition, here we only discuss the dynamic distribution the situation)

See an example of inline optimization

Struct Point x y: Double {VaR, func (draw) {/ / Point.draw implementation func drawAPoint (param: _}} {param.draw}) (Point) let point = Point (x: 0, y: 0) drawAPoint (point)

This code calls for these calls,

  1. DrawAPoint (point)
  2. Point.draw ()
  3. Implementation
    struct / / Point.draw because method is static distributed, that is to say the compiler knows address function, so it can be the first step of optimization
  4. Point.draw ()
  5. Point.draw implementation / /
    of course can also carry out the second steps optimization
  6. Point.draw implementation / /
    see. Through the compiler’s inline optimizations, two steps can be omitted to avoid the overhead of a function call such as a stack, a stack, and so on. Jump directly to the implementation of the function, understand the reason why static distribution faster

If you change the above to class, the compiler can’t do inline optimizations, and the static distribution of struct is better than the method that distributes the dimension

Data reference 2016WWDC416