Introduction to the

In 2014, Apple announced Swift, a new programming language, at WWDC. Over the years, Swift has become a mainstay of the iOS development language, providing very flexible high-level features such as protocols, closures, generics, In addition, Swift has further developed powerful SIL (Swift Intermediate Language) for compiler optimization, which enables Swift to run faster and perform better than Objective-C. How to achieve performance optimization within Swift is explained in this paper. Hope to inspire and help you.

The problem of Swift performance improvement can be conceptually broken down into two parts:

  1. Compiler: Performance optimization carried out by Swift compiler is divided into compilation period and run period from stage, and the content is divided into time optimization and space optimization.
  2. Developers: Help the compiler get more information and optimize by using appropriate data structures and keywords.

Below, we will analyze Swift performance optimization from these two perspectives. By understanding the internal implementation of the compiler to deal with different data structures, we can choose the most appropriate algorithm mechanism, and use the optimization features of the compiler to write high performance programs.

Understand Swift’s performance

To understand Swift’s performance, it’s important to understand Swift’s data structure, component relationships, and how compilation runs.

  • The data structure

    The data structure of Swift can be roughly divided into Class, Struct, and Enum.

  • Relationship between components

    Component relationships can be divided into: Inheritance, Protocols, generics.

  • Method dispatch mode

    Method dispatch can be divided into Static dispatch and Dynamic dispatch.

Improving Swift performance in development requires developers to understand these data structures and component relationships and their internal implementations to improve performance by choosing the most appropriate abstraction mechanism.

First of all, let’s make a conceptual statement about performance standards. Performance standards cover three criteria:

  • Allocation
  • Reference counting
  • Method dispatch

Next, we will explain these indicators respectively.

Allocation

Memory allocation can be divided into heap area stack area, memory allocation on the stack is faster than the heap, structure and class allocation on the stack are different.

Stack

The basic data types and structures are in the stack by default, and the stack memory is contiguous, allocated and destroyed by going on and off the stack, faster than the heap.

Here are some examples:

Example 1 // Allocation // Struct Struct Point {var x, y:Double funcdraw() { … }
}
letPoint1 = Point(x:0, y:0) var point2 = Point(x:0, y:0) X = 5 // Operation on point2 does not affect point1 // use 'point1' // use 'point2'Copy the code

The memory of the above structure is allocated in the stack area, and the internal variables are inline in the stack area. Assigning point1 to point2 actually makes a copy on the stack, creating a new memory-consuming point2. This makes point1 and Point2 completely separate instances, and their operations do not affect each other. After poinT1 and point2 are used, they are destroyed.

Heap

High-level data structures, such as classes, are allocated in the heap area. Unused memory blocks are found during initialization and removed from the block when destroyed. Because there may be multithreaded operations in the heap, locking is required to ensure thread safety, which is also a performance cost.

// Allocation
// Class
class Point {
 var x, y:Double
 func draw() { … }
}
letPoint1 = Point(x:0, y:0) // Allocate memory in heap area, stack area only store address pointerletX = 5 // Since point1 and point2 are one instance, // use 'point1' // use 'point2'Copy the code

We initialized a Class and allocated a chunk of memory on the stack, but instead of storing values directly on the stack, we only stored Pointers to objects on the stack, which are allocated in the heap. Note that in order to manage object memory, in addition to allocating attribute memory (in this case x, y of type Double), there are two additional fields, type and refCount, when the heap is initialized. This structure containing type, refCount, and the actual attribute is called the Blue box.

Summary of Memory allocation

From an initialization perspective, classes have more powerful features than structs that allocate memory in the heap, manage memory, and use Pointers, but have lower performance.

Optimization method:

For frequent operations (such as content bubbles in communication software), try to use structs instead of classes because stack memory allocation is faster, safer, and faster to operate.

Reference counting

Swift manages heap object memory by reference count, and when the reference count is zero, Swift confirms that no object references the memory again, so it frees the memory. Reference count management is a very high frequency indirect operation and requires consideration of thread safety, which makes reference count operation requires high performance consumption.

For structs of basic data types, there is no management of heap memory allocation and reference count, which is higher performance and safer, but for complex structures such as:

