How to write high-performance Swift code

Source: Internet
Author: User
Tags emit

Some of the tips in the documentation can help improve the quality of your Swift program, making your code less error-prone and more readable. Explicitly marking the final class and class protocol is two obvious examples. However, there are some tricks in the documentation that are not in line with the rules, twist, and only solve some special temporary needs than compilers or languages. Many of the recommendations in the documentation come from a variety of tradeoffs, such as runtime, byte size, code readability, and so on.

Enable optimizations

The first thing to do is to enable optimization. Swift offers three different levels of optimization:

    • -onone: This means normal development. It performs minimal optimizations and saves all debugging information.

    • -O: This means for most production code. The compiler performs aggressive optimizations that can greatly alter the type and number of commit code. Debugging information will be omitted but will still be compromised.

    • -ounchecked: This is a special optimization mode that means a particular library or application, which is exchanged for security. The compiler will remove all overflow checks and some implicit type checking. This is not commonly used, as it can cause memory security issues and integer overflows. If you examine your code carefully, it is safe for integer overflows and type conversions.

The current level of optimization that can be modified in the Xcode UI is as follows:

Entire component optimization

By default, Swift compiles each file separately. This allows Xcode to compile multiple files in parallel, very quickly. However, compiling each file separately can prevent some compiler optimizations. Swift can also compile the entire program as if it were a file, as if it were a single compilation unit to optimize the program. This mode can be activated using the command line flag-whole-module-optimization. Programs compiled in this mode will most likely take longer to compile and can run faster.

This mode can be activated through the "Whole Module optimization" In the XCode build settings.

Reduce dynamic scheduling

Swift, by default, is a very dynamic language similar to objective-c. Unlike Objective-c, Swift gives programmers the ability to eliminate and reduce this feature to provide runtime performance. This section provides several examples of language constructs that can be used for such operations.

Dynamic scheduling

Class uses the method of dynamic scheduling and the default property access. So in the following code fragment, A.aproperty, a.dosomething (), and A.dosomethingelse () are called by dynamic scheduling:

Class A {
var aproperty: [Int]
Func dosomething () {...}
Dynamic Dosomethingelse () {...}
}

Class B:a {
Override Var Aproperty {
get {...}
set {...}
}

Override Func dosomething () {...}
}

Func Usingana (a:a) {
A.dosomething ()
A.aproperty = ...
}

In Swift, dynamic scheduling is called indirectly through a vtable[1] (virtual function table). If a dynamic keyword is used to declare, Swift will send the call instead by calling the OBJECTIVE-C notification. In both cases, this situation is slower than a direct function call because it prevents many compiler optimizations for program overhead outside of the indirect call itself [2]. In performance-critical code, people often want to limit this dynamic behavior.

Recommendation: Use "final" when you know that the declaration does not need to be rewritten.

The final keyword is a restriction in a class, a method, or a property declaration, so that such a declaration cannot be overridden. This means that the compiler can call a direct function call instead of an indirect call. For example, the following c.array1 and D.array1 will be accessed directly [3]. In contrast, D.array2 will be accessed through a virtual function table:

Final class C {
No declarations in class ' C ' can overridden.
var array1: [Int]
Func dosomething () {...}
}

Class D {
Final Var array1 [Int]//' array1 ' cannot is overridden by a computed property.
var array2: [Int]//' array2 ' *can* is overridden by a computed property.
}

Func USINGC (C:C) {
C.array1[i] = ...//Can directly access C.array without going through dynamic dispatch.
C.dosomething () = ...//Can directly call c.dosomething without going through virtual dispatch.
}

Func Usingd (D:D) {
D.array1[i] = ...//Can directly access d.array1 without going through dynamic dispatch.
D.array2[i] = ...//would access d.array2 through dynamic dispatch.
}

Recommendation: When declaring something that does not need to be accessed outside the file, use "private"

Using the Private keyword on a declaration limits the visibility of the file that is declared. This gives the editor the ability to identify all other potential overwrite declarations. Thus, because there is no such declaration, the compiler can automatically infer the final keyword, and thus remove the indirect call and property access to the aspect. For example, in the following e.dosomething () and F.myprivatevar, it is possible to have direct access, assuming that in the same file, E, F does not have any overriding declarations:

Private class E {
Func dosomething () {...}
}

Class F {
private Var Myprivatevar:int
}

Func Usinge (e:e) {
E.dosomething ()//There is no sub class in the file, that declares this class.
The compiler can remove virtual calls to DoSomething ()
and directly call A ' s dosomething method.
}

Func USINGF (f:f), Int {
Return F.myprivatevar
}

Efficient use of container types

The Universal container Array and Dictionary are an important feature that is provided by the Swift Standard library. This section describes how to use these types in a high-performance way.

Recommendation: Use value types in arrays

In Swift, types can be divided into two distinct categories: value types (structs, enumerations, tuples), and reference types (classes). A key distinction is that Nsarray cannot contain value types. Therefore, when using a value type, the optimizer does not need to handle the support for Nsarray, thus eliminating most of the consumption on the array.

