前言
? Swift是一門非常優(yōu)秀的語言猾警。由于沒有歷史包袱,Swift得以集眾家之長隆敢,甚至可以說激進(jìn)发皿。默認(rèn)非空,類型安全拂蝎,嚴(yán)格的編譯時檢查穴墅,為開發(fā)者避免了許多的坑。而強(qiáng)大的類型推斷温自,面向協(xié)議的設(shè)計玄货,以及函數(shù)式支持,使得寫代碼成為一種非常愉悅的體驗悼泌。
? 當(dāng)然松捉,作為一門只有4年歷史的語言,Swift仍然有許多亟需改進(jìn)的地方馆里。對于大一些的項目隘世,日常開發(fā)中經(jīng)常會遇到代碼高亮失效,代碼提示失效的問題也拜,好好的一個IDE活生生變成了文本編輯器以舒。而論項目編譯時間,Swift項目的編譯速度通常是OC的3倍慢哈。這讓用iMac(8G內(nèi)存,筆記本機(jī)械硬盤)的筆者養(yǎng)成了經(jīng)常盯著屏幕發(fā)呆的習(xí)慣永票。
? 實際上卵贱,在Swift普及率較高的歐美開發(fā)者當(dāng)中,也有人表示侣集,對于大一些的項目键俱,寧肯用OC。不是Swift語言不好世分,實在是XCode太坑(當(dāng)然主要是swiftc這個前端的鍋)编振。好在,Swift團(tuán)隊的一些成員正在重點改進(jìn)編譯相關(guān)問題臭埋,目前的測試數(shù)據(jù)似乎不錯踪央。預(yù)計到XCode10實裝的時候臀玄,能夠明顯改進(jìn)項目的編譯速度,IDE變白板的情況也會改善很多畅蹂。
正文
? 在那之前健无,官方提供了一個工具,可以方便定位會產(chǎn)生編譯瓶頸的代碼液斜。具體做法如下(XCode 8 以上):
- 在項目的target中累贤,打開build setttings
- 在里面找到
Other Swift Flags
這一項 - 添加兩個參數(shù):
-Xfrontend
和-warn-long-function-bodies=100
,這里100單位是毫秒 - 重新編譯項目
以上的設(shè)置少漆,會在編譯耗時過長的函數(shù)方法上產(chǎn)生一個警告:
Instance method 'updateCurrentTime()' took 6228ms to type-check (limit: 100ms)
? 沒錯臼膏,6秒鐘,而且只是類型檢查??示损。對于類型檢查來說渗磅,100ms已經(jīng)是很大的數(shù)字了,大部分情況下屎媳,1ms左右才是合理的夺溢。
簡單說一下Swift代碼編譯過程:
Parsing: 執(zhí)行語法檢查,生成AST語法樹烛谊,返回IDE語法錯誤及警告
Semantic analysis: 語義分析风响,執(zhí)行類型檢查及類型推斷,生成類型完整的AST丹禀,返回IDE語義錯誤及警告状勤。
Clang importer、SIL generation等等双泪。持搜。。
? 編譯過程的前兩步焙矛,直接決定了語法高亮和代碼提示的響應(yīng)速度葫盼。這個過程里Swift與OC最大的不同,便是引入了類型推斷村斟。實際上個人以為類型推斷是Swift能夠擁有簡潔優(yōu)雅語法的核心要素之一贫导。想當(dāng)年C++沒有auto的時候,用Vector之類的容器真是讓人欲仙欲死蟆盹。
真是成也類型推斷孩灯,敗也類型推斷。
我們來看看有問題的代碼:
// _lastTime和currentTime都是Double類型的屬性
guard abs(Int(round(_lastTime)) - Int(round(currentTime))) > 0 else {
return
}
其中abs()
和round()
是對應(yīng)C函數(shù)的泛型重載逾滥,Int()
是參數(shù)重載峰档,-
和>
是運(yùn)算符重載。
看來復(fù)雜表達(dá)式中有了過多泛型和重載之后,類型推斷的性能會指數(shù)級的下降讥巡,隨之而來的便是類型檢查時間過長掀亩。
因此,作為應(yīng)對手段尚卫,我們只能把代碼拆開:
let iLast: Int = Int(round(_lastTime))
let iCurr: Int = Int(round(currentTime))
let diff: Int = abs(iLast - iCurr)
guard diff > 0 else { return }
于是警告消失了归榕,通過拆分表達(dá)式和顯示類型提示,這段代碼的類型檢查速度提高了至少60倍吱涉。也由于這是代碼編譯過程的一部分刹泄,每次重編譯代碼都節(jié)省了5秒鐘的人生。
這是一個悲傷的故事??怎爵。
上面的例子可能比較特殊特石,不過除了重載以外,尾隨閉包也是類型推斷的重災(zāi)區(qū)鳖链。比如下面的代碼:
// 250ms type-check
let averageScore = Int(round(scoreResult
.map{Float($0.value)}
.reduce(0, +)
/ Float(scoreResult.count)))
還是拆代碼姆蘸,以及閉包的顯式類型提示:
// 10ms 以內(nèi)
let ast: Float = scoreResult
.map{r in return Float(r.value)}
.reduce(0, +)
/ Float(scoreResult.count)
let averageScore: Int = Int(round(ast))
這么把警告一個個干掉之后,雖然不可能完全解決問題芙委,但是滿屏黑白的情況會減少很多逞敷,至少不用經(jīng)常習(xí)慣性的按?S
來刷新了。(沒錯灌侣,除了代碼鍵入推捐,保存
也能觸發(fā)語法檢查)
后記
在官方對于swift編譯性能改進(jìn)的分析中(鏈接),提到了一些改進(jìn)方向:
- 增量編譯模型過于保守侧啼,許多不必要的重新編譯牛柒。
- 名字解析過于激進(jìn),讀入(反序列化)了過多的定義痊乾。
- 前端任務(wù)的二次方級(復(fù)雜度)的任務(wù)中皮壁,很多引用的定義的類型檢查不夠lazy。
- 表達(dá)式的類型推斷對于約束的推導(dǎo)沒有效率哪审,某些情況下(時間復(fù)雜度)是超線性甚至指數(shù)級的蛾魄。
- SIL優(yōu)化的分析過程有時會緩存失敗,導(dǎo)致超線性的性能退化湿滓。
- SIL生成IR的過程畏腕,在某些情況下(比如大的值類型)會生成過多的IR碼,影響后端LLVM的處理時間茉稠。
? 由此,我們可以期待把夸,在Swift 5發(fā)布的時候而线,無論是IDE的性能,還是代碼編譯速度,都有一個明顯的改善膀篮。
PS:Swift 5還可以期待async/await
和Ownership
這樣的并發(fā)及運(yùn)行時性能改進(jìn)嘹狞,以及官方的主要目標(biāo),ABI穩(wěn)定(跨版本二進(jìn)制兼容的一部分)誓竿。