Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸

Posted SwiftCafe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸相关的知识,希望对你有一定的参考价值。

在 Swift 4.1 的编译器中,提供了一个新的优化选项。可以减少代码生成尺寸。 

对生成的代码进行优化,是很多编译器都提供的功能,Swift 之前版本的编译器其实也提供了优化功能,如果你打开 XCode 项目的 Build Settings, 就可以找到一个叫做 Optimization Level 的选项,默认情况下,XCode 会对 Release 下的项目默认开启编译器优化:

这个选项在实际编译的时候,会给编译器传递一个 -O 参数,它就代表进行性能优化。 

新增的编译选项

XCode 9.3,除了上述的性能优化之外,还提供了另外一个新的选项 -Osize, 对代码尺寸进行优化:

Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸

从上图中可以看到 -O 和 -Osize 两个参数是互斥的,只能选一个。 -O 前面我们说过了,是对代码的执行速度进行优化,但执行速度提升了,就会牺牲一部分代码空间。

反之 -Osize 是专门为节省代码空间这个目的而来的优化选项。 那么他们分别能带来多少收益和损失呢? 这点在 swift.org 的官方 Blog 也有介绍:

Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸

-Osize 根据项目不同,大致可以优化掉 5% - 30% 的代码空间占用。 相比 -0 来说,会损失大概 5% 的运行时性能。 如果你的项目对运行速度不是特别敏感,并且可以接受轻微的性能损失,那么 -Osize 就值得一用。 

Single File 和 Whole Module

除了  -O 和 -Osize, 还有另外一个概念也值得说一下。 就是 Single File 和 Whole Module 。 在之前的 XCode 版本,这两个选项和 -O 是连在一起设置的,Xcode 9.3 中,将他们分离出来,可以独立设置:

Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸

Single File 和 Whole Module 这两个模式分别对应编译器以什么方式处理优化操作。 

Single File 是逐个文件进行优化,它的好处是对于增量编译的项目来说,它可以减少编译时间,对没有更改的源文件,不用每次都重新编译。并且可以充分利用多核 CPU,并行优化多个文件,提高编译速度。

但它的缺点就是对于一些需要跨文件的优化操作,它没办法处理。如果某个文件被多次引用,那么对这些引用方文件进行优化的时候,会反复的重新处理这个被引用的文件,如果你项目中类似的交叉引用比较多,就会影响性能。

Whole Module 则是将项目所有的文件看做一个整体,不会产生 Single File 模式对同一个文件反复处理的问题,并且可以进行最大限度的优化,包括跨文件的优化操作。

缺点是,不能充分利用多核处理器的性能,并且对于增量编译,每次也都需要重新编译整个项目。

XCode 的默认设置使用的是 Whole Module 模式。 我特意搜索了一下,在 Stack Overflow 上面找到了一个比较好的总结,也给大家贴出来参考一下:

Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸


对于这个选项,我的理解是,如果没有特殊情况,使用默认的 Whole Module 优化即可。 它会牺牲部分编译性能,但的优化结果是最好的。

何为编译优化

关于编译优化,这话话题其实不小。 简单来说,现在我们用高级语言写出的代码,更大程度上是基于我们人的思维逻辑。 然后通过编译器,变成机器的逻辑。比如现在如果大家开发一个项目,更关注业务逻辑的实现,比如点击购买按钮,能不能正常调用下订单的函数。或者当用户完成某个功能,能不能按照预定的要求弹出评价提示等。

我们现在越来越少的会为怎么写一行代码能够减少电量消耗,或者如何提高多核 CPU 的利用率这类的问题花费精力。编译优化就是在一定程度上帮助我们处理这类问题的功能。

用 swift.org 中一篇 Blog 来举个例子:

struct X {
    var x: Int { return 27 }
}

比如上面这个代码,定义了一个属性 x, 它通过一个函数返回一个整数 27。 如果你开启了编译优化,编译器就有可能将这个属性优化为 inline 函数(因为这个函数体相对简单)。所谓 inline 函数,就是在调用它的地方直接把它展开成代码。比如这样:

let ins = X()
print(ins.x)

上述代码实际在编译优化后,就成了这样:

let ins = X()
print(27)

那么这样替换能带来什么好处呢,因为在程序真正执行的时候,函数调用的开销要比直接执行某段代码大很多,所以将一些比较小的函数直接优化成 inline 的,就肯定会提高程序运行的效率了。

这就是编译优化的一个例子,上面说的 inline 替换是对性能进行优化,可想而知如果你的代码中多次调用了 ins.x 这个属性,那么他们就都会被替换,我们这个例子中这个函数体还比较简单,如果函数体稍微复杂一些,你的代码总量必然会被编译优化增大。 过多的 inline 虽然会对性能提升有帮助,但无疑会增大代码的尺寸。

这也是程序设计中守恒的一个定律,同样的条件下,空间和性能不可能兼得,需要取舍。

相信通过这个解释,大家应该更能理解 -O 和 -Osize 的区别了。 以官方 Blog 的解释,-O 更着重于优化性能,同时会带来代码空间的增大。 -Osize 着重于代码尺寸,比如官方 Blog 上面就有一点明确的说明,-Osize 对于 inline 函数的优化标准就比 -O 谨慎很多。 从这个角度看, inline 优化少了,代码尺寸自然会变小,同样的运行性能就会稍微降低。

当然,上面只是通过 inline 优化给大家举个例子,目的是帮助大家更好的理解编译优化的运作原理。 实际的编译优化操作,要远比我们这里描述的复杂。

关于 -Osize  的更多介绍,大家也可以看一下 swift.org 这篇 Blog:https://swift.org/blog/osize

总结

XCode 9.3 新增的 -Osize 编译选项,给大家提供了一个新的选择。 这篇文章也通过对它的介绍,给大家分享了关于编译优化知识的一些基本概念,也许会帮你在讨论问题的时候多一些谈资。同样,现在其实有不少项目对空间尺寸的优化需求在增多,我想这也是 -Osize 这个新的编译选项提供出来的原因之一吧。 如果你的项目恰好也有这方面的需求,不妨可以试一下它。

最好,劳烦各位几秒钟,做一个小调查,什么时候你看文章最方便,按照这个调整文章推送时间。

Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸


以上是关于Xcode 9.3 新增能力,优化 Swift 编译生成代码的尺寸的主要内容,如果未能解决你的问题,请参考以下文章

Xcode 9.3 编译 Swift 源项目永远不会完成

如何在 Xcode 9.3 中切换到 Swift 4.0?

Xcode 9.3(Swift 4.1)中的 Codable '没有初始化器'

没有这样的模块 'GoogleSignIn' Xcode 9.3 和 Swift 4.1

堆栈视图中的自动约束冲突、Swift 2、iOS 9.3、XCode 7

Swift 自动布局在 Xcode 9.3 中不起作用