In addition, if a value type recursively contains a reference type, the value type simply needs to reference the counter, compared to the reference type. If you use a value type that does not have a reference type, you can avoid additional overhead, freeing up traffic within the array.

Don ' t use a class here.
struct Phonebookentry {
var name:string
var number: [Int]
}

var a: [Phonebookentry]

Remember to make a tradeoff between using large value types and using reference types. In some cases, copying and moving large-value type data consumes more than removing bridging and holding/freeing.

Recommendation: Use Contiguousarray to store reference types when Nsarray bridging is not necessary. If you need an array of reference types, and the array does not need to be bridged to Nsarray, use Contiguousarray instead of array:

Class C {...}


var a:contiguousarray<c> = [C (...), C (...), ..., C (...)]

Recommendation: Use appropriate changes instead of object assignments.

All standard library containers in Swift use COW (copy-on-write) to perform copies instead of instant copies. In many cases, this allows the compiler to omit unnecessary copies by holding containers rather than deep copies. If the container's reference count is greater than 1 and the container is changed, the underlying container is copied. For example: In this case, when D is assigned to C without copying, but when D undergoes a structural change of 2, then D is copied, then 2 is appended to B:

var c: [Int] = [...]
var d = c//No copy would occur here.
D.append (2)//A copy *does* occur here.

Sometimes COW can cause additional copies if the user is not careful. For example, in a function, an attempt is made to perform a modification through an object assignment. In Swift, all parameters are copied in a copy, for example, a parameter is held before the call point, and then released at the end of the called function. In other words, a function like this:

Func Append_one (A: [int]), [int] {
A.append (1)
Return a
}

var a = [1, 2, 3]
A = Append_one (a)

Although the version of a does not change due to distribution, it is not used after append_one, but a may be copied. This can be done by using the InOut parameter to avoid this problem:

Func Append_one_in_place (InOut A: [Int]) {
A.append (1)
}

var a = [1, 2, 3]
Append_one_in_place (&a)

Action not checked

Swift resolves an integer overflow problem by checking for overflow when performing a normal calculation. These checks are inappropriate in efficient code that has determined that no memory security issues will occur.

Recommendation: Use an unchecked integer calculation when you know exactly what is not going to happen.

In code with high performance requirements, you can ignore overflow checking if you know that your code is safe.

A: [Int]
B: [Int]
C: [Int]

Precondition:for all A[i], B[i]: A[i] + b[i] does not overflow!
For i in 0 ... n {
C[i] = A[i] &+ b[i]
}

Generic type

Swift provides a very powerful abstraction mechanism through the use of generic types. The Swift compiler emits a specific block of code that can execute myswiftfunc<t> on any T. The generated code requires a function pointer table and a box containing T as an additional parameter. The different behavior between Myswiftfunc<int> and Myswiftfunc<string> is illustrated by passing different function pointer tables and the abstract size provided by the box. An example of a generic type:

Class Myswiftfunc<t> {...}

Myswiftfunc<int> X//would emit code that works with Int ...
Myswiftfunc<string> Y//... as well as String.

When the optimizer is enabled, the Swift compiler looks for a call to this code and tries to confirm the type used specifically in the call (for example, non-generic type). If the definition of a generic function is visible to the optimizer and knows the specific type, the Swift compiler will generate a special generic function with a special type. This process of calling this particular function avoids the consumption of the associated generics. Some examples of generics:

Class Mystack<t> {
Func push (Element:t) {...}
Func pop ()-T {...}
}

Func Myalgorithm (A: [T], Length:int) {...}

The compiler can specialize code of Mystack[int]
var stackofints:mystack[int]
Use stack of ints.
For I in ... {
Stack.push (...)
Stack.pop (...)
}

var arrayofints: [Int]
The compiler can emit a specialized version of ' Myalgorithm ' targeted for
[Int] ' types.
Myalgorithm (arrayofints, Arrayofints.length)

Recommendation: Place a generic declaration in a file that uses it

The optimizer can perform specialization only if the generic declaration is visible in the current module. This only occurs in the same file as code that uses generics and declares generics. Note that the standard library is an exception. Generics declared in a standard library are visible to all modules and can be specialized.

Recommendation: Allow the compiler to specialize

The compiler can specialize a generic code only if the call location and the called function are in the same compilation unit. We can use a technique to let the compiler optimize the tuned function, which is to perform type checking in the compilation unit where the function is being tuned. The code that performs the type check re-distributes the call to the generic function-but this time it carries the type information. In the following code, we insert a type check in the function Play_a_game, which makes the code hundreds of times times faster.

Framework.swift:

Protocol Pingable {func ping ()-Self}
Protocol Playable {func play ()}

Extension Int:pingable {
Func ping (), Int {return self + 1}
}

Class Game<t:pingable>: Playable {
var t:t

Init (_ v:t) {T = v}

Func Play () {
For _ in 0...100_000_000 {t = t.ping ()}
}
}

