【译文】Swift 3 的 Whole-module optimization

原文:Whole-Module Optimization in Swift 3

Whole-module optimization (以下简称WMO) 是 Swift 编译优化的一种模式。WMO 带来的性能提升主要取决于项目本身,但它能提升2-5倍。

WMO 通过打开编译 flag -whole-module-optimization-wmo 来启用,在 Xcode 8 中新建项目时是默认开启的(注:只在release模式下开启)。同时 Swift Package Manager 在 release 模式下也开启了 WMO 。

WMO 是什么?让我们先看下没有开启 WMO 时编译器是如何工作的吧。

Modules 以及如何编译它们

Modules 是一系列 Swift 文件的集合。每一个 module 被编译为一个分发单元 - framework 或可执行文件。没有开启 WMO 时,Swift 编译器对为 module 中每个文件进行单独编译,这也是默认的做法。用户没有必要手动来做管理这些,编译器或者 Xcode 构建系统会自动处理。

读取和解析源文件之后(还有一些其他工作,如类型检查),编译器优化 Swift 代码,生成机器代码,创建二进制 .o 文件,最后链接器把所有的 .o 文件结合起来生成动态库或可执行文件。

在单个文件的编译过程中,编译器的优化只能集中在单个文件上。这样把跨函数优化(比如函数内联 function inlining、泛型专门化 generic specialization)限制在了那些本文件内定义或调用的函数上。

让我们来看一个例子。假设我们的 module 有一个 utils.swift 文件,里面定义了一个泛型数据结构 Container<T> , 该结构体有 getElement 方法,这个方法在整个 module 里被多处调用,比如 main.swift 里。

main.swift:

func add (c1: Container<Int>, c2: Container<Int>) -> Int {
  return c1.getElement() + c2.getElement()
}

utils.swift:

struct Container<T> {
  var element: T

  func getElement() -> T {
    return element
  }
}

当编译器优化 main.swift 时它不知道 getElement 是如何实现的,只知道这个函数的存在,所以编译器会生成一个指向 getElement 的调用。另一方面,编译器优化 utils.swift 时,它也不知道 getElement 函数会返回哪种具体类型,所以它只能生成该函数的泛型版本,这样比生成一个返回具体类型的函数要慢很多。

即使是 getElement 中简单的一句return,都需要查询类型的元数据来推断出如何复制该元素。它可能只是很简单的 Int 类型,但也可能是更复杂的类型,可能还需要进行引用计数的相关操作。编译器不知道采用何种办法。

Whole-module optimization

如果开启了 WMO ,那么编译器能做的更好。这时编译器会把整个 module 优化成一个文件。

这样有两个优势。第一,编译器知道 module 里所有函数的具体实现,它可以进行函数内联,函数专门化等优化。函数专门化的意思是编译器创建一个为某个特定的调用环境优化过的新版本。例如,编译器能为某个泛型函数生成一个有具体类型的版本。

在我们的示例中,编译器生成了泛型结构体 ContainerInt 类型版本。

struct Container {
  var element: Int

  func getElement() -> Int {
    return element
  }
}

编译器把专门化的 getElement 函数内联到 add 方法中。

func add (c1: Container<Int>, c2: Container<Int>) -> Int {
  return c1.element + c2.element
}

这样编译时可以减少几条机器指令。而单文件编码则会调用泛型函数 getElement两次。

跨文件的函数专门化和内联只是 WMO 优化的两个方面。即使编译器不进行函数内联,如果能够了解函数的具体实现也会有很多帮助。例如它可以根据引用计数推断接下来的行为。编译器能够移除一个函数多余的引用计数。

WMO 优化的第二个好处是编译器可以推断出所有非公开的函数的使用情况。非公开函数只能在 module 内调用,所以编译器能推断出非公开函数的所有引用。有了这些信息,编译器能做什么呢?

一个基本的优化是减少死函数和死方法(没有被调用过的函数和方法)。在 WMO 下,编译器知道如果一个非公开函数或方法没有被调用过就可以删除它。那么为什么编程人员会编写一个从来不被调用的函数呢?嗯。。这并不是死函数和死方法产生的主要原因。通常是因为其他编译优化导致某些函数变成死函数的。

假设 add 函数只被 Container.getElement 调用。把 getElement 内联之后,add 就不会被调用了,add 会被移除。即使编译器不内联 getElement ,编译器也可以移除 getElement 的泛型版本,因为 add 只在 getElement 的专门化版本中被调用。

编译时间

单文件编译时,编译器并行的在单独的进程中编译每一个文件。同时,没有改动的文件无需再次编译(假设所有依赖文件也没有被修改)。这就是增量编译。这样节省了很多编译时间,特别是改动很小的情况下。在 WMO 下怎样进行增量编译呢?让我们更详细的了解一下 WMO 模式下编译器是如何工作的吧。

编译器有几个运行阶段:语法分析,类型检查,SIL 优化,LLVM 后端处理。

大多数情况下,语法分析和类型检查都非常快,而且在接下来发布的 Swift 版本中它们还会更快。SIL(Swift Intermediate Language)优化负责所有重要的 Swift 语言的具体优化,比如函数专门化,函数内联等。这一阶段占用大概三分之一的编译时间。大部分的编译时间都是 LLVM 后端在运行底层优化和代码生成。

开启 WMO 优化后,在 SIL 优化阶段,module 再次被分割成多个部分。LLVM 后端在多个线程中处理这些分割的部分。如果该部分距离上次编译没有改动则不会被重新编译。所以即使开启了 WMO 优化,编译器也能够并行(多线程)地和增量地进行大部分的编译工作。

结论

WMO 优化是一种很好的方式,它有最好的性能表现,并且不用担心在 module 的多个文件之间如何分发 Swift 代码。以上这些优化,在某些特殊的代码环境下,会比单文件优化的性能好上5倍。相比整个项目优化,WMO 有更好的性能和更少的编译时间。

PS:渣翻,翻译有不合适的地方还望指正。编译原理可以参考这篇文章大前端开发者需要了解的基础编译原理和语言知识

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • 2017/07/21 (134-145) 周四 晴 #to do list# 1、TEM8✅ 2、背诵4 今天的自...
    征人随说阅读 179评论 0 0
  • “当时的我是何等的温柔 我把花瓣洒在你的发间 当你离开,我的心不会变凉 想起你,就如同读到最心爱的文字,那般欢畅”...
    Ruby_Li阅读 2,466评论 3 2
  • 今晚画个帅哥,给冬冬开心一下,别总觉得我不开心啊!立马发给闺蜜冬冬取名,与以往不同的是,冬冬尽然秒回,这速度那是第...
    阿娉叁壹绘画阅读 244评论 0 4