[翻译]关于Swift的编译时间优化

原文链接:Regarding Swift build time optimizations

上周,在我读完 @nickoneill 写的一篇优秀的博文《为缓慢的Swift编译时间提速》后,我发现用一个不同的角度去审视 Swift 代码并不是很难的一件事。

可以被认为是简洁的一行代码现在引发了一个新的问题 -- 是否应该把这行代码重构成对应的9行代码以让编译器更容易工作(看看接下来要讲的关于空合运算符(nil coalescing operator)的示例)?到底哪个才是更重要的,简洁的代码还是对编译器友好的代码?这取决于项目的大小和开发者的想法。

慢着。。。这里有一个 Xcode 插件

在展示具体的例子之前,我先想到就是手动查看日志是一件非常耗时的事情。有人提出了用终端命令可以让这件事情变得比较容易,但是我更进一步,把这个用 Xcode 插件 给实现出来了。

Xcode插件.png

对我来说,最初的目的就是找到并修复最耗时的地方,但我现在的想法是让它经历更多的迭代过程。这样的话我就不仅可以让代码编译更有效率,还可以防止第一次进入项目的耗时。

更加惊喜的是

我经常在多个 Git 分支间跳来跳去,等待一个缓慢的项目编译完成往往浪费了大量的时间。我想了好长一段时间为什么我的一个宠物项目会编译地这么缓慢(大概2万行 Swift 代码)。

在我学习了究竟是原因导致的这件事之后,我不得不承认我的确很吃惊,一行代码就需要几秒钟来编译。

让我们一起看看几个例子。

空合操作符

编译器是很不喜欢这里的第一种方式的。在展开下面两处简写的代码之后,编译时间减少了99.4%。

// 编译时间: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

// 编译时间: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)

ArrayOfStuff + [Stuff]

这个看起来像下面这样:

return ArrayOfStuff + [Stuff]
// 而不是
ArrayOfStuff.append(stuff)
return ArrayOfStuff

我经常这样做,每次都会对所需的编译时间产生影响。下面是最差的一个,这里的编译时间减少了97.9%。

// 编译时间: 1250.3ms
let systemOptions = [ 7, 14, 30, -1 ]
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// 一些中间的代码
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]

// 编译时间: 25.5ms
let systemOptions = [ 7, 14, 30, -1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
// 一些中间的代码
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)

三元运算符

仅仅只是把三元运算符替换成 if-else 语句,就让编译时间减少了92.9%。如果将 map 换成 for 循环,就又能减少75%(但是那样的话我的眼睛可就受不了了)。😉

// 编译时间: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}

// 编译时间: 16.9ms
var labelNames: [String]
if type == 0 {
    labelNames = (1...5).map{type0ToString($0)}
} else {
    labelNames = (0...2).map{type1ToString($0)}
}

转换 CGFloat 到 CGFloat

没听懂我在说什么?其实下面例子中值已经是 CGFloat 了,并且有些括号是多余的。在清理完这些冗余之后,编译时间减少了99.9%。

// 编译时间: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180

// 编译时间: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180

Round()

下面是一个很奇怪的例子,下面的例子中变量是一个局部变量与实例变量的混合。这个问题似乎不是出在四舍五入本身,而是在于结合代码的方法。去掉四舍五入的方法大概能减少 97.6% 的构建时间。

// 编译时间: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// 编译时间: 34.7ms
let expansion = a — b — c + d * 0.66 + e

注意:以上所有测试都在MacBool Air(13英寸,2013年中)上进行。

尝试一下吧

不管你是否面临过编译时间太长的问题,编写对编译器友好的代码都是非常有用的。我确信你会在其中找到一些惊喜。作为参考,这里有完整的代码,我的工程中可以5秒内完成编译…

import UIKit

class CMExpandingTextField: UITextField {

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

推荐阅读更多精彩内容

  • 关于 Swift 重要这个文档所包含的准备信息, 是关于开发的 API 和技术的。这个信息可能会改变, 根据这个文...
    无沣阅读 4,275评论 1 27
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 有句话,我是第一次说,而且只说一次:" 这样确切的爱,一生只有一次,我今天才知道,我之所以瓢泼就是为你。"——《廊...
    我行我素的兔阅读 1,238评论 0 2
  • 1、自我目标管理 - 将大的目标细化成一个个小的目标 - 每个目标要有明确的事项和时限 - 杜绝懒惰 2、自我知识...
    jasonlam阅读 253评论 0 1
  • 文/ 莫菲 一.历史残留的印记 几年间,世俗沧桑变化很大,没有了历史的爆炸性战争,却也有历史遗留下的精神财富。我...
    莫琴晓晓阅读 464评论 0 2