Swift Advanced memory model and method scheduling

Source: Internet
Author: User

Objective

Apple launched the Swift3.0 this year, more than 2.3来 said, 3.0 is a major upgrade. On this update, you can find it here, the main thing is to improve the performance of Swift, optimized the design (naming) specification of Swift API.

The previous period of time on a previously written project Imagemasktransition did a simple migration, first guaranteed to run normally in 3.0, only in less than 30 minutes. Overall, this migration is still very easy. However, one thing to note: 3.0 API design specifications than 2.3 have a qualitative change, it is recommended to do the migration of developers first look at the WWDC of the Swift API design guidelines. There is time in the back, and I might as well summarize it.

Memory allocation

By viewing the source language distribution of Swift on GitHub

Can see

    • The swift language is written in C + +
    • Swift's core library was written by Swift itself.

For C + +, the memory interval is as follows

    • Heap Area
    • Stack area
    • Code Area
    • Global static Zone

Swift's memory range is similar to C + +. There are also areas for storing code and global variables, both of which are simpler, and this article focuses more on the following two memory ranges.

    • Stack (stack), a temporary variable that stores a value type, a function call stack, a reference type, a temporary variable pointer
    • Heap, which stores instances of reference types

Stack

The cost of allocating and freeing memory on the stack is minimal, because the stack is a simple data structure. By moving the pointer at the top of the stack, you can create and release the memory. However, the memory created on the stack is limited and is often determined at compile time.

A very simple example: when a recursive function falls into a dead loop, the last function call stack overflows.

For example, a temporary variable that does not have a reference type struct is stored on the stack.

struct point{

var x:double//8 Bytes

var y:double//8 bytes

}

Let size = Memorylayout<point>.size

Print (size)//16

Let point1 = Point (x:5.0,y:5.0)

Let instancesize = Memorylayout<point>.size (ofvalue:point1)

Print (instancesize)//16

    • 9

So, this memory structure

Tips: Each of the cells in the diagram is a word size, on a 64-bit processor, is 8 bytes

Heap

On the heap, you can dynamically allocate memory on the heap, and each time the memory is allocated on the stack, you need to find a location on the heap that can provide the appropriate size, and then return to the corresponding location, marking the size of the specified location memory is occupied.

The heap can dynamically allocate the required size of memory, but because each time to find, and to take into account the thread security between multithreading, so performance is much lower than the stack.

For example, we changed the above to class pointclass{.

var x:double = 0.0

var y:double = 0.0

}

Let Size2 = Memorylayout<pointclass>.size

Print (SIZE2)//8

Let Point2 = Point (x:5.0,y:5.0)

Let instancesize = Memorylayout<point>.size (Ofvalue:point2)

Print (instancesize)//8

Memory structure at this time

Tips: Each of the cells in the diagram is a word size, on a 64-bit processor, is 8 bytes

Memory Alignment (Memory aligned)

Like C/c++/oc, Swift also has the concept of memory alignment. To give an example of a visual

We define a two struct like this

struct s{

var X:int64

var y:int32

}

struct sreverse{

var y:int32

var X:int64

}

Then, use Memorylayout to get the size of two structures

Let ssize = Memorylayout<s>.size//12

Let sreversesize = Memorylayout<sreverse>.size//16

As you can see, only the order of the declarations in the struct is adjusted, and the memory size is changed, which is the memory alignment.

Let's take a look at the memory-aligned memory space distribution:

The reason for memory alignment is that

There are many advantages to CPUword324648 memory alignment

    • Guaranteed access to a member in a transition, the speed of access is increased, and the atomicity of one operation is guaranteed. In addition to these, memory alignment has a lot of advantages, you can see this so answer

Auto Reference Count (ARC)

When it comes to arc, we have to talk about two basic types of Swift:

    • Value types, where values are copied when assigned
    • Reference type, where the reference (pointer) copy is only made when the value is assigned

For example, the following code

In struct point{//swift, a struct is a value type

var x,y:double

}

