page contents

Swift 性能优化分析

自从2014年Apple发布Swift语言以来,历时六年多,Swift已经发布到5.3版本,在5.0版本已经ABI stability,5.2版本也已经module stability,不管是语言还是基础库都日趋稳定,目前国内外大厂也都积极拥抱Swift阵营。

image


自从2014年Apple发布Swift语言以来,历时六年多,Swift已经发布到5.3版本,在5.0版本已经ABI stability,5.2版本也已经module stability,不管是语言还是基础库都日趋稳定,目前国内外大厂也都积极拥抱Swift阵营。

绝大多数公司选择Swift语言开发iOS应用,主要原因是因为Swift相比Objc有更快的运行效率,更加安全的类型检测,更多现代语言的特性提升开发效率;这一系列的优点使Swift语言的热度越来越高。

大多数人知道Swift语言相比于Objc语言运行效率更高,但是却不知道为什么效率更高,在这里我们Swift编译层探讨一下Swift语言高效的原因。


更加高效的数据类型

在开始讨论Swift数据类型之前,我们先讨论一下Swift的函数派发机制;

静态派发、动态派发、消息派发(static dispatch、dynamic dispatch、message dispatch)

动态派发(dynamic dispatch): 动态派发是指编译期无法确定应该调用哪个方法,需要在运行时才能确定方法的调用。

静态派发(static dispatch):是在编译期就能确定的调用方法的派发方式。

除了上面两种方式之外,在Swift里面还会使用Objc的消息派发(message dispatch))机制;Objc采用了运行时采用obj_msgsend进行消息派发,所以Objc的一些动态特性在Swift里面也可以被限制的使用。

静态派发相比于动态派发更快,而且静态派发还会进行内联等一些优化,减少函数的寻址及内存地址的偏移计算等一系列操作,使函数的执行速度更快,性能更高。


数据类型(struct/class)

我们都知道,内存分配可以分为堆区(Heap)和栈区(Stack)。由于栈区内存是连续的,内存的分配和销毁是通过入栈和出栈操作进行的,速度要高于堆区。堆区存储高级数据类型,在数据初始化时,查找没有使用的内存,销毁时再从内存中清除,所以堆区的数据存储不一定是连续的。

类(class)和结构体(struct)在内存分配上是不同的,基本数据类型和结构体默认分配在栈区,而像类这种高级数据类型存储在堆区,且堆区数据存储不是线程安全的,在频繁的数据读写操作时,要进行加锁操作。

我们在swift文档里面能看到对结构的描述,结构体是值类型(Value Type),当值类型的数据赋值给一个变量或常量,或者传递给一个函数时,是值拷贝;

例如:

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"

通过这个例子我们能清楚的看到,当hd赋值给cinema时,是将hd中存储的值拷贝给cinema,所以当给cinema的width属性赋值的时候,并不会改变hd中的属性值,如下图所示:

image

结构体除了属性的存储更安全、效率更高之外,其函数的派发也更高效。由于结构体不能被继承,也就是结构体的类型被final修饰,根据我们对于动态派发及静态派发的描述,那么其内部函数应该是属于静态派发,在编译期就确定了函数的执行方式,其函数的调用通过内联(inline)的方式进行优化,其内存连续,减少了函数的寻址及内存地址的偏移计算,其运行相比于动态派发更加高效。


协议类型(protocol type)

多态是面向对象的一大特性,在结构体中不能通过继承或者引用语言的多态,swift就引入了协议(protocol),通过协议来实现了结构体的多态特性,这也是swift面向协议编程的核心所在。

对于类(class)来说,每个类都会创建一个虚拟函数表指针,这个指针则指向一个v-table表,也就是虚函数表,表内存储着该类的函数指针数组,拥有继承关系的子类会在虚函数表内通过继承顺序(C++可以实现多继承)去展示虚函数表指针。类里面方法的派发则是根据v-table表里面函数指针来进行派发。

而结构体(struct)没有继承,也就是说结构体并没有v-table表用于函数的派发。为了实现这一特性,在结构体的协议(protocol)的实现里添加了Protocol Witness Table用于管理协议类型的方法派发。


