編譯器漫談

編譯器漫談

最近藉由一次R包安裝失敗,我又重新收集了下關於編譯的概念與知識,為了讓下一次失憶的我不至於再折騰半天,就歸納到這裡。

C語言源碼編譯運行過程是這樣的:先預處理源碼,調入模塊,然後轉換成彙編語言文件,彙編語言文件可以被彙編器轉為機器碼,然後通過連接器合併為可執行文件,最後加載到內存裡運行。因為C語言是多數操作系統的基礎,所以多數操作系統也自帶對應的C編譯器。更重要的是,C語言的庫很豐富,也就是工具函數比較多,換別的就得自己寫。這是很有意思的路徑依賴案例,事實上任何語言都應該可以拿來寫操作系統,不過C是在開發Unix時候設計出來的,現在流行的開發級操作系統都是unix/類unix操作系統,加上C在內存管理與CPU交互上的先天優勢,歷史機遇下成為了主流。

C的核心地位還體現在很多高級語言的編譯器是構建在C之上的,或者是C++之上,多數高級語言都通過限制自由度(例如不讓操作內存、功能模塊化等)來實現上手容易與較高的開發效率,但只要關注程序性能,肯定回去學C或C++的。GNU的GCC編譯器是一個相對通用的編譯器合集,可以用來編譯包括C在內的多種語言。然而,GCC也是有歷史包袱的,所有有人就另起爐灶單獨針對C或C++重寫了效率更高的編譯器及其後臺,這就是蘋果的LLVM項目與clang編譯器。但要注意的是LLVM支持的語言不如GCC多,所以如果你還要用到fortain或java編譯器,那就還是老老實實用GCC吧,或者cmake的時候分別指定編譯器,只要你不嫌麻煩。一般而言,效率與性能往往不能兼得。

高級語言的編譯過程跟相對底層的C或C++是不一樣的,Java就是自己定義了一套運行環境JVM,編譯出的文件也是JVM可讀的,這就提高了Java的可移植性,降低了跨平臺開發的難度,當然你得保證這些平臺上可以運行JVM。其實很多高級語言是解釋型的,REPL裡可直接運行代碼,但同樣會有人為高級語言寫編譯器來提高運行性能,這個是按需求來。我個人感覺是用REPL的人一般是應用層的,關心有沒有滿足自己需求的函數;用編譯語言的人一般是開發層的,關心軟件工程及性能。然而,高級語言裡如果打算提高運行效率,也會提供C或C++的接口讓程序員可以通過外力來提高自由度。過度的功能封裝實際也限制了高級語言的應用場景。

說到效率,自然少不了並行計算,openmp就是一種並行化方案,可以支撐C與C++。很多R包會通過使用 openmp 來底層加速算法,但這樣的包一般都需要單獨編譯。目前GCC與Clang在編譯器層其實都實現了對 openmp 的支持,編譯時加上 -fopenmp 就可以。不過 mac os 自帶的編譯器是沒有這個功能的,所以你需要 homebrew 來自己安裝這些支持 openmp 的編譯器然後在 .R/Makevars 裡把默認編譯器換成新的就可以了。

例如你裝了llvm/Clang,可以寫上:

CC=/usr/local/opt/llvm/bin/clang 

CXX=/usr/local/opt/llvm/bin/clang++
CXX11=/usr/local/opt/llvm/bin/clang++

或者GCC版(注意版本要對應):

CC=/usr/local/bin/gcc-8
CXX=/usr/local/bin/gcc-8
CXX11=/usr/local/bin/gcc-8

或者更簡單的方法就是不用並行計算,直接在.R/Makevars 裡參數留空強制跳過對openmp的編譯要求:

SHLIB_OPENMP_CFLAGS=
SHLIB_OPENMP_CXXFLAGS=

同樣的道理可以用在開發上,如果你編寫R包涉及了相關並行計算功能,需要在src目錄下創建Makevars文件來幫助用戶提前配置編譯參數,不過這方面我就沒經驗了。

來源:於淼 / https://yufree.cn/cn/2019/02/14/compiler/ ,只作分享,不作任何商業用途,版權歸原作者所有


分享到:


相關文章: