編譯器技術的虛擬化發展!?淺談LLVM
撇開特殊的情況不說,一般而言從程式碼的撰寫到應用程式的完成,最後總不免地需要進行一道程式碼『編譯』的動作。而這項編譯的工作,其實是透過『編譯器』的演算處理來完成。其編譯的主要目的,是要把本來撰寫者原本較容易閱讀的程式語法轉換成硬體機械可以直接執行的指令。以C語言為例子來說,編譯的動作其實指的就是從C語言轉換至組合語言的這個過程。雖然編譯不過是操作上的一道指令,但編譯過程中編譯器卻進行了許許多多程式碼的後處理。從這些處理動作,我們可以簡單地分為三個部分來看:前端(frontend)、中介(intermediate)與後端(backend)。而編譯器則會在這三個不同階段上分別針對開發者所撰寫的程式碼進行語法檢查、語法最佳化、目標處理器指令生成…等等。
既然談到了編譯器,一定要先介紹的就是大名頂頂的GCC。GCC的全名是GNU Compiler Collection。大概打從筆者開始對C語言有記憶時,GCC幾乎就已經是程式開發套件中不可或缺的一環。說起來GCC已經是歷史發展久遠(由Richard Matthew Stallman在1985年開始的),支援性廣泛,並且依然持續更新中的編譯器套裝。而最近期的穩定版本4.6.1也不過才是一個多月前的更新(2011-08-17)。
早期筆者為了編譯自行定制的MPlayer(GPL授權下的免費軟體),其中就有不少跟GCC糾結的經驗。整體來說,在MPlayer編碼與播放的演算上,同樣的程式碼編譯成i386架構,其效能往往硬是比其他平台好上一節。甚至在開發者之間還流傳著些許玩笑意謂的成見,例如:"It’s not a secret that the ARM code generated by GCC is considered to be crap.",而目前檯面上正夯的iPhone手機所採用的A4 CPU正是ARM架構的Cortex-A8核心。當然導致這樣差異的原因有許多不同的說法,不外乎數學函式庫支援的程度、向量/浮點運算單元的能力、執行緒排程、分支預測…諸如此類種種。簡而言之,編譯器所產生機械碼的品質,與編譯器所針對架構優化而著墨耕耘的程度,有著正比的關係。可惜的是編譯器後端指令集生成是一個相當複雜的步驟,例如像是必須把抽象暫存器替換成處理器的實體暫存器,這必須透過與平台處理器指令集規格和技術細節鉅細靡遺地正確批配才能完成。在處理器運算技術不斷推陳出新的今天,要完整的納入所有CPU指令集的優化顯然是一件曠日廢時的工作。甚至工程師在編譯器指令集優化支援的本身,有可能還趕不上處理器架構的演進。
當然有想法自然就會有一些方案被提出,而一些比較彈性的作法漸漸被獲得重視。比如說虛擬化技術,在硬體層之外再包上一層名之為虛擬機器(Virtual Machine)的抽象層,而編譯器只要將程式碼編譯成中間碼(如Bytecode),進而就可以相對達到跨平台或跨指令架構的目的。或是動態編譯技術(Dynamic Compiliation),如JIT(Just-in-time)編譯器,在中間碼(Bytecode)進入執行階段(Runtime)前,才將Bytecode編譯成給最終硬體執行的微碼(Microcode)。等等~等,你這個說的是Java Virtual Machine吧!?嘿嘿,這個梗撲到這裡就是企圖讓大家這樣猜。其實也沒說錯,編譯器編譯朝硬體架構抽象化的發展,確實很多是源自於Java語言才開始有的想法。早期我們知道Java語言為了建構可攜性與跨平台的目的,結果卻往往就是得付出效能不彰的代價。儘管虛擬機器優化以及後來漸漸Java語言也開始支援對硬體的直接存取呼叫(如GPU),然而進入執行階段前的預處理仍然是Bytecode運作上免不了的時間耗損。而傳聞中Java處理器的計畫卻仍然一直停留在樓梯響的階段,筆者自始未曾見過商業化的產品出現在終端消費市場。
假設跨平台以及可攜性不再是編譯器最重要的唯一訴求時,是否還有更具備彈性的編譯器解決方案呢!?自2000年起一個名為LLVM(Low Level Virtual Machine)的專案開始啟動,而在2005年該專案獲得Apple公司的支持與採用,隨即很快地被納入Xcode作為編譯器的選項之一。底層虛擬機器(LLVM)承自虛擬化技術,建構了編譯階段(Compile-time),鏈結階段(Link-time),執行階段(Run-time)以及閒置階段(Idle-time)的最佳化。但與Java虛擬機器(JVM)不同的是,LLVM仍然允許程式碼依照需要進行靜態編譯,就如同傳統GCC架構的方式一樣。而JIT(Just-in-time)編譯的方面,LLVM進一步允許執行階段進行量變分析(Profiling),以利針對特定的硬體架構進行更積極的優化處理。而LLVM本身,也可以僅僅扮演編譯器系統的中介層角色,意即前端(frontend)介面可以依據需要替換。例如保留傳統前端GCC分析器與LLVM中介層這樣的搭配組合;或是針對C語言家族(特別是Object-C)所開發的前端解析器Clang,加上LLVM中介層的組合。
一直以來,筆者總覺得編譯器是個既神祕又神奇的東西。因為編譯器在程式撰寫者看不到的地方偷偷地做了很多事情。而編譯器的採用也與程式碼最後執行階段的效能有很直接的關係。無論是智慧型手機,平板電腦,或是其他相類的嵌入式系統,這些設備在設計上多樣化的硬體配置和架構組合是可以想見的未來趨勢。資訊科技的發展似乎總不斷地進行著翻天覆地的變化,而這些工程上的成就給與了我們未來有很大的想像空間。
本文作者為:Exiliar, 同步發表於 iOSBible.com 及 Yahoo 3C 頻道。