It has taken more than six years since Apple released Swift language in 2014. Swift has been released to version 5.3, ABI Stability has been established in version 5.0, and Module stability has been established in version 5.2. Both the language and the base library are increasingly stable. At present, large domestic and foreign companies are also actively embracing Swift camp.

Most companies choose Swift to develop iOS applications. The main reason is that Swift has faster operation efficiency, safer type detection and more features of modern languages to improve development efficiency compared with Objc. This series of advantages make Swift language more and more hot.

Most people know that Swift is more efficient than Objc, but they don’t know why. Here we discuss the reasons for Swift’s efficiency in the Swift compiler layer.

More efficient data types

Before we start talking about Swift data types, let’s discuss Swift’s function distribution mechanism;

Static dispatch, Dynamic Dispatch, message Dispatch (static Dispatch, Dynamic Dispatch, Message Dispatch)

Dynamic dispatch: Dynamic dispatch is when it is not clear at compile time which method should be called and the method can only be called at run time.

Static dispatch: Is the dispatch of a calling method that is determined at compile time.

In addition to the above two methods, Swift also uses Objc’s message dispatch mechanism. Objc uses the runtime obj_MSgSend for message distribution, so some of Objc’s dynamic features can be restricted to Swift.

Static dispatch is faster than dynamic dispatch, and static dispatch will carry out some optimization, such as inlining, reduce the function addressing and memory address offset calculation and a series of operations, so that the execution of the function is faster, higher performance.

Data type (struct/class)

As we all know, memory allocation can be divided into Heap and Stack. Because stack area memory is continuous, memory allocation and destruction are performed by loading and unloading operations, which are faster than the heap area. The heap stores advanced data types, and unused memory is searched for during data initialization and cleared from memory upon destruction, so the heap’s data stores are not necessarily contiguous.

Classes and structs are different in memory allocation. Basic data types and structs are allocated on the stack by default, while advanced data types such as classes are stored in the heap. The heap is not thread-safe, and locks are required for frequent data reads and writes.

The structure is a Value Type. When the Value Type is assigned to a variable or constant, or passed to a function, it is a copy of the Value.

Such as:

struct Resolution {
 var width = 0
 var height = 0
}
 
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
 
print("cinema is now (cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
 
print("hd is still (hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"
Copy the code

From this example, we can clearly see that when HD is assigned to cinema, the value stored in HD is copied to cinema. Therefore, when assigning width property to cinema, the property value in HD will not be changed, as shown in the following figure:

In addition to storing attributes more securely and efficiently, the distribution of functions is also more efficient. Because of the structure can not be inherited, that is, the type of structure is decorated final, according to our description of dynamic and static begin, so its internal function should be distributed belongs to static, define the function at compile time, the implementation of the way, the function call is optimized by means of inline (inline) and its memory continuously, The function addressing and memory address offset calculation are reduced, and the operation is more efficient than dynamic distribution.

Protocol Type

Polymorphism is one of the main features of object-oriented, in the structure can not inherit or reference language polymorphism, Swift introduced protocol, through the protocol to achieve the structure of polymorphic characteristics, which is also the core of protocol oriented programming swift.

For each class, a pointer to the virtual table is created. This pointer points to a v-table, or virtual table, that holds an array of Pointers for the class. Subclasses that have inherited relationships display the virtual table Pointers in the order of inheritance (C++ allows multiple inheritance). Class methods are distributed according to the function pointer in the V-table.

Structs have no inheritance, that is, structs have no V-table for function distribution. To implement this feature, the Protocol Witness Table has been added to the protocol implementation of the structure to manage method distribution of protocol types.

The build process

Above, some optimization of swift data structure is introduced. In addition to data structure optimization, SWIFT also carries out a lot of optimization in the compilation process, among which the most core optimization is the introduction of SIL in the compilation process

SIL, Swift Intermediate Language, is an Intermediate Language designed to optimize the Swift compilation process. It contains the following features:

  1. A set of high-level optimization safeguards to provide a predictable baseline of runtime and diagnostic behavior;
  2. Mandatory requirements for swift language data flow analysis and diagnosis of problems that do not meet mandatory requirements. For example, variables and structures must be explicitly initialized, code reachability (method return detection), switch coverage;
  3. Ensure high-level optimization. Contains retain/release optimizations, devirtualization of dynamic methods, closure inlining, memory initialization enhancement, and generic method instantiation.
  4. Stable allocation format that can be used to allocate “fragile” inline, optimizing the generics of Swift library components to binary.

Clang compilation process

The Clang compilation process has the following disadvantages:

  1. Wide abstraction gap between source and LLVM IR;
  2. IR isn’t suitable for source-level analysis;
  3. CFG lacks fidelity;
  4. CFG is off the hot path;
  5. Duplicated effort in CFG and IR lowering occurs.

Due to these shortcomings, the Swift language development team implemented a number of optimizations during development, the most critical of which was the introduction of SIL.

Swift Compilation process

Swift as a high-level and secure language has the following characteristics:

High-level language

  • Move more of the language into code
  • Support for Protocol-based Generics

Security language

  • Full data flow checking: Uninitialized variables, function return processing checks, these items will generate corresponding compilation errors if the check fails (Uninitialized vars, unreachable code should be compiler errors)
  • Bounds and overflflow checks

Swift compilation process:

Swift source code to IR process:

The introduction of SIL into the Swift compilation process has several advantages:

  1. Fully represents program semantics.
  2. Designed for both code generation and analysis;
  3. Sits on the hot path of the compiler pipeline.
  4. Bridge the abtraction gap between source and LLVM bridge the abtraction gap between source and LLVM.

The Swift compiler flow

The Swift compiler, as a high-level compiler, has the following strict transfer flow structure.

The flow of the Swift compiler is as follows:

  • Parse: Syntax analysis components form the AST from Swift source code

  • The semantic analysis component type checks the AST and annotates it with type information.

  • SILGen components form “raw “SIL from the AST

  • A series of in

    raw

    Run on SIL, used to determine optimization and diagnostic eligibility, embedding language-specific diagnostics for nonconforming code. These operations must be performed, even under the -onone option. After producing

    Formal (canonical)

    SIL.

  • In general, it is optional to run SIL optimization on a formal SIL, which can improve the performance of the resulting executable. This can be controlled by optimization level and is not performed in -onone mode.

  • IRGen will downgrade the official SIL to LLVM IR.

  • The LLVM back end provides LLVM optimization, executes the LLVM code generator and generates binaries.

In the above process, SIL implemented a series of optimizations for the Swift compilation process to ensure both safe and efficient code execution.

At the end

Above from Swift language design data structure and compilation process and other aspects of a simple analysis, there are many details are not particularly clear in the article, if you are interested in more information, you can refer to the following information.

Original text: xie. Infoq. Cn/article / 322…