Swift performance correlation

The first question stems from the fact that, in Swift, Struct:Protocol is better than an abstract class? “. But what you’re looking for is Swift performance related stuff. Finishing the notes, for everyone to refer to. Some questions before the start of the matter, do not know if you have the following questions:

  • Why is it that Swift is faster than Objective-C?
  • Why is it so slow when compiling Swift?
  • How to write Swift more gracefully?

If you have similar questions, I hope this note can help you explain some of the above issues. PS. above a few questions are great, if there are different ideas and understanding, I hope you can share it, we discuss together. )

Types in Swift

First of all, let’s unify some concepts about types.

  • Trivial type

Some types need to be operated in the form of a byte representation, without the need for extra work, which we call the trivial type (trivial). For example, Int and Float are trivial types, and struct or enum that contain only trivial values is also an ordinary type.

Struct AStruct a: Int struct {var} BStruct {var} / / AStruct a: AStruct & BStruct are common types;
  • reference type

For reference types, the value instance is a reference to an object. Copying this value instance means creating a new reference, which will increase the reference count. Destroying this instance means destroying a reference, which reduces the reference count. Continue to reduce the reference count, and eventually it will turn 0 and cause the object to be destroyed. However, we need to pay special attention to the value of replication and destruction that we are talking about here, just to the operation of the reference count, rather than copying or destroying the object itself.

Struct CStruct a: Int class {var} AClass {var a: CStruct} class {BClass} / / AClass var a: AClass & BClass is a reference type;
  • Combination type

In the case of AClass, a reference type that contains an ordinary type is a reference type, but a reference type is used for an ordinary type.

Struct DStruct {var a: AClass} / / DStruct is a combination of types

Main factors affecting performance

The main reasons are as follows:

  • Memory allocation (Allocation): mainly in heap memory allocation or stack memory allocation.
  • Citation count (Reference counting): how to weigh the reference count.
  • Dispatch (Method): mainly in the static scheduling and dynamic scheduling problems.

Memory allocation (Allocation)

Today talk about the heap and stack in the memory partition.

  • Heap (heap)

Heap is used to store the process is dynamically allocated memory segment, its size is not fixed, can be dynamically expanded or reduced. When the process calls malloc and other functions to allocate memory, the newly allocated memory is dynamically added to the heap (heap is expanded); when using free and other functions to release the memory, the freed memory is removed from the heap (heap is reduced)

  • Stack (stack heap)

The stack is also called stack, local variables stored procedures to create temporary users, that is to say we “{}” brackets function defined in the variable (but not including the declaration of static variables, static means that the variables are stored in the data section). In addition, when the function is called, its parameters are pressed into the process stack that is invoked, and the return value of the function will be returned to the stack until the end of the call. Due to the stack in the first out of the characteristics, so the stack is particularly convenient to save / restore call site. In this sense, we can view the stack as a memory area for storing and exchanging temporary data.

In Swift, there is a stack in an ordinary type, and a reference type is present in the heap, as shown in the following figure:

Swift performance correlation

As we all know, Swift suggests that we use an ordinary type, so what is the good of the ordinary type? In other words, what are the advantages of the data in the stack and the data in the heap? “

  • Data structure stored in the stack data structure is relatively simple, only some of the things related to the value of the data stored in the heap is more complex, as shown above, there will be type, retainCount, etc..
  • The allocation of data and read the data stored in the stack from the bottom of the stack area (push), from the stack top (POP), similar to a stack of data structures. Because we can only modify the stack at the end, so we can achieve this by maintaining a data structure to the end of the stack pointer, and in which the memory allocation and release only need to assign the integer. So the allocation and release of memory on the stack is very small. Data stored in the heap is not a direct push/pop, similar to the list of data structures, the need to find an optimal algorithm through the use of unused memory blocks, and then store data. At the same time, we need to re interpolation of memory.
  • Multithreading is unique to the stack, so there is no need to consider thread safety issues. Heap data is multi-threaded sharing, so in order to prevent the thread is unsafe, need to synchronize the lock to solve this problem.

To sum up, when the memory allocation, as far as possible to choose the stack rather than the heap will allow the program to run faster.

Reference count (Reference counting)

The first reference count is a memory management technique that does not require the programmer to manipulate pointers directly to manage memory.

The use of reference counting memory management technology, will bring some impact on performance. Mainly in the following two aspects:

  • It takes a lot of release/retain code to maintain an object lifecycle.
  • Stored in the heap area is shared by multiple threads, so for each modification of the retainCount need to ensure the safety of the thread through the synchronization lock.

