Clang 比 GCC 好在哪裡?
其實llvm對比gcc最大的優勢是:
license
沒有這個根本發展不到現在這個狀況,要不是作死的GPLv3,llvm作為後來者相比gcc那些代差優勢根本不足以讓那麼多廠商轉投llvm懷抱。
所以,做技術的同學不要以為技術牛就可以打天下,精準的市場地位有時候可以解決很多問題
update1:
就著這個話題繼續說,除了license,llvm的第二優勢是什麼呢?答案是Clang。
在clang之前,EDG一統江湖,基本上能做得比他們好的比他們貴(自己養個團隊做前端很花錢),比他們便宜的沒他們做得好。但是EDG總共才幾條槍,客戶對應慢不說,還不生成IR,最多給你提供一些IR的建議。所以clang的出現很好的彌補了這個問題。所以你看sony就只用clang而不要後面的。update2:
接下來才到第三點,也就是上面很多同學講的:llvm ir的良好定義,良好的模塊化等等。這些優點使得非晶元廠商尤其是很多開源項目可以方便的快去定製和開發,所以比gcc友好得多。
然而這點對於晶元商並非決定性的,畢竟他們都有自己用於炫技的編譯器,llvm做的優化等等他們都做過,並且很可能不會比llvm差。當然了,綜上優點,作為通用編譯器還是個不錯的選擇,所以你可以看到諸如intel這種公司都開始基於llvm來做後端了。
但是廠商的需求和llvm本身的想法還是有蠻大的差距,導致在llvm上開發也不是那麼爽。這裡講個新鮮熱乎的例子:
前幾天的llvm開發者大會,intel準備了一系列(大概3,4個)talks,bof等來介紹他們基於llvm和openmp4.5做的vectorization的工作。中間有一個是具體講後端方面工作的。intel的做法和我之前做過的類似,就是定義大量的intrinsics,向上暴露體系細節,使得可以在較高的層次做優化。因為有些優化需要比如控制流,數據流,別名的信息,太低層這些信息就沒了,很難做優化。
結果剛開講就被貌似是Christ lattner打斷了(我當時正處於倒時差的兩眼昏花中,離得還遠,本來視力就不好,看著像是他,但無法肯定 )。總之就是說,你們這樣做不符合intrinsic的設計理念啊,這個設計出來是用於一些公共的向下lower的操作或者routine的等等。有人附和說,這樣設計和嵌入彙編相差不大了吧。intel的人就反駁這樣做是為了優化巴拉巴拉。結果最後整場變成了設計review,莔rz。而且還沒review出任何結論,想來也是,Intel會在乎你的設計理念是啥嗎?zr莔莔rz這個例子想說明的就是廠商用llvm的目的是做後端,優化優化再優化。並不在乎llvm設計上的美學追求,這點上其實llvm後端的層次確實也太多了點。中間端llvmir一統天下的美好完全消失了。
後端里,llvmir進來先是SelectionDAG然後變成MI,做調度又來個ScheduleDAG最後到MC layer。做個後端ir變來變去,還有ssa,de-ssa的處理,更別說還一層層的丟信息,確實比較痛苦。換大家都想做global優化的現在真是誰用誰難受。
不過llvm應該也意識到了這個問題。新的ISel會改到Global Instruction Selection,變成llvmir &> G-MI &> MI &> MC這樣,不過目前還不大清楚GMI到MI會不會丟以及丟什麼信息,暫時不知道能有多大改善。
update3:
繼續接著上面的話題更新一波,照例先講個這次大會上的小例子。這次有場是apple和google(長腿妹子喲)聯袂介紹ThinLTO的進展。編過debug版llvm的應該都對它最後漫長以及極占內存的鏈接過程有印象。ThinLTO就是為了解決這個問題。
具體來說,他們在llvmir之後先生成bitcode,然後為每份bitcode生成一個summary,然後ThinLTO僅讀取summary並據此將互相調用的部分連成多份bitcode,然後各自進行後端的處理並最終鏈接起來。這樣既可以進行鏈接優化也因為讀的是summary而不是完整的llvmir可以大大減少內存消耗和時間。
看上去是個不錯的辦法不是嗎?然而聽的時候旁邊一哥們扶著腦門吐槽:oh no, another layer?
為什麼會引起吐槽呢?這個要從ir的目的說起,這裡我們不去提ir對於編譯器分析優化便利方面的問題,比如ssa形式可以大大減少數據流分析的複雜度。
僅說對於一個跨體系的編譯器來說,我們希望將體系相關的信息儘可能的限制在有限的範圍內(通常是越底層越好)。而在更高層面對沒有體系細節的ir,以此可以使絕大部分優化變得體系無關,從而開發一個pass就可以優化所有的體系。很理想很美好不是嗎?為了實現這個目的,我們通常會需要一個體系細節都被抽象掉的ir,然後逐漸下降,僅在必要的時候引入體系細節來進行優化,於是就有了很多的ir層次。然而對於具體體系的後端開發者來說,需要的是在任何有必要的時候進行針對本體系的優化。其他體系優化的好不好跟我有關嗎?所有的優化和信息都應該為我的體系相關優化服務。
我好不容易生成了漂亮的指令,現在我需要做些優化來去掉多餘的copy。什麼?你告訴我你的優化和分析信息是基於公共ir的,我的指令不能做?怒……由於這樣天然的屁股矛盾,導致編譯器架構的維護者和體系後端的維護者間有一些天然的矛盾。
前者希望保持純潔性,設計的美感,重要的是,全都給各個體系自己做了,我吃啥?
後者希望不斷往裡面添加新的特性,在各個需要的層面暴露體系的細節。於是戰爭開始,尤其是一個倍受廠商關注和支持的編譯器架構不可避免的要向擁有巨大影響力的金主妥協。為什麼gcc變得如此龐雜晦澀難懂?除了和llvm的代差以外,歷代的各種博弈也是個原因。所以,llvm相比gcc的第四大優點是足夠的年輕,也足夠的健壯,還沒有被各方變成一個複雜的充滿難以被外人理解的奇形怪狀實現的怪物。
不過現在intel各種vecterization來了,arm hpc也來了,以後會變成怎樣誰也不知道。當然,對於我等碼農,尤其是國內碼農來說,弄成啥樣也還是繼續做,大不了再換個平台,平均每10來年就會有個新平台來代替之前那個被糟蹋的老平台,上面神仙打架我們看著就好,咱湊不上去。說個題外話,不知道有生之年能不能看到國內的成功編譯器架構,咱也去參加次dev meeting,自費!
ps: update3均為個人感想,說錯請指教編譯速度更快、編譯產出更小、出錯提示更友好。尤其是在比較極端的情況下。
兩年多前曾經寫過一個Scheme解釋器,詞法分析和語法解析部分大約2000行,用的是Boost.Spirit——一個重度依賴C++模版元編程的框架。當時用g++ 4.2編譯的情況是:
- 編譯速度極慢:完整編譯一次需要20分鐘
- 編譯過程中內存消耗極大:單個g++實例內存峰值消耗超過1G
- 中間產出物極大:編譯出的所有.o文件加在一起大約1~2G,debug鏈接產物超過200M
- 編譯錯誤極其難以理解:編譯錯誤經常長達幾十K,基本不可讀,最要命的是編譯錯誤經常會長到被g++截斷,看不到真正出錯的位置,基本上只能靠裸看代碼來調試
這裡先不論我使用Spirit的方式是不是有問題,或者Spirit框架自身的問題。我當時因為實在忍受不了g++,轉而嘗試clang。當時用的是clang 2.8,剛剛可以完整編譯Boost,效果讓我很滿意:
- 編譯速度有顯著提升,記得大約是g++的1/3或1/4
- 編譯過程中的內存消耗差別好像不大
- 中間產出物及最終鏈接產物,記得也是g++的1/3或1/4
- 相較於g++,編譯錯誤可讀性有所飛躍,至少不會出現編譯錯誤過長被截斷的問題了
當時最大的缺點是clang編譯出的可執行文件無法用gdb調試,需要用調試器的時候還得用g++再編譯一遍。不過這個問題後來解決了,我不知道是clang支持了gdb還是gdb支持了clang。至少我當前在Ubuntu下用clang 3.0編譯出的二進位文件已經可以順利用gdb調試了。
最後一點,其他同學也有講到,就是Clang採用的是BSD協議。這是蘋果資助LLVM、FreeBSD淘汰GCC換用Clang的一個重要原因。從代碼上說,clang結構更簡單。因為clang只需要完成詞法和語法分析,代碼優化和機器代碼的生成工作由llvm完成。所以和全部由自己包下的gcc比起來,clang可以更專註地做好一件事。
這種結構也使clang可以被單獨拿出來用在其他的程序里。比如vim的clang_complete插件就是利用clang進行語法分析後給出精確的自動補全和語法錯誤提示的。而gcc就沒法很方便地做到這一點。
在實用性方面,除了有更快的編譯速度更快和更友好的出錯提示外,clang還內置有靜態分析工具,可以對代碼進行靜態分析(clang --analyze)。這也是gcc做不到的。除了其它答案介紹的速度和出錯信息可讀性的優勢之外,Clang 的另一個優勢是代碼結構清晰,可以作為庫使用,成為其它 app(主要是 IDE)的內嵌 C/C++ parser。這樣,editor 工具可以使用和 compiler 一樣的 parser 來完成 edit-time 的語法檢查。
GCC 的結構比較混亂。業界一直有說法是 FSF 故意如此,以便讓 GCC 無法作為其它 app 的內嵌部分。Clang是LLVM的前端,可以用來編譯C,C++,ObjectiveC等語言。傳統的編譯器通常分為三個部分,前端(frontEnd),優化器(Optimizer)和後端(backEnd)。在編譯過程中,前端主要負責詞法和語法分析,將源代碼轉化為抽象語法樹;優化器則是在前端的基礎上,對得到的中間代碼進行優化,使代碼更加高效;後端則是將已經優化的中間代碼轉化為針對各自平台的機器代碼。Clang則是以LLVM為後端的一款高效易用,並且與IDE結合很好的編譯前端。
時過境遷,許多答案已經過時,特別是關於編譯速度和錯誤提示的討論建議參考 @齊亮 回答中的兩個持續更新的鏈接。
出錯提示很友好,gcc的那個錯誤提示就不是給人看的
google "clang vs gcc"
http://clang.llvm.org/comparison.htmlhttp://gcc.gnu.org/wiki/ClangDiagnosticsComparison說clang能替代gcc的肯定沒有玩過AVR https://reviews.llvm.org/D32991 GCC for AVR的特色builtin肯定不允許支持,GNU binutils elf32avr的全套Relocation實現還沒有migrate,MC層對AVR指令支持等,但我會把avr-ld替換成NewLLD,avr-gcc就不指望替換成clang --target=avr了:)
不加任何其他參數,-S -g 產生的彙編代碼,clang 會帶有明確的注釋來表明彙編指令是源代碼哪一行那一列的產物。就憑這一點,一秒路轉粉。
ubuntu下使用過一下clang,錯誤提示各種人性化,還有高亮提示錯誤。但是就編譯速度還是gcc/g++要快一點,特別是多文件連接的時候。附帶gcc4.7.2,clang3.4。
到了2017,事情已經變了很多。除了BSD vs GPL那個證書問題,沒辦法。
比如一個明顯的:在競爭壓力下,現在GCC的錯誤提示有了巨大改進,從4.9開始就對錯誤提示有色彩區分、波浪號和著重號標記、代碼修改建議等等。GCC的release notes還著重強調它的進步,滿滿的都是「求再愛我一次」。現在7.1也發布了。https://gcc.gnu.org/gcc-7/changes.htmlhttps://gcc.gnu.org/gcc-6/changes.htmlLinux上試過Clang,但老是給我報鏈接錯誤,網上找到了原因,可以改,但就懶得改了..看來「在makefile里把gcc換成clang就行」(所謂」drop-in replacement」)並不總是成立的(/ω\)也許我太沒耐心了吧其實個人感覺最終要的是結構清晰。 LLVM突出了它的IR,這樣它的前後端和IR的耦合度就降低了,這樣就非常方便開發新的前端和後端。同時,LLVM基於庫的劃分也非常清晰,可以非常容易的在其框架之上建立自己的工程。同時,LLVM的主要前端Clang,還可以很方便的作為一個庫來使用。非常方便構建一些編譯方面的小工具等。至於速度比GCC快了多少倍的事情,說實話,實際測得數據沒有公布的那麼誇張。
clang是由N多庫組成,使用者可以使用它們來完成對CC++Objc代碼的很多事情,比如語法檢查,AST遍歷等等.
GCC 4.8.0 vs. LLVM Clang 3.3 Compiler Performance Three-Way這個地址 是目前比較新的 benchmark
補充一下友好錯誤提示的實例,clang在編譯過程可以直接指出相對簡單的出錯位置以及它「認為」正確的方式。語法要求稍嚴謹一些比如%d和%ld的區別。這對於初學者也好,成熟的工程師也好都是很有幫助的。另外就是還無法與gcc完全的兼容,一些軟體用clang編譯會出現莫名其妙的錯誤,但是用gcc編譯可以通過。用clang編譯的速度確實很快。明顯能感覺到。我現在一般寫程序也好或者編譯程序也好都會首先使用clang/clang++
友好的錯誤提示,中間信息(帶來補全,重構插件的可能性),編譯速度快.還有最重要的一條BSD License,看GPL就不爽.clang還有靜態分析,gcc沒這功能
一些優化做的比gcc到位。
雖然gcc在給出足夠提示之後也能優化,但是明顯沒有clang那麼機智。
不過這大概是llvm的功勞?推薦閱讀:
※Partial Evaluation, Constant Propagation, AI的關係是什麼?
※有沒有在 UB 和 ID 上處處和「習慣認知」不同的 C/C++ 編譯器?
※如何將lisp/scheme翻譯成llvm IR,並通過llvm生成機器碼?
※linux下編程,定義數組一多就會崩潰,如何解決?
※(截止2014.8.21)windows平台上完成一個編譯器(詞法、語法分析),想使用C++11開發,有啥好的技術推薦嗎?