Class Person{//swift, class is a reference type

var name:string

var age:int

Init (name:string,age:int) {

Self.name = Name

Self.age = Age

}

}

var point1 = point (x:10.0, y:10.0)

var Point2 = Point1

point2.x = 9.0

Print (point1.x)//10.0

var person1 = person (name: "Leo", age:24)

var Person2 = Person1

Person2.age = 25

Print (person1.age)//9.0

    • Let's take a look at the corresponding memory usage value type has many advantages, the main advantage is two-thread safety, each time is to obtain a copy, does not exist at the same time to modify a piece of memory-immutable state, using value types, do not need to consider the code elsewhere may have an impact on the current code. There will be no side effect. ARC is relative to the reference type. > Arc is a memory management mechanism. When the reference count (reference count) of an object of a reference type is 0, the object is freed. We use Xcode 8 and iOS development to visually view the change in reference count for the next value type variable. Create a new iOS single page project, language Select Swift, then write the following code! [Write a description of the picture here] (https://img-blog.csdn.net/20161113111539024) Then, when the breakpoint stops at 24 lines, the reference count of the person is as follows here, the bottom of ' thread_2673 ' is the main line heap person object Holding, iOS system is added by default. So, ' var Leo = person (name: "Leo", age:25) ' This line is, exactly, a reference count plus one, not a reference count of one. Of course, the automatic creation of these systems will be automatically destroyed, we do not need to consider. As you can see, the only reference for person is from ' vm:stack thread ', which is the stack. Because of the existence of reference counts, class needs to allocate more than one word to store the reference count on the heap:

When the code on the stack finishes executing, the stack breaks the reference to the person, and the reference count is reduced by one, and the automatically created reference is broken off. At this point, the person's reference count is 0, and the memory is freed.

Method Scheduling (method Dispatch)

Swift's method scheduling is divided into two types

    • Statically dispatching static dispatch. When the static schedule is executed, it jumps directly to the implementation of the method, and static scheduling can be optimized for inline and other compile periods.
    • Dynamically dispatch dynamic dispatch. Dynamic scheduling at execution time, according to the runtime (runtime), in the form of table, the method to find the execution of the body, and then execute. Dynamic scheduling also has no way to perform compile-time optimizations like static.

Struct

For structs, method scheduling is static.

struct point{

var x:double//8 Bytes

var y:double//8 bytes

Func Draw () {

Print ("Draw point at\ (x, y)")

}

}

Let point1 = Point (x:5.0, y:5.0)

Point1.draw ()

Print (memorylayout<point>.size)//16

    • 1

As you can see, because it is a static Dispatch, the execution of the method can be known at compile time. Therefore, there is no need for additional space at runtime to store the method information. After compiling, the invocation of the method is directly the variable address of the incoming, there is a code area.

If compiler optimizations are turned on, then the above code is optimized to inline,

Let point1 = Point (x:5.0, y:5.0)

Print ("Draw point at\ (POINT1.X,POINT1.Y)")

Print (memorylayout<point>.size)//16

    • 1
    • 2
    • 3

Class

Class is dynamic dispatch, so after adding a method, the class itself is still assigned a word on the stack. On the heap, an additional word is required to store the class's type information, which is stored in the class's type information, virtual table (v-table). According to V-table, we can find the corresponding method of executing the body.

Class point{

var x:double//8 Bytes

var y:double//8 bytes

Init (x:double,y:double) {

self.x = X

Self.y = y

}

Func Draw () {

Print ("Draw point at\ (x, y)")

}

}

Let point1 = Point (x:5.0, y:5.0)

Point1.draw ()

Print (memorylayout<point>.size)//8

    • 14

Inherited

Because class Entities store additional type information, inheritance is easy to understand. Subclasses only need to store the type information of the subclass.

For example

Class point{

var x:double//8 Bytes

var y:double//8 bytes

Init (x:double,y:double) {

self.x = X

Self.y = y

}

Func Draw () {

Print ("Draw point at\ (x, y)")

}

}

