一、LLVM簡介
1、概念
- LLVM有狹義和廣義之分
- 廣義的LLVM: LLVM編譯器架構,包括編譯前端(Frontend)、優化器(Optimizer)和編譯後端(Backend)三大部分;
- 狹義的LLVM:LLVM架構的編譯後端,主要負責,程式碼優化、目的碼生成等;
- LLVM專案已經成長為一個十分龐大的專案,有很多子專案,在這裡就不討論了;我們只關注:LLVM編譯器架構 和 編譯後端LLVM。
2、LLVM架構
-
LLVM架構也是採用經典的編譯器架構(三段式)設計:
- 前端 (Frontend) :對原始碼做詞法分析、語法分析、語義分析、生成中間程式碼
- 優化器 (Optimizer) :用於中間程式碼優化
- 後端 (Backend) :根據中間程式碼,用於生成對應的 CPU架構的機器碼
- LLVM架構示意如下:
-
Objective C/C/C++語言使用的編譯器前端是
Clang
,Swift
是 Swift 語言的編譯器,完整的表示是:Swift Compiler -
從LLVM架構示意圖,也能明白這種三段式架構的優勢:
- 增加新的語言(如Swift),只需要實現新的 Frontend即可,Optimizer 和 Backend 可以重用;
- 新增新的 CPU 架構時(如ARM),也只需要實現新的 Backend即可;
3、更多
-
LLVM專案原始碼地址:github.com/llvm/llvm-p…
# clone下專案 git clone --depth=1 https://github.com/llvm/llvm-project.git 複製程式碼
二、LLVM處理過程
大致處理過程:預處理 -> 詞法分析 -> Token -> 語法分析 -> AST -> 中間程式碼生成 -> LLVM IR -> 優化 -> 生成彙編程式碼 -> Link -> 目標檔案
1、編譯前端(Clang)處理
- 預處理(preprocessor):替進行標頭檔案引入,巨集替換,註釋處理,條件編譯(#ifdef)等操作
- 詞法分析(lexical anaysis):詞法分析器讀入原始檔的字元流,將他們組織稱有意義的詞素(lexeme)序列,對於每個詞素,此法分析器產生詞法單元(token)作為輸出。
- 語法分析(semantic analysis):詞法分析產生的詞法單元Token流會被解析成一顆抽象語法樹(abstract syntax tree - AST);有了抽象語法樹,Clang就可以對這個樹進行分析,找出程式碼中的錯誤。比如型別不匹配,亦或Objective-C中向target傳送了一個未實現的訊息。
- CodeGen(中間程式碼生成):遍歷語法樹,生成LLVM IR(中間)程式碼。LLVM IR是編譯前端的輸出,編譯後端的輸入。
2、IR處理
- LLVM會對生成的IR進行優化,優化會呼叫相應的Pass進行處理;
- Pass由多個節點組成,都是
Pass
類的子類,每個節點負責做特定的優化;- 補充:Pass相關可以見Writing an LLVM Pass和開發和除錯第一個 LLVM Pass
3、編譯後端處理
-
生成彙編程式碼:LLVM對IR進行優化後,會針對不同CPU架構生成不同的目的碼,最後以彙編程式碼的格式輸出;
-
彙編:彙編器以彙編程式碼作為輸入,將彙編程式碼轉換為機器程式碼,最後輸出目標檔案(Object File),
-
連結:連結動態庫,o檔案,生成一個Mach-O格式的可執行檔案,Mach-O檔案的更多瞭解可見: Mach-O檔案周邊二三事
三、Xcode 編譯優化介紹
一般而言,在Xcode下編譯專案,選擇的是Debug模式;提示Xcode編譯速度,主要是解決此類場景下問題
1、檢視Xcode編譯時間
- 關閉Xcode
- 開啟終端,輸入如下命令後,
defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
複製程式碼
- 重啟Xcode,Build,可以檢視編譯時間
2、Xcode編譯過程
- 建立
app_name.app
的資料夾; - 把
Entitlements.plist
寫入到DerivedData
裡,處理打包的時候需要的資訊(如bundle-identifier); - 建立一些輔助檔案,比如各種.hmap,這是headermap檔案;
- 執行CocoaPods的編譯前指令碼:檢查Manifest.lock檔案;
- 編譯.m檔案,生成.o檔案。
- 連結動態庫,o檔案,生成一個Mach-O格式的可執行檔案。
- 編譯assets,編譯storyboard,連結storyboard
- 拷貝動態庫Logger.framework,並且對其簽名
- 執行CocoaPods編譯後指令碼:拷貝CocoaPods Target生成的Framework
- 對app_name.App簽名,並驗證
- 生成
app_name.app
3、編譯的常規優化
-
使用
forward declaration
(前向宣告):即用@class class_name
,而不是#import "class_name.h"
,這樣能減少編譯時間; -
常用標頭檔案放到預編譯檔案裡:Xcode的pch檔案是預編譯檔案,這裡的內容在執行Xcode Build之前就已經被預編譯,並且引入到每一個.m檔案裡了。
-
Debug時
Build Active Architecture Only
設定為YES:只編譯出支援當前CPU架構的安裝包; -
提高編譯執行緒數:Xcode預設的編譯執行緒數,就是CPU的核心數,可適當增加編譯執行緒數來提高編譯速度
# 獲取當前核心數: sysctl -n hw.ncpu # 設定編譯執行緒數: defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 12 # 獲取編譯執行緒數: defaults read com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 複製程式碼
-
Debug時,
Debug Information Format
改為DWARF
,不需要dSYM
檔案,有一定的效果; -
Debug時,Link-Time Optimization設定為NO:不使用LTO特性
-
Enable Index-While-Building Functionality設定為NO: 預設開啟,Xcode 編譯時會建立程式碼索引,影響編譯速度;關閉後,在編譯時就不會進行索引,而是在空閒時間建立程式碼索引(自動補全Code、查詢定義)
4、補充Link-Time Optimization(LTO)特性
-
LTO主要是對連結過程的一個優化,開啟LTO有三個好處:
-
多餘程式碼去除(Dead code elimination):LTO在link時候,發現跨多檔案的無用程式碼;
-
跨過程優化(Interprocedural analysis and optimization):最終不會執行的程式碼,在二進位制結果檔案中不出現;
-
內聯優化(Inlining optimization):彙編中不使用 “
call func_name
” 語句,直接將外部方法內的語句“複製”到呼叫者的程式碼段內。好處是不用進行呼叫函式前的壓棧、呼叫函式後的出棧操作,提高執行效率與棧空間利用率。
-
-
LTO有
Monilithic
和Incremental
兩個方式選擇,Release下,建議選擇Incremental
,Debug禁用LTO,因為LTO對符號剝離有點影響,可能會影響斷點的單步執行; -
選擇
Incremental
這個優化方式,會有link cache,使二次編譯的速度更快(只編譯和連結少數修改過的檔案),另一方面它還可能減小Code Size
;而Monilithic
方式並不支援多執行緒和增量連結 -
開啟
Incremental
的選項後,如果出現duplicate symbols
錯誤,可以看下程式碼,有可能是全域性變數使用不規範,應該:在定義時,必須使用static
關鍵字或者單獨在.m
檔案中定義,.h
檔案中只能宣告變數,而不應該定義變數;
5、總結
- 瞭解Xcode的編譯過程,使用常規的優化手段,對提升編譯速度有一定的效果(哈哈,我說的是一定);
- 但是如果需要進一步優化編譯速度,需要完善基礎能力建設,保障需要的程式碼用原始碼,不使用的用二進位制;這涉及到很多挑戰,包括但不限於:專案元件化、Pod 庫自動出二進位制、Build前切換Pod庫二進位制和原始碼能力等;
- 當然還有種有錢任性的做法,裝置升級,體驗過:13.3英寸Mac 升級到15.4英寸Mac(記憶體16GB、磁碟521GB、CPU:2.6 GHz ),編譯速度帶來質的提升(just a joke)。
四、補充Clang小知識
1、補充Clang的優勢
Clang比GCC有更好的效能(更快速度、更省記憶體),主要有:
- 編譯速度快,佔用記憶體小,並且相容 GCC。
- Clang可以發現顯示出問題所在的行和具體位置,並且可以確切地說明出現這個問題的原因,並指出錯誤的型別是什麼
- 模組化設計:Clang採用基於庫的模組化設計,易於IDE整合及其他用途的重用
- 診斷資訊可讀性強:在編譯過程中,Clang建立並保留了大量詳細的後設資料(metadata),有利於除錯和錯誤報告;
- 設計清晰簡單,容易理解,易於擴充套件增強。
2、Clang提供的能力
- LibClang:LibClang 可以訪問 Clang 的上層高階抽象的能力,比如獲取所有 Token、遍歷語法樹、程式碼補全等。
- Clang Plugin:允許在AST 上做些操作,這些操作可以被整合到編譯中,成為編譯的一部分(影響編譯過程)。
- LibTooling:可以完全控制 Clang AST,通過 LibTooling 能夠編寫獨立執行的語言分析和檢查工具
3、 常見的OC靜態分析工具
-
OCLint:預設支援70+檢查規則,可定製、能夠幫助發現問題
-
Clang Static Analyzer(Clang 靜態分析器): C++ 開發的,分析 C、C++ 和 Objective-C 的開源工具
-
Infer:Facebook 開源的靜態分析工具,可以檢查出 C、Java 和 Objective-C 空指標訪問、資源洩露以及記憶體洩露等問題。