用C语言编写编译器的语言怎么会比C更快呢?


175

查看Julia的网页,您可以看到几种语言在多种算法中的一些基准(时序如下所示)。最初使用C语言编写的带有编译器的语言如何胜过C代码?

在此处输入图片说明 图:相对于C的基准时间(越小越好,C性能= 1.0)。



382
作为人造物体的汽车如何比人类移动得更快?
2015年

19
根据表,Python比C慢。您认为用Python编写与您喜欢的C编译器生成相同代码的C编译器是不可能的吗?那是用什么语言写的?
卡斯顿S

6
babou的评论颇受关注,但我认为我们不需要相同的多个版本。
拉斐尔

14
一个相关的想法。许多编译器是自托管的,这意味着它们是用自己的语言(通常加上某种汇编语言)编写的,并使用其自身的先前版本进行编译。然而,编译器变得越来越好。精神打击
Schwern

Answers:


263

编译器的实现与编译器的输出之间没有必要的关系。你可以写在像Python或Ruby,其最常见的实现是很慢的一个语言的编译器,而编译器可以输出高度优化的能够跑赢C.编译器本身就需要很长时间来运行的机器代码,因为它的代码以慢速语言编写。(更准确地说,是用慢速执行的语言编写的。正如拉斐尔在评论中指出的那样,语言并不是天生就快或慢。我在下面对此进行扩展。)编译后的程序将与其运行速度一样快。允许自己的实现-我们可以用Python编写一个编译器,该编译器生成与Fortran编译器相同的机器代码,并且即使编译时间很长,我们的编译程序也将与Fortran一样快。