// Reference Counting
// Struct containing references
struct Label {
 var text:String
 var font:UIFont
 func draw() { … }
}
let label1 = Label(text:"Hi"// The stack contains Pointers stored in the heapletLabel2 = label1 // Label2 generates a new pointer to the same string and font address as label1 // use 'label1' // use 'label2Copy the code

As you can see here, structures that contain references need to manage double the reference count compared to Class. Multiple reference counts occur each time a structure is passed as an argument to a method or a direct copy is made. The following figure can be intuitively understood:

Note: Copy processing is displayed for structures containing reference types

Class is handled when copied:

Reference count summary

  • ClassTo allocate memory in the heap, reference counters are required for memory management.
  • Basic typeStructAllocate memory in stack area, no reference count management.
  • Contains strongly typedStructUsing Pointers to manage properties in the heap, copying a structure creates new stack memory, creating multiple Pointers to references,ClassThere will only be one.

Optimize the way

When using a structure:

  1. By using exact types, such as UUID instead of String (UUID bytes are fixed at 128 bytes instead of arbitrary String length), you can inline memory, store UUID on the stack, and we know that stack memory management is faster and safer, and no reference counting is required.
  2. Enum replaces String, manages memory on the stack, has no reference count, and is syntactically more developer-friendly.

Method Dispatch

As we mentioned earlier in Static dispatch vs. Dynamic Dispatch, the way to determine the execution method at compile time is called Static dispatch, it can’t be determined at compile time, Dispatch that can only be determined at run time is called Dynamic dispatch.

Static Dispatch is faster, and Static dispatch can be further optimized for inlining, resulting in faster execution and higher performance.

However, in the case of polymorphism, we cannot determine the final type at compile time, so Dynamic dispatch is used. The implementation of dynamic dispatch is that each type creates a table containing an array of method Pointers. Dynamic dispatch is more flexible, but because there are table lookups and jumps, and because many features are not clear to the compiler, it is equivalent to some late optimization by the compiler. So it’s slower than Static Dispatch.

Here is a piece of polymorphic code, and analysis of the implementation:

// Reference semantic implementation polymorphic class Drawable {funcdraw() {} }
class Point :Drawable {
 var x, y:Double
 override func draw() {... } } class Line :Drawable { var x1, y1, x2, y2:Double override funcdraw() {... } } var drawables:[Drawable]for d inDrawables {d.draw ()}Copy the code

Method Dispatch summary

The Class uses Dynamic Dispatch by default, which hinders compiler optimizations such as inline and whole Module inline because almost every link is indeterminable at compile time.

Use Static Dispatch instead of Dynamic Dispatch to improve performance

We know that Static dispatches are faster than Dynamic dispatches, and how to use Static dispatches as much as possible in development.

  • We can use the final keyword to modify the Class to produce a final Class, using Static dispatch.

  • A private keyword modifier that makes a method or property visible only to the current class. The compiler performs Static dispatch on the method.

The compiler can check inheritance through Whole Module Optimization, evaluate certain classes that are not marked final, and use Static Dispatch if it can determine the method to execute at compile time. Struct uses Static dispatch by default.

One key to Swift being faster than OC is its ability to negate dynamic dispatch.

conclusion

Swift provides more flexible structs for performance optimization in terms of memory, reference counting, method dispatch, etc. Choosing the right data structure at the right time can make our code performance faster and safer.

extension

You might ask how does a Struct implement polymorphism? The answer is Protocol Oriented programming.

How do different algorithmic mechanisms Class, Protocol Types, and Generic code perform in these three aspects? How are Protocol Types and Generic code implemented respectively? So let’s go with that.

Protocol Type

Here we discuss how Protocol Types store and copy variables, and how method dispatch is implemented. Polymorphisms without inheritance or reference semantics:

protocol Drawable { func draw() }
struct Point :Drawable {
 var x, y:Double
 func draw() {... } } struct Line :Drawable { var x1, y1, x2, y2:Double funcdraw() {... }} var drawables:[Drawable] // A set of types that comply with the Drawable protocolfor d inDrawables {d.draw ()}Copy the code

The above is realized through Protocol Type polymorphism. There is no inheritance relationship between several classes, so dynamic dispatch cannot be realized by using V-table as usual.

To view the implementation of Vtable and Witness Table, click. Details are not explained here. Because of the different sizes of Point and Line, array storage implements consistent storage, using An Interface Container. Protoloc Witness Table is used to find the correct execution method.

Existential Container

An Intuitive Container is a special memory layout for managing Protocol Types that adhere to the same Protocol. Because these data types do not share the same inheritance (a prerequisite for v-Table implementations) and have different memory sizes, Managed using An Interface Container for storage consistency.

The structure is as follows:

  • A valueBuffer consists of three words, each of which contains eight bytes. A valueBuffer can store either a value or a pointer to an object. For small values (smaller than valueBuffer), the value is stored directly in the address of valueBuffer, inline valueBuffer, no additional heap memory initialization. When the number of values is greater than three attributes (large values), or the total size exceeds the space occupied by valueBuffer, memory is opened in the heap and stored in the heap, where valueBuffer stores memory Pointers.
  • Value Witness Table reference becauseProtocol TypeType, memory space, initialization methods, etc., are different in order to pairProtocol TypeLife cycle for specific management, used toValue Witness Table.
  • Protocol Reference management of the Witness tableProtocol TypeMethod dispatch.

Memory distribution is as follows:

1. payload_data_0 = 0x0000000000000004,
2. payload_data_1 = 0x0000000000000000,
3. payload_data_2 = 0x0000000000000000,
4. instance_type = 0x000000010d6dc408 ExistentialContainers`type    
       metadata for ExistentialContainers.Car,
5. protocol_witness_0 = 0x000000010d6dc1c0 
       ExistentialContainers protocol witness table for 
       ExistentialContainers.Car:ExistentialContainers.Drivable 
       in ExistentialContainers
Copy the code

Protocol Witness Table (PWT)

In order to achieve Class polymorphism, that is, reference semantic polymorphism, v-table needs to be implemented, but the premise of V-table is to have the same parent Class, that is, to share the same inheritance relationship, but for Protocol Type, it does not have this feature, so in order to support the polymorphism of Struct, The protocol Oriented programming mechanism is used, that is, the Protocol Witness Table can be implemented by clicking on the Vtable and Witness Table implementation for details. Each structure will create a PWT Table with Pointers inside. Point to method concrete implementation).

Value Witness Table (VWT)

Manages initialization, copying, and destruction of arbitrary values.

  • The Value Witness Table is structured as shown above and is used to manage initialization, copying, memory reduction, and destruction of Protocol Type instances that comply with the Protocol.

  • The Value Witness Table can also be split into %relative_vwtable and % Absolute_vwtable in the SIL.

  • Value Witness Table and Protocol The Witness Table manages memory management (initialization, copy, and destruction) and method calls of Protocol Type instances.

Let’s take this a step further with a concrete example:

// Protocol Types
// The Existential Container in action
func drawACopy(local: Drawable) {local.draw()}let val :Drawable = Point()
drawACopy(val)
Copy the code

In the Swift compiler, the pseudo-code implemented through An Interface Container is as follows:

// Protocol Types
// The Existential Container in action
func drawACopy(local :Drawable) {
 local.draw()
}
letVal :Drawable = Point() drawACopy(val) struct ExistContDrawable {var valueBuffer:(Int, Int, Int) var VWT: ValueWitnessTable var PWT: DrawableProtocolWitnessTable} / / drawACopy method to generate pseudo code func DrawACopy (val:ExistContDrawable) {// Pass an Interface container to varlocal= ExistContDrawable() // Initializes containersletVWT = val. VWT // Obtain the value Witness table, which is used to manage the life cycleletPWT = val. PWT // Obtain protocol Witness table for method dispatch local.type =type 
 local.pwt = pwt
 vwt.allocateBufferAndCopyValue(&local, val) // VWT for lifecycle management, initialize or copy pwt.draw(vwt.projectBuffer(&)local(small value is inline in the stack, large value is initialized in the heap, and the stack holds Pointers), so the method determination is also type dependent. The method is found by the current object address, through a certain displacement to find the method address. VWT. DestructAndDeallocateBuffer (temp) / / VWT lifecycle management, destroy memory}Copy the code

Protocol Type Storage attribute

Swift Class instances and attributes are stored in the heap, Struct instances are stored in the stack, and Protocol types are stored in the heap if they contain pointer attributes. Small Number is implemented inline through An Interface Container, and large numbers are stored in the heap. How do you handle Copy?

Protocol Large number Copy optimization

When a Copy condition occurs:

letALine = Line(1.0, 1.0, 1.0, 3.0)let pair = Pair(aLine, aLine)
let copy = pair
Copy the code

The valueBuffer of the new Exsitential Container points to the same value that creates a pointer reference, but what if you want to change the value? Unlike Class, Copy should not affect the value of a Struct instance.

A technique called Indirect Storage With copy-on-write is used here. Reduce heap initialization by increasing the use of memory Pointers. Reduce memory consumption. When the value needs to be changed, the reference count check is first checked. If the reference count is greater than 1, new memory is created and a new instance is created. When changes are made to the content, a new block of memory is opened, with the following pseudocode:

class LineStorage { var x1, y1, x2, y2:Double }
struct Line :Drawable {
 var storage :LineStorage
 init() { storage = LineStorage(Point(), Point()) }
 func draw() {... } mutating funcmove() {
   if! IsUniquelyReferencedNonObjc (& storage) {/ / how to exist more references, the new memory, or directly modifying storage = LineStorage (storage)} storage. start = ... }}Copy the code

The goal: the cost of using multiple Pointers to refer to the same address is much lower than the cost of creating multiple heap memory. The following comparison chart:

Protocol Type Polymorphism summary

  1. Supports Dynamic Polymorphism of Protocol Type.

  2. This is achieved using Witness Table and An Interface Container.

  3. For large numbers of copies, you can optimize them through Indirect Storage.

A Dynamic Polymorphism is a way to ask what a Static Polymorphism is. Take a look at the following example:

// Drawing a copy
protocol Drawable {
 func draw()
}
func drawACopy(local :Drawable) {
 local.draw()
}

let line = Line()
drawACopy(line)
// ...
let point = Point()
drawACopy(point)
Copy the code

In this case, we can use Generic code for further optimization.

The generic

We’ll discuss how generic attributes are stored and how generic methods are dispatched next. The difference between generics and Protocol types is that:

  • Generics support static polymorphism.
  • There is only one type per calling context. Check out the following example,fooandbarMethods are of the same type.
  • Type substitution occurs through type degradation in the call chain.

For the following example:

func foo<T:Drawable>(local :T) {
 bar(local)
}
func bar<T:Drawable>(local: T) {... }let point = Point()
foo(point)
Copy the code

Parse the calls to foo and bar:

Foo (point)-->foo<T = point >(point) Swift binds the generic T to the specific type used by the caller when the method is executed, in this case point bar(local) -->bar<T = Point>(local) // When calling the internal bar method, foo's already bound variable type Point is used. As you can see, the generic T has been degraded here and replaced by type PointCopy the code

The implementation of a generic method call is:

  • Any instances of the same type share the same implementation, that is, use the same Protocol Witness Table.
  • Use Protocol/Value Witness Table.
  • There is only one type per call context: it is not used hereExistential Container, but willProtocol/Value Witness TablePassed as an extra parameter to the caller.
  • Variable initialization and method calls are passed inVWTandPWTTo execute.

Seeing this, we don’t think generics have any faster features than Protocol Type. How can generics be faster? Further optimization can be carried out under static polymorphism, which is called specific generic optimization.

Generic specialization

  • Static polymorphism: There is only one type in the call station Swift uses the feature of only one type to perform type degradation substitution.
  • When a type is degraded, a specific type of method is produced
  • Create a method for each type of generics and you might ask, well, doesn’t the code space explode if each type produces a new method?
  • In a static polymorphismSpecific optimizationspecializationBecause it’s static polymorphism. So you can do very powerful optimizations, such as inline implementation, and further optimizations by taking context. Thus reducing the number of methods. Optimized to be more precise and specific.

Such as:

func min<T:Comparable>(x:T, y:T) -> T {
  return y < x ? y : x
}
Copy the code

Expanding from ordinary generics, we need to perform calculations on generic types, including initialization addresses, memory allocation, life cycle management, and so on, to support all types of MIN methods. In addition to operating on value, you also operate on methods. This is a very complex and huge project.

func min<T:Comparable>(x:T, y:T, FTable:FunctionTable) -> T {
  let xCopy = FTable.copy(x)
  let yCopy = FTable.copy(y)
  letM = FTable. LessThan (yCopy, xCopy)? y :x FTable.release(x) FTable.release(y)return m
}
Copy the code

When determining the Type of an input parameter, such as Int, the compiler can use generic specialization to perform Type Substitute optimization:

func min<Int>(x:Int, y:Int) -> Int {
  return y < x ? y :x
}
Copy the code

When does generic specialization specilization occur?

When using specific optimizations, callers need to do type inference, where they need to know the context of the type, such as its definition and internal method implementation. If the caller and the type are compiled separately, there is no way to implement the type inside the caller’s inference, and there is no way to use specific optimizations to ensure that the code compiles together, in this case Whole Module Optimization. Whole Module Optimization is a prerequisite for generic specialization optimization of caller and callee methods in different files.

Generics are further optimized

Further optimizations for specific generics:

// Pairs inOur program using generic types struct Pair<T :Drawable> {init(_ f:T, _ s:T) {first = f; second = s } var first:T var second:T }let pairOfLines = Pair(Line(), Line())
// ...

let pairOfPoint = Pair(Point(), Point())
Copy the code

The use of a pair of generics can be further optimized when multiple generics are used and it is certain that the generic type will not be modified at run time.

The optimization is to make the generic memory allocation specified by a pointer in-memory, with no additional heap initialization cost. Note that because storage inlining is done, the memory distribution of a generic specific type has been determined, and the in-memory inlining of a generic type cannot store different types. So again, this optimization applies only when generic types are not modified at run time, i.e. you cannot support both line and point types in the same method.

Whole Module optimization is an optimization mechanism for the Swift compiler. You can open it with -whole-module-optimization (or -wmo). This is enabled by default after XCode 8. Swift Package Manager uses Whole Module Optimization by default in release mode. Module is a collection of files.

After parsing the source file, the compiler optimizes it to generate machine code and output the object file, after which the linker combines all the object files to generate shared libraries or executable files. Whole Module Optimization can perform optimization operations such as inlining through cross-function optimization. For generics, inference optimization can be performed by obtaining specific implementations of types, inlining type degradation methods, and removing redundant methods.

Advantages of full module optimization

  • The compiler knows the implementation of all methods and can optimize for inlining and generic specialization, and remove redundant reference counting operations by counting references to all methods.
  • By knowing all non-public methods, you can eliminate them if they are not being used.

How to reduce compilation time The opposite of full module optimization is file optimization, which compiles a single file. The advantage of this is that it can be executed in parallel and there is no recompilation for unmodified files. The disadvantage is that the compiler does not know the full picture and cannot optimize in depth. Let’s take a look at how full module optimization can avoid recompiling unmodified files.

The internal running process of the compiler is divided into: syntax analysis, type checking, SIL optimization, LLVM backend processing.

Parsing and type checking are generally fast, and SIL optimizations perform important Swift specific optimizations, such as generic specialization and method inlining, which take up about a third of the total compile time. The LLVM back-end execution takes up most of the compilation time, running degraded optimizations and generating code.

After full module optimization, SIL optimization splits the module into multiple parts again, and the LLVM back end processes the split modules through multiple threads, leaving unmodified parts unprocessed. In addition to avoiding the need to modify a small part of the entire module to be executed again at the LLVM back end, using multi-threaded parallel operations also reduces processing time.

Extension: Hidden “bugs” in Swift

Swift has a problem with the method dispatch mechanism, so after design and optimization, it can produce results that are not consistent with our common understanding. This is certainly not a Bug. However, this should be explained separately to avoid the problems caused by the lack of understanding of the mechanism during development.

Message dispatch

We got an idea of method dispatch from the above illustration in conjunction with Static versus Dynamic Dispatch. There is an explanation for objective-C method dispatch.

If you are familiar with OC, you will know that OC uses the runtime mechanism to send messages using obj_msgSend. Runtime is very flexible. We can not only use swizzling for method calls, but also extend the functionality of objects through ISa-Swizzling. The application scenarios include hook and KVO.

Everyone asks when developing with Swift, can Swift use OC’s runtime and message forwarding mechanisms? The answer is yes.

Swift can mark a method with the keyword Dynamic, which tells the compiler that the method uses OC’s run-time mechanism.

Note: our common keyword @objc does not change Swift’s original method dispatch mechanism. The keyword @objc simply tells the compiler that the code is visible to the OC.

To sum up, Swift includes three dispatch methods after the extension of dynamic keyword: Static Dispatch, Table Dispatch and Message Dispatch. The following table shows how different data structures dispatch in different situations:

! [Swift dispatch method](img/Swift_Compile_Performance/Swift dispatch method.png)

If you mix these dispatch methods incorrectly during development, there may be bugs, which are analyzed below:

Sr-584 This is a case where a parent class method overloads in a subclass’s Extension and behaves differently than expected.

class Base:NSObject {
    var directProperty:String { return "This is Base" }
    var indirectProperty:String { return directProperty }
}

class Sub:Base { }

extension Sub {
    override var directProperty:String { return "This is Sub"}}Copy the code

Execute the following code, directly call no problem:

Base().directProperty // "This is Base" Sub().directProperty // "This is Sub"Copy the code

Indirect invocation results in different results than expected:

The Base (). IndirectProperty // "This is Base" Sub (). indirectProperty // expected"this is Sub", but is "This is Base" < -unexpected!Copy the code

Add the dynamic keyword to base.directProperty to get the result of “this is Sub”. Swift states in the Extension documentation that you cannot overload existing methods in Extension.

“Extensions can add new functionality to a type, but they cannot override existing functionality.”

Cannot override a non-dynamic class declaration from an extension.

The reason for this problem is that NSObject’s Extension uses Message Dispatch, whereas Initial Declaration uses Table Dispath (see Swift Dispatch Method above). Extension overloaded methods are added to Message Dispatch. The virtual table is not modified. The virtual table still contains methods of the parent class, so the parent class methods are executed. To reload methods in Extension, you need to indicate Dynamic to use Message Dispatch.

SR-103

A method implemented in an extension of a protocol that cannot be subclassed by class overloading:

protocol Greetable {
    func sayHi()
}
extension Greetable {
    func sayHi() {
        print("Hello")}} func greetings(greeter: Greetable) {greeter.sayhi ()}Copy the code

Now define a class Person that complies with the protocol. Subclass of LoudPerson:

class Person:Greetable {
}
class LoudPerson:Person {
    func sayHi() {
        print("sub")}}Copy the code

Executing the following code results in:

var sub:LoudPerson = LoudPerson()
sub.sayHi()  //sub
Copy the code

Code that does not meet expectations:

Var sub:Person = LoudPerson() sub.sayhi () //HellO <- Uses the default implementation of protocolCopy the code

Notice that the override keyword does not appear in subclass LoudPerson. LoudPerson did not successfully register Greetable in the Witness table method. Therefore, for instance declared as Person but actually LoudPerson, the compiler uses Person to search for instance. If Person does not implement the protocol method, Witness table is not generated and the sayHi method is directly invoked. The solution is to implement the protocol methods in the Base class and provide the default methods without the implementation. Or mark the base class as final to avoid inheritance.

To understand this further, use an example:

/ / Defined protocol. protocol A { func a() -> Int } extension A { func a() -> Int {return 0
    }
}

// A class doesnDon't have implement of the function. Class B: A {} class C: B {func A () -> Int {return 1}} // A class has this implement. Class D: A {func A () -> Int {return 1}} Class E: D {override func A () -> Int {return 2}} // Failure cases. (B) (a) / / 0 C () () a / / 1. (C) (as a) a () / / 0 # We thought the return 1. / / Success cases. D().a() // 1 (D() as A).a() // 1 E().a() // 2 (E() as A).a() // 2Copy the code

other

We know that Class Extension uses Static Dispatch:

class MyClass {
}
extension MyClass {
    func extensionMethod() {}} SubClass: MyClass {override funcextensionMethod() {}}Copy the code

Error: Declarations in extensions can not be overridden yet.

conclusion

  • There are three criteria that affect the performance of a program: initialization, reference Pointers, and method dispatch.

  • This paper compares the performance of two data structures: Struct and Class under different standards. Compared with OC and other languages, Swift strengthens the capability of the structure, so the performance can be effectively improved by using the structure on the premise of understanding the above performance.

  • On top of that, we also introduced classes for powerful constructs: Protocol Type and Generic. It also showed you how they support polymorphism and how they can make your program faster by using conditional generics.

The resources

  • swift memorylayout

  • witness table video

  • protocol types pdf

  • protocol and value oriented programming in UIKit apps video

  • optimizing swift performance

  • whole module optimizaiton

  • increasing performance by reducing dynamic dispatch

  • protocols generics existential container

  • protocols and generics

  • why swift is swift

  • swift method dispatch

  • swift extension

  • universal dynamic dispatch for method calls

  • compiler performance.md

  • structures and classes

Author’s brief introduction

Asian male, Meituan Dianping iOS engineer. In 2017, I joined Meituan-Dianping, responsible for the development of professional version of catering butler, and studying the principle of compiler. At present, it is actively promoting the construction of Swift componentization.

Recruitment information

Our catering ecological technology department is a place with active technical atmosphere and great talents gathering. New arrival stores grasp the real large-scale SaaS practice opportunities, multi-tenant, data, security, open platform and other all-round challenges. The business field is complex with many technical challenges, and the technology and business capabilities are rapidly improving. Most importantly, join us and you will realize your dream of truly changing the industry through code. We welcome all talents to join us, Java is preferred. If you are interested, please send your resume to [email protected]. We look forward to your coming.