编译过程

上面介绍了一些swift在数据结构上的一些优化,除了数据结构优化之外,swift在编译过程也进行了大量的优化,其中最核心的优化,是在编译过程中引入SIL

SIL,Swift Intermediate Language,是为了优化swift编译过程而设计的中间语言,主要包含了以下功能:

  1. 一系列的高级别优化保障,用于对运行时和诊断行为提供可预测的基线;
  2. 对swift语言数据流分析强制要求,对不满足强制要求的问题产生诊断。例如变量和结构体必须明确初始化,代码可达性即方法return的检测,switch的覆盖率;
  3. 确保高级别优化。包含retain/release优化,动态方法的去虚拟化,闭包内联,内存初始化提升和泛型方法实例 化.
  4. 可用于分配"脆弱"内联的稳定分配格式,将Swift库组件的泛型优化为二进制。


Clang编译流程

image

Clang编译过程有以下几个缺点:

  1. 与代码与LLVM IR之间有巨大的抽象鸿沟(Wide abstraction gap between source and LLVM IR );
  2. IR不适合源码级别的分析(IR isn't suitable for source-level analysis );
  3. CFG(Control Flow Graph)缺少精准度(CFG lacks fidelity );
  4. CFG偏离主道(CFG is off the hot path );
  5. 在CFG和IR降级中会出现重复分析(Duplicated effort in CFG and IR lowering)。

由于以上这些缺点,swift语言开发团队在开发过程中进行了一系列的优化,其中最关键的是引入SIL.


swift编译流程

Swift作为一个高级别和安全的语言具有以下特点:

高级别语言

  • 通过代码充分的展示语言的特性(Move more of the language into code)
  • 支持基于协议的泛型(Protocol-based generics)

安全语言

  • 充分的数据流检查:未初始化变量,函数返回处理检测,这些项在检测不合格时会产生对应的编译错误(Uninitialized vars, unreachable code should be compiler errors)
  • 边界和溢出的检测(Bounds and overflflow checks)

swift编译流程:

image

Swift 源码到IR之间的流程:

image

Swift 编译过程引入SIL有几个优点:

  1. 完成的变数程序的语义(Fully represents program semantics );
  2. 既能进行代码的生成,又能进行代码分析(Designed for both code generation and analysis );
  3. 处在编译管线的主通道(Sits on the hot path of the compiler pipeline );
  4. 架起桥梁连接源码与LLVM,减少源码与LLVM之间的抽象鸿沟(Bridges the abtraction gap between source and LLVM)


Swift编译器的流程

Swift编译器作为高级编译器,具有以下严格的传递流程结构。

Swift编译器的流程如下:

  • Parse: 语法分析组件从Swift源码构成AST
  • 语义分析组件对AST进行类型检查,并对其进行类型信息注释。
  • SILGen组件从AST形成"原始(raw)"SIL
  • 一系列在  SIL上运行的,用于确定优化和诊断合格,对不合格的代码嵌入特定的语言诊断。这些操作一定会执行,即使在-Onone选项下也不例外。之后产生 正式(canonical) SIL.
  • 一般情况下,是否在正式SIL上运行SIL优化是可选的,这个检测可以提升结果可执行文件的性能.可以通过优化级别来控制,在-Onone模式下不会执行.
  • IRGen会将正式SIL降级为LLVM IR.
  • LLVM后端提供LLVM优化,执行LLVM代码生成器并产生二进制码.

在上面的流程中,SIL对Swift的编译过程进行了一系列的优化,即保证的代码执行的安全性,又提升了代码执行的效率.


结尾

上面从Swift语言设计的数据结构及编译流程等方面进行了简单的分析,中间有很多细节没有在文章里阐述特别清晰,如果有兴趣了解更多,可以参考以下资料。

image

原文:https://xie.infoq.cn/article/322a5ad0598d2e408269171b4

  • 发表于 2020-12-26 15:29
  • 阅读 ( 615 )
  • 分类:操作系统

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1474 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章