如果我们谈论的是口译员,那就另当别论了。解释程序必须在解释程序运行时运行,因此在实现解释程序的语言与解释代码的性能之间存在联系。要使解释后的语言运行得比实现解释器时所用的语言快,就需要进行一些巧妙的运行时优化,最终的性能取决于代码对这种优化的满意程度。许多语言(例如Java和C#)将运行时与混合模型一起使用,该模型将解释器的某些优点与编译器的某些优点结合在一起。

作为一个具体的例子,让我们更仔细地看一下Python。Python有几种实现。最常见的是CPython,它是用C编写的字节码解释器。还有PyPy,它是用称为RPython的Python专用方言编写的,它使用类似于JVM的混合编译模型。在大多数基准测试中,PyPy比CPython快得多。它使用各种惊人的技巧在运行时优化代码。但是,PyPy运行的Python语言与CPython运行的Python语言完全相同,除非有一些不影响性能的差异。

假设我们用Python语言为Fortran编写了一个编译器。我们的编译器产生与GFortran相同的机器代码。现在,我们编译一个Fortran程序。我们可以在CPython之上运行编译器,也可以在PyPy上运行,因为它是用Python编写的,并且这两种实现都运行相同的Python语言。我们会发现,如果我们在CPython上运行编译器,然后在PyPy上运行,然后使用GFortran编译相同的Fortran源代码,我们将获得完全相同的机器代码,因此,编译过的程序将始终运行以大约相同的速度。但是,生成该编译程序所需的时间将有所不同。CPython可能比PyPy花费更长的时间,而PyPy可能比GFortran花费更长的时间,即使它们最后都将输出相同的机器代码。

通过扫描Julia网站的基准测试表,看来解释器上运行的所有语言(Python,R,Matlab / Octave,Javascript)都没有比C更好的基准测试。这通常与我所期望的一致,尽管我可以想象用Python的高度优化的Numpy库(用C和Fortran编写)编写的代码会击败类似代码的某些C实现。正在编译等于或优于C的语言(Fortran,Julia)或使用带有部分编译的混合模型(Java,可能还有LuaJIT)。PyPy还使用了混合模型,因此如果我们在PyPy而非CPython上运行相同的Python代码,则完全有可能在某些基准测试中击败C。


9
这是一个了不起的答案。非常清晰,可理解且信息丰富。非常感谢您抽出宝贵的时间来编写它!
Alex A.

7
javascript和java都使用JIT编译器运行,但是java进行了一项测试,测试比Java更快。运行时/编译器可以更快运行的最大原因是拥有更多信息。C / C ++编译器(通常)比手工编写程序集的代码优化(通常)要多得多,这仅仅是因为编译器具有更多可用信息。当然,从理论上讲,该人可以编写更好的汇编代码,但是这比大多数人需要的知识和技能还要多。JIT语言可以在此基础上进行更多扩展,从而能够对其运行的确切机器进行优化
Programmdude

编译器进行的优化是要考虑的重要事项。一个真正聪明的编译器会认识到该程序是综合基准,只需简单地优化几乎所有代码,即可创建预期的输出。
ghellquist

@ghellquist当然,如果基准测试足够人为,并且编译器足够聪明。但是,这与编译器的实现语言没有直接或直接的关系,因此我在这里没有提及。
tsleyson

97

一个男人制造的机器怎么能比一个男人强大呢?这是完全相同的问题。

答案是编译器的输出取决于该编译器实现的算法,而不取决于实现该编译器的语言。您可以编写一个速度非常慢,效率低下的编译器,该编译器会产生非常有效的代码。编译器没有什么特别的:它只是一个需要一些输入并产生一些输出的程序。


33
国际象棋程序如何击败编写它的人?
托尔比约恩Ravn的安徒生

25
通过采取更好的行动!<rimshot>
Tony Ennis

用宾恩·吉列特(Penn Gilette)的答案来解释为什么计算机在国际象棋上击败男人并不重要:“您希望GE设计的机器人在拳击比赛中输给一个男人吗?”
戴夫·坎特

90

我想反对一个普遍的假设,我认为这是错误的,以至于在选择工作工具时有害。

没有慢速或快速语言之类的东西。¹

在我们实际去做CPU的路上,有很多步骤²。

  1. 至少一名具有某些技能的程序员。
  2. 他们使用的(正式)语言(“源代码”)。
  3. 他们使用的库。
  4. 将源代码转换为机器代码(编译器,解释器)的东西。
  5. 整体硬件架构,例如处理单元的数量和内存层次结构的布局。
  6. 管理硬件的操作系统。
  7. CPU上的优化。

每个项目都会对您可以衡量的实际运行时间有所贡献,有时甚至很大。不同的“语言”侧重于不同的事物³。

仅举一些例子。

  • 1比2-4:就正确性和效率而言,普通C程序员产生的代码可能比普通Java程序员差得多。那是因为程序员在C中承担更多责任

  • 1/4 vs 7:在像C这样的低级语言中,您可能可以作为程序员利用某些CPU功能。在高级语言中,只有编译器/解释器可以知道目标CPU 才能这样做。

  • 1/4 vs 5:您是否想要或必须控制内存布局以最好地使用手头的内存架构?有些语言可以控制您,有些则不能。

  • 2/4 vs 3:解释的Python本身非常慢,但是流行绑定到高度优化的,本地编译的科学计算库。因此,如果大多数工作是由这些库完成的,那么最终使用Python进行某些操作很快

  • 2 vs 4:标准的Ruby解释器很慢。另一方面,JRuby可能非常快。使用另一种编译器/解释器,这就是相同的语言。

  • 1/2 vs 4:使用编译器优化,可以将简单的代码转换为非常有效的机器代码。

最重要的是,您发现的基准没有多大意义,至少在归结到您所包含的表时至少没有意义。即使您只对运行时间感兴趣,也需要指定从程序员到CPU 的整个链。换掉任何元素都可以极大地改变结果。

明确地说,这回答了问题,因为它表明编写编译器(第4步)所用的语言只是一个难题,并且可能根本不相关(请参阅其他答案)。


  1. 当然,某些语言功能的实现成本要比其他语言功能高。但是功能的存在并不意味着您必须使用它们,而昂贵的功能可以节省许多便宜的功能的使用,并最终节省费用。(其中的其他优点是运行时无法衡量的。)
  2. 我跳过算法级别,因为它并不总是适用,并且主要独立于所使用的编程语言。请记住,例如,不同的算法更适合于不同的硬件。
  3. 我故意不在这里讨论不同的成功指标:运行时间效率,内存效率,开发人员时间,安全性,安全性(可证明的)正确性,工具支持,平台独立性,...

    即使将语言设计用于完全不同的目标,也只能将它们进行比较,这是一个巨大的谬误。


1
@babou同意,很好的解释。那么,可以使用哪种更好的度量标准或一组度量标准来比较语言与其各自的编译器/解释器呢?另外,还有一个小问题:您说“没有慢速或快速语言之类的东西”,然后又说“ Python本身太慢了”,但是我假设您的意思是Python解释器。
StrugglingProgrammer15年

2
@benalbrecht我的观点是,没有这样一套好的指标。总是要权衡取舍。如果您构建设备驱动程序,那么首先要确保它是正确的。如果您建立了Twitter的骨干力量,那么您首先要提高效率。在这两种情况下,您都使用工具并雇用允许这样做的人员。如果您是从事Android应用程序交易的初创公司,那么您将使用人们所了解的信息和/或使您的产品上市时间最短的信息。如果您教算法,则需要一种语言,该语言应具有简洁明了的语法和简洁的模板。等等。优先级不同,因此我们使用不同的语言。
拉斐尔

另请参阅此切线相关问题
拉斐尔

23

关于优化,这里有一件被遗忘的事情。

关于fortran优于C的争论由来已久。将格式错误的争论放在一起:用C和fortran编写相同的代码(正如测试人员认为的那样),并根据相同的数据测试性能。问题是,这些语言不同,C允许指针别名,而fortran不允许。

因此代码并不相同,在经过C测试的文件中没有__restrict会产生差异,在重写文件以告知编译器它可以优化指针之后,运行时变得相似。

这里的要点是,某些优化技术在新创建的语言中更容易(或开始合法)。


X

其次,VM可以在运行时执行压力测试,因此它可以提取压力代码并对其进行优化,甚至可以在运行时对其进行预先计算。预先编译的C程序不会期望压力很大,或者(大多数时间)不会为通用机器系列提供可执行文件的通用版本。

在此测试中,还有JS,而且VM的速度比V8快,并且在某些测试中它的性能也比C快。

我已经检查过了,在C编译器中还没有独特的优化技术。

C编译器必须立即对整个代码进行静态分析,然后再使用给定的平台并解决内存对齐问题。

VM只是将部分代码音译为优化的程序集并运行它。

关于Julia-我检查了它是否在AST的代码上运行,例如,GCC跳过了这一步,最近才开始从那里获取一些信息。这加上其他限制和VM技术可能会有所解释。

例子:让我们做一个简单的循环,它从变量开始到终点,并将部分变量加载到运行时已知的计算中。

C编译器从寄存器生成加载变量。
但是在运行时,这些变量是已知的,并且在执行过程中被视为常量。
因此,与其从寄存器中加载变量(并且不执行缓存,因为它可能会更改,而且从静态分析中尚不清楚),不如将它们完全视为常量并进行折叠,传播一样。


12

前面的答案几乎都是从实用角度出发进行解释的,尽管这个问题是有意义的,但拉斐尔的答案很好地解释了这个问题

除了这个答案外,我们还应该指出,当今,C编译器是用C编写的。当然,正如Raphael所指出的那样,它们的输出及其性能可能尤其取决于它所运行的CPU。但这也取决于编译器完成的优化量。如果您使用C编写了一个针对C的更好的优化编译器(然后您可以使用旧的编译器运行该编译器),那么您将获得一个新的编译器,使C成为比以前更快的语言。那么,C的速度是多少? 请注意,您甚至可以自己编译新的编译器,作为第二遍,这样,尽管仍提供相同的目标代码,但编译效率更高。而充分就业定理表明,他们是没有止境的这样的改进(感谢拉斐尔的指针)。

但是,我认为将这个问题正式化可能是值得的,因为它很好地说明了一些基本概念,尤其是事物的指称性与操作性观点。

什么是编译器?

CSTCCSTP:SP SP:T TP

CSTCST{(P:S,P:T)PSSPTT}

CSTPSPTP

P:TP:SCST

优化参数后,我们可能希望编译器具有良好的效率,以便可以在合理的时间内执行转换。因此,编译器程序的性能对用户很重要,但对语义没有影响。我说的是性能,因为某些编译器的理论复杂性可能比人们预期的要高得多。

关于自举

这将说明区别,并显示出实际应用。

SISCST:SSCST:SISP:SP:TST

CST:SSCST:TTTT


“在语义上重要的是完成的事情,而不是完成的方式(和速度)” –值得一提的是,实践中存在非功能性标准。有许多功能上等效的目标程序,但出于某些原因(效率,大小,更好的内存对齐等),我们可能更喜欢某些目标程序。也就是说,您定义的编译器作为函数的视图比我们希望的要受限制的多(它通常还会跳过副作用,例如I / O)。但是,它可以满足您的解释目的。
拉斐尔

@Raphael关于完整的就业定理,我想到了这一点(在我对C的评论中),但是我不知道这个名字,因此推迟寻找参考。谢谢你这样做。---我谈论的语义是编译器的语义,而不是目标程序的语义。在语法和操作上(不仅在语义上)保留目标程序。还是我误会了你的话。我进行了编辑,使文本中的内容更加精确。
2015年

@Raphael既然您没有删除您的评论,是否意味着我误解了它,或者没有正确回答?从语义的角度来看,编译器(而非编译程序)作为函数的视图太受限制了。当然,作为一个函数,除了已编译的程序外,它还可以采用其他参数(例如优化指令),但这是一个细节,不会改变讨论。
2015年

我认为我的评论是“没有比这个模型更多的东西”的指针。您写的内容没有错,但不是全部。从理论上讲,这似乎是显而易见的:“在”编译器功能本身并不明确,因为有无限多的可能的目标计划,所有的语义等价。选择哪个是设计编译器中非常重要的部分。
拉斐尔

CP

6

根据Blum的加速定理,有些程序是在最快的计算机/编译器组合上编写和运行的,其运行速度要比第一台运行BASIC的PC上运行的程序慢。只是没有一种“最快的语言”。您可以说的是,如果您用多种语言编写相同的算法(实现;如前所述,周围有很多不同的C编译器,我什至遇到过一个功能强大的C解释器),那么它在每种语言中的运行速度会变慢或变慢。

不可能有一个“总是较慢”的层次结构。这是每个人都精通几种语言的现象:每种编程语言都是为特定类型的应用程序设计的,并且更常用的实现已针对该类型的程序进行了优化。我非常确定,例如,一个用Perl编写的字符串鬼混的程序可能会击败用C编写的相同算法,而用C对大型整数数组进行压缩的程序将比Perl更快。


1
“每种编程语言都是为特定类型的应用程序设计的”实际上,人们实际使用的大多数编程语言都是通用语言,与针对特定应用程序设计的相反。只是某些语言最终由于某些社会影响而在某些领域中得到更多使用。
立方

我想这取决于您对“特定类型的应用程序”一词的理解范围。虽然大多数主流语言的确不是DSL,但设计时一定要考虑到某些用途。C旨在实现Unix。Java是为编写交互式电视脚本而设计的。Smalltalk专为教孩子而设计。ECMAScript专为服务器端和客户端Web脚本而设计。Perl专为文本处理和Unix脚本而设计。PHP是为服务器端Web脚本而设计的。Erlang专为可靠性而设计。计划的目的是为探索...
约尔格W¯¯米塔格

…OO和Actor模型的基础。APL被设计为用于数学教学的符号。朱莉娅是专为科学编程而设计的。当然,所有这些语言现在都在其原始问题域之外使用,但是这些语言中仍然存在一些属性,这些属性使它们对于某些类型的应用程序而言或多或少地适合,尽管它们都可以用于构建各种类型的应用程序。东西。
约尔格W¯¯米塔格

4

让我们回到原始行:“用C编写编译器的语言怎么会比C更快?” 我认为这真的是要说:用Julia编写的程序(用C编写的核心)比用C编写的程序还要快?具体来说,用Julia编写的“ mandel”程序如何在用C编写的等效“ mandel”程序的87%的运行时间中运行?

到目前为止,Babou的论文是对该问题的唯一正确答案。到目前为止,所有其他答复或多或少都在回答其他问题。babou的文章的问题在于,对“什么是编译器”进行了多段篇幅的理论描述,其用语是原始海报可能难以理解。掌握“语义”,“象征性”,“实现”,“可计算”等词语所指概念的任何人都已经知道该问题的答案。

较简单的答案是C代码或Julia代码均不能直接由计算机执行。两者都必须进行翻译,并且该翻译过程引入了许多方法,可以使可执行的机器代码变慢或变快,但仍产生相同的最终结果。C和Julia都进行编译,这意味着将一系列翻译转换为另一种形式。通常,人类可读的文本文件会转换为某种内部表示形式,然后作为计算机可以直接理解的一系列指令写出。对于某些语言,它还不止于此,而且Julia是其中之一-它具有“ JIT”编译器,这意味着整个翻译过程不必整个过程都一次完成。但是,任何语言的最终结果都是不需要进一步翻译的机器代码,可以直接发送给CPU的代码以使其执行某些操作。最后,这就是“计算”,有多种方法可以告诉CPU如何获得所需的答案。

可以想象一种既有“加”运算符又有“乘”运算符的编程语言,以及只有“加”运算符的另一种语言。如果您的计算需要乘法,那么一种语言将会“慢”,因为CPU当然可以直接执行这两种操作,但是如果您没有任何办法表示需要乘以5 * 5,则只需写“ 5”即可。 + 5 + 5 + 5 + 5“。后者将花费更多时间来获得相同的答案。大概,朱莉娅(Julia)正在进行其中的一些事情。也许该语言允许程序员以无法直接用C语言表达的方式陈述计算Mandelbrot集的预期目标。

用于基准测试的处理器被列为Xeon E7-8850 2.00GHz CPU。C基准测试使用gcc 4.8.2编译器为该CPU生成指令,而Julia使用LLVM编译器框架。gcc的后端(为特定CPU架构生成机器代码的部分)在某种程度上可能不如LLVM后端先进。这可能会影响性能。还有许多其他事情在发生-编译器可以通过以不同于程序员指定的顺序发布指令来“优化”,或者如果它可以分析代码并确定它们不是,则根本不做任何事情。需要获得正确答案。而且程序员可能以使其变慢的方式编写了C程序的一部分,但并没有

所有这些都是说的方式:有很多方法可以编写用于计算Mandelbrot集的机器代码,并且所使用的语言对如何编写机器代码具有重要影响。您对编译,指令集,缓存等了解得越多,获得所需结果的能力就越强。从Julia所引用的基准测试结果中得出的主要结论是,没有一种语言或工具可以胜任所有工作。实际上,整个图表中最好的速度因素是Java!


2

编译程序的速度取决于两件事:

  1. 执行它的机器的性能特征
  2. 可执行文件的内容

编写编译器所用的语言与(1)无关。例如,可以使用C或Java或Python编写Java编译器,但是在所有情况下,执行程序的“机器”都是JVM。

编写编译器所用的语言与(2)无关。例如,没有理由为什么用Python编写的C编译器不能输出与用C或Java编写的C编译器完全相同的可执行文件。


1

我将尝试提供一个简短的答案。

问题的核心在于语言的“速度”的定义

大多数(如果不是全部)速度比较测试都不会测试最大可能速度。相反,他们用要测试的语言编写一个小程序来解决问题。在编写程序时,程序员会在测试时使用假定的*作为最佳实践和该语言的约定。然后他们测量程序执行的速度。

*这些假设有时是错误的。


0

用X语言编写的代码(其编译器是用C编写的)可以胜过用C语言编写的代码,但前提是,与语言X相比,C编译器的优化效果较差。如果我们不进行优化讨论,那么X的编译器能否产生更好的性能目标代码要比C编译器生成的目标代码好,那么用X编写的代码也可能赢得比赛。

但是,如果语言X是一种解释性语言,并且解释器是用C编写的,并且我们假设语言X的解释器和用C编写的代码是由同一C编译器编译的,那么用X编写的代码绝不会胜过代码如果两个实现都遵循相同的算法并使用等效的数据结构,则用C语言编写。


2
与以前的答案相比,这有什么用?另外,由于其他答案中提到的原因,我不认为您的第二段是正确的。
拉斐尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.