内联函数(Inline Function):
如果开启了编译器优化,编译器会自动将某些函数变成内联函数(将函数调用展开成函数体),我们可以看到 Release 模式默认开启优化,并且是按照速度去优化:

比如我们有一个函数,那么一旦调用 test() 这个函数,系统就会为这个函数分配栈空间,并且在栈空间进行分配局部变量的操作,函数执行完之后会对函数栈空间进行回收:
func test() {
print("test")
}
test()
我们思考一下,test() 这个函数里面的代码非常少,仅仅做一件事情打印,那么我们直接把 test() 函数里这行代码拿出来,这样性能不就更好吗?其实内联做的操作就是这样,将函数调用展开成函数体代码,这样就减少了函数的调用开销,不必再开辟回收函数的栈空间了。展开后代码如下:
print("test")
接下来我们运行看一下汇编代码,首先在第 14 行代码打一个断点,然后打开显示反汇编选项(Debug -> Debug Workflow -> Always show Disassembly):

1、首先编译器未开启优化的情况:

TestSwift`main:
0x100003e40 <+0>: pushq %rbp
0x100003e41 <+1>: movq %rsp, %rbp
0x100003e44 <+4>: subq $0x10, %rsp
0x100003e48 <+8>: movl %edi, -0x4(%rbp)
0x100003e4b <+11>: movq %rsi, -0x10(%rbp)
-> 0x100003e4f <+15>: callq 0x100003e60 ; TestSwift.test() -> () at main.swift:10
0x100003e54 <+20>: xorl %eax, %eax
0x100003e56 <+22>: addq $0x10, %rsp
0x100003e5a <+26>: popq %rbp
0x100003e5b <+27>: retq
我们发现有一个 callq 0x100003e60 的操作,这句汇编代码的意思就是调用 test() 函数,所以未开启优化的情况下没有进行内联操作。
2、然后我们打开编译器优化选项:

我们在 test() 函数调用的地方设置断点,然后运行,我们发现 test() 函数没有执行,但是 “test” 字符串被打印出来了:

然后我们把断点设置到第 11 行,运行查看汇编代码

TestSwift`main:
0x100003e00 <+0>: pushq %rbp
0x100003e01 <+1>: movq %rsp, %rbp
0x100003e04 <+4>: pushq %rbx
0x100003e05 <+5>: pushq %rax
0x100003e06 <+6>: leaq 0x421b(%rip), %rdi ; demangling cache variable for type metadata for Swift._ContiguousArrayStorage<Any>
0x100003e0d <+13>: callq 0x100003f10 ; __swift_instantiateConcreteTypeFromMangledName at <compiler-generated>
0x100003e12 <+18>: movl $0x40, %esi
0x100003e17 <+23>: movl $0x7, %edx
0x100003e1c <+28>: movq %rax, %rdi
0x100003e1f <+31>: callq 0x100003f42 ; symbol stub for: swift_allocObject
0x100003e24 <+36>: movq %rax, %rbx
0x100003e27 <+39>: movaps 0x162(%rip), %xmm0
0x100003e2e <+46>: movups %xmm0, 0x10(%rax)
-> 0x100003e32 <+50>: movq 0x1c7(%rip), %rax ; (void *)0x00007fff80be2d98: type metadata for Swift.String
0x100003e39 <+57>: movq %rax, 0x38(%rbx)
0x100003e3d <+61>: movq $0x74736574, 0x20(%rbx) ; imm = 0x74736574
0x100003e45 <+69>: movabsq $-0x1c00000000000000, %rax ; imm = 0xE400000000000000
0x100003e4f <+79>: movq %rax, 0x28(%rbx)
0x100003e53 <+83>: movabsq $-0x1f00000000000000, %rdx ; imm = 0xE100000000000000
0x100003e5d <+93>: movl $0x20, %esi
0x100003e62 <+98>: movl $0xa, %ecx
0x100003e67 <+103>: movq %rbx, %rdi
0x100003e6a <+106>: movq %rdx, %r8
0x100003e6d <+109>: callq 0x100003f3c ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
0x100003e72 <+114>: movq %rbx, %rdi
0x100003e75 <+117>: callq 0x100003f4e ; symbol stub for: swift_release
0x100003e7a <+122>: xorl %eax, %eax
0x100003e7c <+124>: addq $0x8, %rsp
0x100003e80 <+128>: popq %rbx
0x100003e81 <+129>: popq %rbp
0x100003e82 <+130>: retq
我们看汇编可以发现,print(“test”)代码被直接放到 main 函数当中,也就是编译器帮我们做了内联操作。
但是有一些函数是不会被内联:
1、函数体比较长的函数不会被内联(如果函数体比较长,函数被调用次数也非常多,进行内联操作生成的汇编代码会非常多,也就是机器码会变多,最终代码体积变大安装包变大);
2、递归调用不会被内联;
3、包含动态派发(类似Oc动态绑定)的函数不会被内联;
网友评论