Clang 比 GCC 好在哪里?

关注者
1,960
被浏览
1,037,701

27 个回答

其实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编译的情况是:

  1. 编译速度极慢:完整编译一次需要20分钟
  2. 编译过程中内存消耗极大:单个g++实例内存峰值消耗超过1G
  3. 中间产出物极大:编译出的所有.o文件加在一起大约1~2G,debug链接产物超过200M
  4. 编译错误极其难以理解:编译错误经常长达几十K,基本不可读,最要命的是编译错误经常会长到被g++截断,看不到真正出错的位置,基本上只能靠裸看代码来调试

这里先不论我使用Spirit的方式是不是有问题,或者Spirit框架自身的问题。我当时因为实在忍受不了g++,转而尝试clang。当时用的是clang 2.8,刚刚可以完整编译Boost,效果让我很满意:

  1. 编译速度有显著提升,记得大约是g++的1/3或1/4
  2. 编译过程中的内存消耗差别好像不大
  3. 中间产出物及最终链接产物,记得也是g++的1/3或1/4
  4. 相较于g++,编译错误可读性有所飞跃,至少不会出现编译错误过长被截断的问题了

当时最大的缺点是clang编译出的可执行文件无法用gdb调试,需要用调试器的时候还得用g++再编译一遍。不过这个问题后来解决了,我不知道是clang支持了gdb还是gdb支持了clang。至少我当前在Ubuntu下用clang 3.0编译出的二进制文件已经可以顺利用gdb调试了。

最后一点,其他同学也有讲到,就是Clang采用的是BSD协议。这是苹果资助LLVM、FreeBSD淘汰GCC换用Clang的一个重要原因。