For the automatic reference count, when adding release/retain is used to write a lot of writing is not written, so release/retain has a certain degree of redundancy. This redundancy is about 10% or so (as shown below, images from the iOS executable file downsizing method).

Swift performance correlation

And this is why, although the underlying memory management algorithm for the ARC has been optimized, and there is no faster than the speed of the reasons written in MRC. This article describes in detail the speed of ARC and MRC comparison.

To sum up, although the introduction of automatic reference counting, greatly reducing the memory management related things, but for the reference count, too much or redundant reference count will slow down the operation of the program.

As for the reference count, there is a trade-off problem, specifically how the balance will be explained later.

Method scheduling (Method dispatch)

In Swift, there are two kinds of scheduling methods:

  • Static scheduling: inline and other compiler can be optimized, in the implementation of the time, it will jump directly to the realization of the method.
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) / 1 compiled into point.draw following inline (2) / / operation, direct jump to the Point.draw implementation
  • Dynamic scheduling: at the time of execution, it will use the V-Table method to find the execution of the method and then execute it. Compile time optimization. V-Table is different from the OC scheduling, in the OC, is the first in the running time in the sub class to find a method, if you can not find, then go to the parent class to find ways. For V-Table, its scheduling process is as follows:
Swift performance correlation

Therefore, the performance of “static scheduling > dynamic scheduling” and “Swift &gt in V-Table; dynamic scheduling of Objective-C”.

Protocol type (Protocol types)

The Swift introduces the concept of a protocol type, as follows:

Protocol Drawable draw (struct) {func} Point {var: Drawable x, y: Double func (draw) {...}} struct {Line: Drawable var x1, Y1, X2, y2: Double func (draw) {...}} / / var drawables: [Drawable] Drawable is called for D in drawables protocol type {d.draw (})

In the above code, Drawable is called the protocol type, because ordinary types have no inheritance, so there are some difficult problems to achieve polymorphism, but Swift introduced the protocol type is a good solution to the ordinary type of polymorphism, but in the design of protocol types when there are two major problems:

  • For a similar Drawable protocol type, how to schedule a method?
  • For different types, with different size, when saved to the drawables array, how to ensure that the memory alignment?

For the first question, how to schedule a method? As for the ordinary type, and not what the virtual function pointer, so in Swift and no V-Table, but is still used in a The Protocol Witness Table (PWT) in the function table, as shown below:

Swift performance correlation

For each Struct:Protocol will generate a StructProtocol of PWT.

For the second question, how to ensure the memory alignment problem?

Swift performance correlation

There is a simple and crude way is the maximum memory array alignment Size as standard, but this way will not only cause the waste of memory problems, there will be a more difficult problem, how to find the maximum Size. So in order to solve this problem, Swift introduces a data structure called Existential Container.

Swift performance correlation
  • Existential Container
Swift performance correlation

This is one of the most common Existential Container.

  • The first three word:Value buffer. Used to store the value of Inline, if the number of word is greater than 3, then the use of pointers, the allocation of the corresponding size of the memory on the heap
  • Fourth word:Value Witness Table (VWT). Each type corresponds to such a table, used to store the value of the creation, release, copy and other operating functions. (manage Existential Container lifecycle)
  • Fifth word:Protocol Witness Table (PWT), used to store the protocol function.

Use the pseudo code to indicate the following:

Swift / struct pseudo code ExistContDrawable {var valueBuffer: (Int, Int, Int) var vwt: ValueWitnessTable var pwt: DrawableProtocolWitnessTable}

Therefore, the data structure of Point and Line in the above code is as follows:

Swift performance correlation

Here need to pay attention to a few points:

  • Before the ABI stability value size may change, for the 3 word is not still Swift team is still weighing up the buffer
  • Existential Container size is not only 5 word. Examples are as follows:
Swift performance correlation

For the size of the main difference is the PWT pointer to Any, there is no specific function, so do not need the PWT pointer, but for ProtocolOne& the combination of ProtocolTwo protocol, is the need of two PWT pointers to the said.

OK, due to the introduction of the Existential Container, we can use the protocol as a type to solve the problem of trivial types that are not inherited, so Struct:Protocol and abstract classes are becoming more and more like.

Back to our original question, “where is the Struct:Protocol in the Swift better than the abstract class? “

  • Because Swift can only be a single inheritance, the abstract class is easy to cause the “God class”, and Protocol can be a number of these are not the problem
  • In memory allocation, Struct is in the stack, and the abstract class is in the heap, so the simple data Struct:Protocol performance will be better than the abstract class
  • Is it more or less forced to write? )