Func Play_a_game (game:playable) {
This check allows the optimizer to specialize the
Generic call ' Play '
If let z = game as? game<int> {
Z.play ()
} else {
Game.play ()
}
}

-------------->8

Application.swift:

Play_a_game (Game (10))

The overhead of a large value object

In the swift language, value types hold a copy that is unique to their data. There are many advantages to using value types, such as the value type has a separate state. When we copy a value type (equivalent to a copy, initialize a parameter pass, etc.), the program creates a copy of the value type. For large value types, this copy is time-consuming and may affect the performance of the program.

Let's take a look at the code below. This code uses a node of value type to define a tree, the nodes of the tree contain other nodes of the protocol type, and the computer graphics scene is often made up of entities and morphological changes that can be represented by value types, so this example is very practical

Protocol P {}
struct Node:p {
var left, Right:p?
}

struct Tree {
var node:p?
Init () {...}
}

When a tree is copied (parameter passing, initializing, or assigning) the entire tree needs to be copied. This is a costly operation that requires a lot of malloc/free calls and a lot of reference counting operations.

However, we do not relate whether the value is copied, as long as these values still exist in memory.

Use COW for large value types (copy-on-write, copy on write and array somewhat similar)

The method of reducing the data overhead of copying large value types takes the write-time copy behavior (actual copy work occurs when the object changes). The simplest implementation of a copy-on-write scenario uses an existing data structure, such as an array, that already exists for write-time replication. Swift's data is a value type, but does not replicate every time the array is passed as a parameter because it has a copy-on-write attribute.

In our tree example, we reduce the cost of copying by wrapping the contents of the tree into an array. This simple change has a huge effect on the performance of our tree data structures, and the cost of passing an array as a parameter changes from O (n) to O (1).

struct Tree:p {
var node: [P?]
Init () {
node = [Thing]
}
}

But there are two obvious shortcomings in using arrays to implement the COW mechanism, and the first problem is that methods such as append and count, which are exposed by arrays, have no effect in the context of value wrapping, which makes encapsulation of reference types tricky. Maybe we can solve this problem by creating a encapsulated structure and hiding these unused APIs, but we can't solve the second problem. The second problem is that there is code inside the array that guarantees program security and code that interacts with OC. Swift checks to see if the following table is in the bounds of the array and needs to be checked to see if the storage space needs to be expanded when the value is saved. These run-time checks can slow down.

An alternative scenario is to implement a specialized data structure that uses the COW mechanism instead of encapsulating the array as a value. An example of building such a data structure is as follows:

Final class Ref<t> {
var val:t
Init (_ V:t) {val = v}
}

struct Box<t> {
var ref:ref<t>
Init (_ X:t) {ref = ref (X)}

var value:t {
get {return Ref.val}
set {
if (!ISUNIQUELYREFERENCEDNONOBJC (&ref)) {
ref = Ref (NewValue)
Return
}
Ref.val = newvalue
}
}
}

Type box can replace the array in the previous example

Unsafe code

The classes in Swift language are memory-managed using reference counting. The Swift compiler inserts code that increments the reference count each time the object is accessed. For example, consider an example of a linked list that iterates through the use of a class implementation. Traversing a list is done by moving the reference to the next node of the linked list: Elem = Elem.next, each time the reference is moved, Swift increments the reference count of the next object and reduces the reference count of the previous object, which is expensive but cannot be avoided as long as the Swift class is used

Final class Node {
var next:node?
var data:int
...
}

Recommendation: Use unmanaged references to avoid load on reference counts

In the most efficient code, you can choose to use unmanaged references. unmanaged<t> struct allows developers to turn off reference counting for special references

var ref:unmanaged<node> = unmanaged.passunretained (Head)

While let next = Ref.takeunretainedvalue (). Next {
...
Ref = unmanaged.passunretained (Next)
}

Agreement

Recommendation: Marking a class-implemented protocol as a class protocol

Swift can specify that the protocol can only be implemented by the class. One of the benefits that a tag protocol can only be implemented by a class is that the compiler can optimize the program based on this. For example, the ARC memory management system can easily hold (increase the object's reference count) if it knows it is processing a class object. If the compiler does not know this, it must assume that the struct can also implement the Protocol, then it must be ready to hold or release different data structures, which can be costly.

If a restriction can only be implemented by a class, mark the Protocol as a class protocol for better performance

Protocol Pingable:class {func ping () Int}

Footnote

The "1" virtual method table or vtable is a type constraint table that contains a type method address that is referenced by an instance. When you are distributing dynamically, first look up the table from the object and then look up the methods in the table
"2" This is because the compiler does not know that the specific method to be called
"3" For example, loading a field directly from a class or calling a method directly
"4" explains what COW is
"5" In certain cases the optimizer can remove retain,release by inline and ARC optimization techniques because no replication is caused

reprinted from:Oschina

original link:http://www.oschina.net/translate/swift-optimizationtips

How to write high-performance Swift code

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.