感謝 up 主 ZOMI 醬:https://space.bilibili.com/517221395
編譯器基礎概念#
- 什麼是編譯器?
- 為什麼 AI 框架需要引入編譯器?
- AI 框架和 AI 編譯器之間什麼關係?
編譯器與解釋器#
編譯器(Compiler)和解釋器(Interpreter)最大的區別在於:解釋器在程序運行時將代碼轉換成機器碼,編譯器在程序運行之前將代碼轉換成機器碼。
JIT 和 AOT 編譯方式#
目前,程序主要有兩種運行方式:靜態編譯和動態解釋。
- 靜態編譯的代碼程序在執行前全部被翻譯為機器碼,通常將這種類型稱為 AOT(Ahead of time),即 “提前編譯”;
- 動態解釋的程序則是對代碼程序邊翻譯邊運行,通常將這種類型稱為 JIT(Just in time),即 “即時編譯”。
AOT 程序的典型代表是用 C/C++ 開發的應用,其必須在執行前編譯成機器碼,然後再交給操作系統具體執行;
而 JIT 的代表非常多,如 JavaScript、Python 等動態解釋的程序。
特點 | JIT (即時編譯) | AOT (提前編譯) |
---|---|---|
優點 | 1. 可以根據當前硬體情況即時編譯生成最優機器指令 2. 可以根據當前程序的運行情況生成最優的機器指令序列 3. 當程序需要支持動態鏈接時,只能使用 JIT 的編譯方式 4. 可以根據進程中內存的實際情況調整代碼,使內存能夠更充分的利用 | 1. 在程序運行前編譯,可以避免在運行時的編譯性能消耗和內存消耗 2. 可以在程序運行初期就達到最高性能 3. 可以顯著加快程序的執行效率 |
缺點 | 1. 編譯需要佔用運行時 Runtime 的資源,會導致進程執行時候卡頓 2. 編譯佔用運行時間,對某些代碼編譯優化不能完全支持,需在流暢和時間權衡 3. 在編譯準備和識別頻繁使用的方法需要佔用時間,初始編譯不能達到最高性能 | 1. 在程序運行前編譯會使程序安裝的時間增加 2. 將提前編譯的內容保存起來,會佔用更多的內存 3. 牺牲高級語言的一致性問題 |
在 AI 框架中區別#
目前主流的 AI 框架,都会带有前端的表達層,再加上 AI 編譯器對硬體使能,因此 AI 框架跟 AI 編譯器之間關係非常緊密,部分如 MindSpore、TensorFlow 等 AI 框架中默認包含了自己的 AI 編譯器。目前 PyTorch2.X 版本升級後,也默認自帶 Inductor 功能特性,可以對接多個不同的 AI 編譯器。
編譯方式 | 描述 | 典型代表 |
---|---|---|
AOT (提前編譯) | 靜態編譯的代碼程序在執行前全部被翻譯為機器碼,適合移動、嵌入式深度學習應用。 | 1. 推理引擎:訓練後的 AI 模型提前固化,用於推理部署。 2. 靜態圖生成:神經網絡模型表示為統一的 IR 描述,運行時執行編譯後的內容。 |
JIT (即時編譯) | 動態解釋的程序邊翻譯邊運行,適合需要即時優化的場景。 | 1. PyTorch JIT:將 Python 代碼即時編譯成本地機器代碼,優化和加速深度學習模型。 2. Jittor:基於動態編譯 JIT,使用元算子和統一計算圖的深度學習框架,實現高效操作和自動優化。 |
Pass 和中間表示 IR#
Pass 主要是對源程序語言的一次完整掃描或處理。在編譯器中,Pass 指所採用的一種結構化技術,用於完成編譯對象(IR)的分析、優化或轉換等功能。Pass 的執行就是編譯器對編譯單元進行分析和優化的過程,Pass 構建了這些過程所需要的分析結果。
在編譯器 LLVM 中提供的 Pass 分為三類:Analysis pass、Transform pass 和 Utility pass。
Pass 類型 | 描述 | 功能 | 常見例子 |
---|---|---|---|
Analysis Pass | 計算相關 IR 單元的高層信息,但不對其進行修改。這些信息可以被其他 Pass 使用,或用於調試和程序可視化。 | 1. 從 IR 單元中挖掘並存儲信息 2. 提供查詢接口供其他 Pass 訪問 3. 提供 invalidate 接口以處理信息失效 | Basic Alias Analysis、Scalar Evolution Analysis |
Transform Pass | 使用 Analysis Pass 的分析結果,以某種方式改變和優化 IR。 | 1. 改變 IR 中的指令和控制流 2. 可能會減少函數調用,暴露更多優化機會 | Inline Pass |
Utility Pass | 功能性的實用程序,不屬於 Analysis Pass 或 Transform Pass。 | 1. 執行特定任務,如提取 basic block | extract-blocks Pass |
IR(Intermediate Representation)中間表示,是編譯器中很重要的一種數據結構。編譯器在完成前端工作以後,首先生成其自定義的 IR,並在此基礎上執行各種優化算法,最後再生成目標代碼。
如圖所示,在編譯原理中,通常將編譯器分為前端和後端。其中,前端會對所輸入的程序進行詞法分析、語法分析、語義分析,然後生成中間表達形式 IR。後端會對 IR 進行優化,然後生成目標代碼。
例如:LLVM 把前端和後端給拆分出來,在中間層明確定義一種抽象的語言,這個語言就叫做 IR。定義了 IR 以後,前端的任務就是負責最終生成 IR,優化器則是負責優化生成的 IR,而後端的任務就是把 IR 給轉化成目標平台的語言。LLVM 的 IR 使用 LLVM assembly language 或稱為 LLVM language 來實現 LLVM IR 的類型系統,就指的是 LLVM assembly language 中的類型系統。
因此,編譯器的前端,優化器,後端之間,唯一交換的數據結構類型就是 IR,通過 IR 來實現不同模塊的解耦。有些 IR 還會為其專門起一個名字,比如:Open64 的 IR 通常叫做 WHIRL IR,方舟編譯器的 IR 叫做 MAPLE IR,LLVM 則通常就稱為 LLVM IR。
IR 在通常情況下有兩種用途,1)一種是用來做分析和變換,2)一種是直接用於解釋執行。
編譯器中,基於 IR 的分析和處理工作,前期階段可以基於一些抽象層次比較高的語義,此時所需的 IR 更接近源代碼。而在編譯器後期階段,則會使用低層次的、更加接近目標代碼的語義。基於上述從高到低的層次抽象,IR 可以歸結為三層:高層 HIR、中間層 MIR 和 底層 LIR。
IR 類型 | 描述 | 用途 | 特點 |
---|---|---|---|
HIR (High IR) | 基於源程序語言執行代碼的分析和變換。 | 用於 IDE、代碼翻譯工具、代碼生成工具等,執行高層次代碼優化(如常數折疊、內聯關聯)。 | 1. 準確表達源程序語言的語義 2. 可以使用 AST 和符號表 |
MIR (Middle IR) | 獨立於源程序語言和硬體架構執行代碼分析和優化。 | 用於編譯優化算法,執行通用優化(如算術優化、常量和變量傳播、死代碼刪除)。 | 1. 與源程序代碼和目標程序代碼無關 2. 通常基於三地址代碼(TAC) |
LIR (Low IR) | 依賴於底層具體硬體架構做優化和代碼生成。 | 用於執行與具體硬體架構相關的優化,生成機器指令或匯編代碼。 | 1. 指令通常與機器指令一一對應 2. 體現了具體硬體架構的底層特徵 |
三地址代碼 TAC 的特點:最多有三個地址(也就是變量),其中賦值符號的左邊是用來寫入,右邊最多可以有兩個地址和一個操作符,用於讀取數據並計算。
多層 IR 和單層 IR 比較起來,具有較為明顯的優點:
- 可以提供更多的源程序語言的信息
- IR 表達上更加地靈活,更加方便優化
- 使得優化算法和優化 Pass 執行更加高效
如在 LLVM 編譯器裡,會根據抽象層次從高到低,採用了前後端分離的三段結構,這樣在為編譯器添加新的語言支持或者新的目標平台支持的時候,就十分方便,大大減小了工程開銷。而 LLVM IR 在這種前後端分離的三段結構之中,主要分開了三層 IR,IR 在整個編譯器中則起着重要的承上啟下作用。從便於開發者編寫程序代碼的理解到便於硬體機器的理解。
總結#
- 解釋器是一種計算機程序,將每個高級程序語句轉換成機器代碼
- 編譯器把高級語言程序轉換成機器碼,即將人可讀的代碼轉換成計算機可讀的代碼
- Pass 主要是對源程序語言的一次完整掃描或處理
- 中間表示 IR 是編譯器中的一種數據結構,負責串聯起編譯器內各層級和模塊
02 傳統編譯器發展#
編譯器與編程語言幾乎是同步發展起來的,發展過程可以分為幾個階段:
- 第一階段:20 世紀 50 年代,出現了第一個編譯程序,將算術公式翻譯成機器代碼,為高級語言的發展奠定了基礎。
- 第二階段:20 世紀 60 年代,出現多種高級語言和相應的編譯器,如 Fortran、COBOL、LISP、ALGOL 等,編譯技術也逐漸成熟和規範化
- 第三階段:20 世紀 70 年代,出現了結構化程序設計方法和模塊化編程思想,以及面向對象的語言和編譯器,如 Pascal、C、Simula 等,編譯技術也開始注重工程代碼的可讀性和可維護性。
- 第四階段是 20 世紀 80 年代,出現了並行計算機和分佈式系統,以及支持並行和分佈式的語言和編譯器,如 Ada、Prolog、ML 等,編譯技術也開始考慮程序的並行和分佈能力。
- 第五階段:20 世紀 90 年代,出現了互聯網和移動設備等新興平台,以及支持跨平台和動態特性的語言和編譯器,如 Java、C#、Python 等,編譯技術也開始關注程序的安全性和效率。
- 第六階段:21 世紀第一個 10 年,出現了以 Lua 為首的 Torch 框架,用於解決爆炸式湧現的 AI 應用和 AI 算法研究,之後又推出 TensorFLow、PyTorch、MindSpore、Paddle 等 AI 框架,隨著 AI 框架和 AI 產業的發展,出現了如 AKG、MLIR 等 AI 編譯器。
傳統編譯器之爭#
目前主流如 LLVM 和 GCC 等經典的開源編譯器,通常分為三個部分,前端 (frontEnd),優化器 (Optimizer) 和後端 (backEnd)。
- Front-End:主要負責詞法和語法分析,將源代碼轉化為抽象語法樹,即將程序劃分為基本的組成部分,檢查代碼的語法、語義和語法,然後生成中間代碼
- Optimizer:優化器則是在前端的基礎上,對得到的中間代碼進行優化(如去掉冗餘代碼、子表達式消除等工作),使代碼更加高效
- Back-end:後端則是將已經優化的中間代碼,針對具體的硬體生成目標代碼,轉換成為包括代碼優化器和代碼生成器
類型 | GCC | Clang/LLVM |
---|---|---|
許可證 | GNU GPL | Apache 2.0 |
代碼模塊化 | 一體化架構 | 模塊化 |
支持平台 | Unix, Windows, MAC | Unix, MAC |
代碼生成 | 高效,有很多編譯器選項可以使用 | 高效,LLVM 後端使用了 SSA 表單 |
語言獨立類型系統 | 沒有 | 有 |
構建工具 | Make Base | CMake |
解析器 | 最早採用 Bison LR,現在改為遞歸下解析器 | 手寫的遞歸下降解析器 |
鏈接器 | LD | lld |
調試器 | GDB | LLDB |
總結#
- 編譯技術是計算機科學皇冠上的一顆明珠,作為基礎軟件中的核心技術
- 編譯器能夠識別高級語言程序代碼中的詞彙、句子以及各種特定的格式和數據結構
- 編譯過程,是將源代碼程序轉換成機器能夠識別的二進制碼
- 傳統編譯器通常分為三個部分,前端 (frontEnd),優化器 (Optimizer) 和後端 (backEnd)