However, although the type of protocol on the surface is indeed “better” than the abstract class, I would like to say, do not casually use the agreement as a type to use.

Why do you say that? First look at a section of code:

Struct Pair {init (f: Drawable _ _, s: Drawable) {first = f; second = s var first: Drawable var second: Drawable}}

First of all, we put the Drawable protocol as a type, as the Pair attribute, because the agreement type of value buffer only three word, so if a struct (such as the above Line) more than three word, then the value would be saved to the stack, so it will cause the phenomenon:

Swift performance correlation

A simple copy, resulting in the property of the copy, causing a large heap memory allocation.

So don’t just use the protocol as a type. The above situation is invisible, but you have not found.

Of course, if you want to use the agreement as a type can also be resolved, first of all need to Line instead of struct, the purpose is to introduce the reference count class. So, after the Line to class, it becomes as shown below:

Swift performance correlation

As for the modification of the line X1 causes all pair under the line value of the X1 has changed, we can introduce Copy On Write to solve.

When we use Line ordinary type, because line takes 4 word, when the agreement as a type, value buffer line will not exist, leads to the heap memory allocation, and each copy will trigger a heap memory allocation, so we use a reference type to replace the ordinary type, increase the reference count while reducing the heap memory allocation, this is a good tradeoff between the reference count.

Generic (Generic code)

First of all, if we treat the protocol as a type, we call it dynamic polymorphism:

Protocol Drawable draw (func) {func} drawACopy (local: Drawable) (let line) {local.draw} = (Line) drawACopy (line) point = Point let / /... (drawACopy) (point)

And if we use generics to rewrite it, we call it static polymorphism:

Drawing a copy using a generic / method protocol Drawable (draw) {func} func drawACopy< T: Drawable> (local T) {local.draw} (let) = Line (line) drawACopy (line) point = Point let / /... (drawACopy) (point)

And what is the difference between dynamic and static?

Before Xcode 8, the only difference is due to the use of generic, so in the scheduling method, we can according to the context to determine exactly what type of the T, so it does not need to Existential Container, so do not use the Existential generic Container, but because polymorphism, they still need to VWT and PWT passed as parameters for the contact. The temporary variable is still in accordance with the logic of storage – distribution of ValueBuffer 3 word, if the stored data size more than 3 word, then opened in memory on the heap. As shown in the figure:

Swift performance correlation

This form is not the same as the type of agreement and no difference. The only thing is that there is no Existential Container intermediate layer.

However, after Xcode 8, the introduction of the Whole-Module Optimization to make generic writing more static.

First, the compiler generates a drawACopy method for each type because of the type that can be determined according to the context:

Func drawACopy< T: Drawable> (local T) {local.draw} (func drawACopyOfALine) / / compiled (local: Line) (func drawACopyOfAPoint) {local.draw} (local: Point) {local.draw}) (for example: drawACopy (local: / Point (x: 1, y: 1) (drawACopyOfAPoint) / / into local: Point (x: 1, y: 1))

Because each type of generating a drawACopy method, the drawACopyOfAPoint call it programming a static scheduling, according to the previous time of static scheduling, the compiler will do inline processing, so the above code through the compiler process after the code is as follows:

DrawACopy (local: Point (x: 1, y: 1)) / / become Point (x: 1, y: 1) (.Draw)

Because the compiler step by step, no longer need vwt, PWT and value buffer. So for generics to do polymorphism, it is called static polymorphism.

Some summary

  • Why in the compilation of Swift so slow because the compiler to do a lot of things, such as static scheduling of inline processing, static polymorphism analysis processing
  • Why Swift compared to Objective-C will speed up for Swift, more static, such as static scheduling, static polymorphism, etc.. More stack memory allocation with fewer reference counts
  • How to write a more elegant Swift do not treat the agreement as a type to deal with if you need to deal with the agreement as a type, you need to pay attention to the replication of big Value on the heap memory allocation problem. Write Storage + Copy On can be used to deal with Indirect. For some abstractions, Struct:Protocol can be used instead of abstract classes. At least there will be no God class, and the processing of good performance is better than the abstract class.

Reference material

  • Understanding Swift Performance
  • Swift performance optimization in the real world
  • Exploring Swift Memory Layout
  • The level is limited, if there is a mistake, I hope to correct! Coderonevv#gmail.com


After work, I wrote a few notes, if necessary, can be seen in my GitHub.