Class point3d:point{

var z:double//8 Bytes

Init (x:double,y:double,z:double) {

Self.z = Z

Super.init (x:x, Y:y)

}

Override Func Draw () {

Print ("Draw point at\ (x, y, z)")

}

}

Let point1 = Point (x:5.0, y:5.0)

Let Point2 = Point3D (x:1.0, y:2.0, z:3.0)

Let points = [Point1,point2]

Points.foreach {(P) in

P.draw ()

}

Draw Point at (5.0, 5.0)

Draw Point at (1.0, 2.0, 3.0)

    • 1

Agreement

Let's first look at a piece of code

struct point:drawable{

var x:double//8 Bytes

var y:double//8 bytes

Func Draw () {

Print ("Draw point at\ (x, y)")

}

}

struct line:drawable{

var x1:double//8 Bytes

var y1:double//8 bytes

var x2:double//8 Bytes

var y2:double//8 bytes

Func Draw () {

Print ("Draw line from \ (x1,y1) to \ (x2,y2)")

}

}

Let point = Point (x:1.0, y:2.0)

Let Memoryaspoint = Memorylayout<point>.size (ofvalue:point)

Let memoryofdrawable = Memorylayout<drawable>.size (ofvalue:point)

Print (Memoryaspoint)

Print (memoryofdrawable)

Let line = line (x1:1.0, y1:1.0, x2:2.0, y2:2.0)

Let Memoryasline = Memorylayout<line>.size (ofvalue:line)

Let MemoryOfDrawable2 = Memorylayout<drawable>.size (ofvalue:line)

Print (Memoryasline)

Print (MemoryOfDrawable2)

You can see that the output

//point as Point

//point as Drawable

//line as Line

//line as Drawable

16 and 32 are not difficult to understand, point contains two double properties, line contains four double properties. The corresponding number of bytes is also true. So, what's going on with two of 40? And, for Point, 40-16=24, 24 more bytes. For line, only 40-32 = 8 bytes. This is because Swift uses the following memory model for the protocol type-existential Container.

The existential container consists of the following three sections:

    • Top three Word:value buffer. Used to store inline values, and if the number of Word is greater than 3, a pointer is used to allocate memory on the heap that corresponds to the required size.
    • Fourth Word:value Witness Table (VWT). Each type corresponds to a table that stores the operation functions of creating, releasing, and copying values.
    • Fifth Word:protocol Witness Table (PWT), a function to store the protocol.

So, the memory structure diagram, as follows

[Point]

[Line]

Fan type

The paradigm allows the code to support static polymorphism. Like what:

Func drawacopy<t:drawable> (local:t) {

Local.draw ()

}

Drawacopy (Point (...))

Drawacopy (Line (...))

So, how do you call methods and store values when you use the paradigm?

[Fan Type]

The model does not use existential Container, but the principle is similar.

    • The VWT and PWT are used as stealth parameters and are passed into the paradigm method.
    • Temporary variables are still stored in the logical storage of valuebuffer-3 word, and if the size of the stored data exceeds 3 Word, a memory store is opened on the heap.

Compiler optimizations for generics

1. Synthesis of specific methods for each species

Like what

Func drawacopy<t:drawable> (local:t) {

Local.draw ()

}

After compiling, there will actually be two methods

Func Drawacopyofaline (local:line) {

Local.draw ()

}

Func Drawacopyofapoint (local:point) {

Local.draw ()

}

And then

Drawacopy (Local:point (x:1.0, y:1.0))

    • 1

will be compiled into

Func Drawacopyofapoint (Local:point (x:1.0, y:1.0))

    • 1

Swift's compiler optimizations will do more, though the code is much more, but the compiler also compresses the code. So, in fact, it doesn't have any effect on the size of the binary package.

Resources

    • WWDC 2016-understanding Swift Performance
    • WWDC 2015-optimizing Swift Performance
    • Does Swift Guarantee the storage order of fields in classes and structs?

53147910

Swift Advanced memory model and method scheduling

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.