为什么某些编程语言比其他编程语言“更快”或“更慢”?


80

我已经注意到,某些基于编程语言(例如C ++ / Rust)构建的应用程序或算法比在同一台机器上运行的基于Java / Node.js的应用程序或算法运行得更快或更灵活。我对此有一些疑问:

  1. 为什么会这样?
  2. 是什么决定了编程语言的“速度”?
  3. 这和内存管理有关系吗?

如果有人为我解决了这个问题,我将不胜感激。


18
请注意,尤其是对于JVM和CLR,已经对优化VM进行了广泛的研究,它们在许多情况下都可以胜过已编译的C ++,但是容易天真地进行错误的基准测试。
chrylis -strike


26
也就是说,将Java和与Javascript相关的任何内容捆绑在一起是侮辱性的。
拉斐尔

5
在将其关闭为过于广泛(可以编写有关相关主题的教科书!)或重复使用之间,我感到非常痛苦。请社区投票!
拉斐尔

4
这个问题也非常相似,它决定了编程语言的速度
vzn

Answers:


95

在编程语言的设计和实现中,有很多选择会影响性能。我只说几句。

每种语言最终都必须通过执行机器代码来运行。诸如C ++之类的“编译”语言在编译时仅被解析,解码和转换为机器代码一次。如果以直接方式实施,则“解释的”语言将在运行时,每次,每次都被解码。也就是说,每次我们运行一条语句时,解释器都必须检查它是if-then-else还是赋值等,并采取相应的措施。这意味着,如果我们循环100次,我们将解码相同的代码100次,这会浪费时间。幸运的是,口译人员经常通过例如即时编译系统来优化此功能。(更正确地说,没有“编译”或“解释”语言之类的东西-它是实现的属性,而不是语言的属性。不过,

不同的编译器/解释器执行不同的优化。

如果该语言具有自动内存管理功能,则其实现必须执行垃圾回收。这具有运行时成本,但使程序员不必执行容易出错的任务。

一种语言可能更接近机器,从而使专业程序员可以对所有内容进行微优化,并从CPU中挤出更多性能。但是,这在实践中实际上是否有好处是有争议的,因为大多数程序员并没有真正进行微优化,而且与其他普通程序员相比,编译器通常可以更好地优化高级语言。(但是,有时离机器较远也可能有其好处!例如,Haskell具有很高的水平,但是由于其设计选择,它能够具有非常轻的绿色线程。)

静态类型检查也可以帮助优化。在一种动态类型的解释语言中,每次进行计算时x - y,解释器通常必须检查两者是否x,y均为数字,否则(例如)引发异常。如果在编译时已检查类型,则可以跳过此检查。

某些语言总是以合理的方式报告运行时错误。如果使用只有20个元素的a[100]Java 编写a,则会出现运行时异常。在C语言中,这将导致未定义的行为,这意味着程序可能会崩溃,覆盖内存中的某些随机数据,甚至完全执行其他任何操作(ISO C标准没有任何限制)。这需要运行时检查,但是为程序员提供了更好的语义。

但是,请记住,在评估一种语言时,性能并不是全部。不要迷恋它。尝试对所有内容进行微优化是一个常见的陷阱,但却无法发现正在使用效率低下的算法/数据结构。克努斯曾说过 “过早的优化是万恶之源”。

不要低估正确编写程序的难度。通常,最好选择一种具有更人性化语义的“慢速”语言。此外,如果有一些特定的性能关键部分,则始终可以用另一种语言来实现。作为参考,在2016 ICFP编程竞赛中,获奖者使用的语言是:

1   700327  Unagi                       Java,C++,C#,PHP,Haskell
2   268752  天羽々斬                     C++, Ruby, Python, Haskell, Java, JavaScript
3   243456  Cult of the Bound Variable  C++, Standard ML, Python

他们都没有使用一种语言。


81
Knuth引用的一整个版本是:“程序员浪费大量时间来思考或担心程序非关键部分的速度,而在考虑调试和维护时,这些提高效率的尝试实际上会产生严重的负面影响。我们应该忘记效率低下的问题,大约有97%的时间是这样:过早的优化是万恶之源,但我们不应该在这3%的临界水平上放弃机会。”
德里克·埃尔金斯

6
另外,某些语言保证看似纯真的东西会影响编译器的优化能力,请参阅C ++与FORTRAN中的严格混叠
PlasmaHH

9
加入,所以我可以投票@DerekElkins的评论。该报价的上下文常常会丢失,有时甚至会得出一个结论,即它主张所有优化都是邪恶的。
管道

9
具有讽刺意味的是,@ DerekElkins,您可能错过了最重要的部分:紧随其后的两个句子。“一个好的程序员不会因这种推理而沾沾自喜,他应该明智地仔细查看关键代码;但是只有在确定了该代码之后,才是明智的选择。对一个代码的哪些部分进行先验判断通常是一个错误。程序是非常关键的,因为一直使用测量工具的程序员的普遍经验是他们的直观猜测会失败。” PDF
p268

9
@DerekElkins要明确地说,我不想在您的口中说出您是在声称这一点,但是我经常遇到人们在基本报价中加了一点点,并认为Knuth提倡过早优化3的时候,当整篇文章讲清楚他一贯反对过早优化%,几乎总是反对小的优化,但在部分提倡小的优化测量的关键。
8bittree

18

是什么决定了编程语言的“速度”?

没有编程语言的“速度”。由特定程序编写的特定程序的速度只有在特定环境中运行的特定执行引擎的特定实现的特定版本才能执行。

使用不同的实现在同一台计算机上运行用同一语言编写的相同代码可能会有巨大的性能差异。甚至使用同一实现的不同版本。例如,使用10年前的版本和今年的版本在完全相同的计算机上运行完全相同的ECMAScript基准测试,可能会导致性能提高2倍至5倍,甚至10倍。这是否意味着ECMAScript比ECMAScript快2倍,因为在同一台计算机上运行相同程序的新实现快2倍了?那没有道理。

这和内存管理有关系吗?

并不是的。

为什么会这样?

资源。钱。与整个PHP,Ruby和Python社区加起来,他们在VM上工作的人相比,Microsoft可能为编译器程序员雇用更多的人。

对于某种程度上以某种方式影响性能的编程语言功能,也有一个解决方案。例如,C(我在这里使用C作为一类类似语言的替代,其中一些甚至在C之前就存在)不是内存安全的,因此,同时运行的多个C程序可能会踩踏彼此的记忆。因此,我们发明了虚拟内存,并使所有C程序都经过一个间接层,以便它们可以假装它们是计算机上唯一运行的程序。但是,这很慢,因此,我们发明了MMU,并在硬件中实现虚拟内存以加快速度。

但!内存安全语言不需要所有这些!拥有虚拟内存并不能帮助他们一点。实际上,情况更糟:虚拟内存不仅不能帮助使用内存安全的语言,而且即使以硬件实现,虚拟内存仍然会影响性能。它对垃圾收集器的性能尤其有害(这是大量内存安全语言的实现所使用的)。

另一个例子:现代主流通用CPU采用复杂的技巧来减少高速缓存未命中的频率。其中许多技巧都试图预测将来要执行的代码和所需的内存。但是,对于具有高度运行时多态性的语言(例如OO语言),真的很难预测这些访问模式。

但是,还有另一种方法:缓存未命中的总成本是缓存未命中的数量乘以单个缓存未命中的成本。主流CPU试图减少未命中的次数,但是如果您可以减少单个未命中的成本怎么办?

Azul Vega-3 CPU是专门为运行虚拟化JVM设计的,它具有功能非常强大的MMU,其中包含一些专门的指令,可帮助垃圾回收和转义检测(动态等效于静态转义分析),功能强大的内存控制器以及整个系统仍可以解决超过20000个未完成的缓存未命中的问题。不幸的是,像大多数特定于语言的CPU一样,其设计只是被“巨人”英特尔,AMD,IBM等公司用光了用力。

CPU体系结构只是一个示例,它影响了一种语言的高性能实现的难易程度。像C,C ++,D,Rust这样适合现代主流CPU编程模型的语言比像Java,ECMAScript,Python,Ruby这样的必须“战斗”并规避CPU的语言更容易快速实现。 ,PHP。

真的,这都是金钱问题。如果您花等量的钱在ECMAScript中开发高性能算法,则ECMAScript的高性能实现是为ECMAScript设计的高性能操作系统,为ECMAScript设计的高性能CPU在过去已经花费了很多。数十年的时间来使类C语言快速发展,那么您可能会发现性能平等。只是,此时,与快速使ECMAScript类似的语言相比,快速使类似于C的语言花费了更多的钱,并且从MMU和CPU到操作系统以及整个C语言的假设已经融入了整个C语言中。虚拟内存系统,直至库和框架。

我个人最熟悉Ruby(通常被认为是“慢语言”),所以我举两个例子:HashRubinius中的类(Ruby中的中央数据结构之一,键值字典)。 Ruby实现是用100%纯Ruby编写的,其性能与HashYARV(使用最广泛的实现)中的类,是用C编写的。还有一个图像处理库,它是YARV的C扩展编写的,对于没有实现的实现,也有一个(缓慢的)纯Ruby“后备版本”不支持使用大量高动态性和反射性Ruby技巧的C;JRuby的一个实验分支利用Oracle Labs的Truffle AST解释器框架和Graal JIT编译框架,可以执行纯Ruby“后备版本”,就像YARV可以执行原始的高度优化的C版本一样快。这只是(有些东西除外)由一些非常聪明的人通过动态运行时优化,JIT编译和部分评估来做一些非常聪明的事情而实现的。


4
尽管从技术上讲,语言并不是天生就快,但某些语言更着重于允许程序员编写快速代码。C主要针对CPU进行了优化,而不是相反。例如,int尽管出于性能考虑,C选择了固定大小s,尽管事实上无限制的整数(如Python使用的整数)在数学上更为自然。在硬件中实现无界整数不会像固定大小的整数那样快。试图隐藏实现细节的语言需要复杂的优化才能接近朴素的C实现。
gmatht

1
C有一个历史-创建C是为了使用汇编语言编写的系统可以移植到其他系统。因此,最初的目的是为Unix提供一个“便携式汇编程序”,并且设计得非常好。从1980年至1995年,它的性能如此之好,以至于Windows 95出现时,它具有临界质量。
托尔比约恩Ravn的安德森

1
我不同意关于资源的评论。重要的不是团队中的人数,而是团队中最好的人的技能水平。
Michael Kay

“微软可能会为编译器程序员雇用更多的人,而不是整个PHP,Ruby和Python社区加起来都是在他们的VM上工作的人。”我想这取决于您愿意为“编译器程序员”这个名词扩展多长时间您包括了多少(Microsoft开发了很多编译器)。例如,仅VS C ++ 编译器团队是相对较小的AFAIK。
立方'18年

7

从理论上讲,如果您编写的代码在两种语言中具有完全相同的功能,并且都使用“完美”的编译器进行编译,则两者的性能应该相同。

在实践中,性能会有所不同的原因有几个:

  1. 有些语言更难优化。

    这特别包括使代码更具动态性的功能(动态键入,虚拟方法,函数指针),还包括例如垃圾回收。

    有多种方法可以使使用此类功能的代码快速运行,但是与不使用它们相比,它通常最终至少会慢一些。

  2. 一些语言实现必须在运行时进行一些编译。

    这尤其适用于具有虚拟机的语言(例如Java)和执行源代码的语言,而没有二进制的中间步骤(例如JavaScript)。

    这样的语言实现必须在运行时做更多的工作,这会影响性能,尤其是启动后的时间/启动后的性能。

  3. 语言实现有意地花费了更少的时间进行优化。

    所有编译器都关心编译器本身的性能,而不仅仅是生成的代码。这对于运行时编译器(JIT编译器)尤其重要,因为运行时编译器花费的时间太长,从而降低了应用程序的执行速度。但是它也适用于提前编译器,例如C ++一样。例如,寄存器分配是一个NP完全问题,因此不能完美地解决它,而是使用在合理的时间内执行的启发式方法。

  4. 不同语言中的成语差异。

    使用通用库以某种语言通用格式编写的代码(惯用代码)可能会导致性能差异,因为这种惯用代码在每种语言中的行为都略有不同。

    例如,请考虑vector[i]使用C ++,list[i]C#和list.get(i)Java。C ++代码可能不执行范围检查,也不执行虚拟调用,C#代码执行范围检查,但不执行虚拟调用,而Java代码执行范围检查,这是一个虚拟调用。三种语言都支持虚拟和非虚拟方法,并且C ++和C#在访问基础数组时都可以包括范围检查或避免使用范围检查。但是这些语言的标准库决定以不同的方式编写这些等效的函数,因此,它们的性能将有所不同。

  5. 一些编译器可能缺少一些优化。

    编译器作者的资源有限,因此即使忽略其他问题,他们也无法实现所有可能的优化。他们拥有的资源可能会比其他领域更专注于一个优化领域。因此,即使没有根本原因使一种语言比另一种语言更快速或更容易优化,使用不同语言编写的代码由于其编译器的差异而可能具有不同的性能。


有些语言不具有一个编译器。对于某些语言,没有编译器(参考)。
拉斐尔

3
对于某些语言,不能有静态编译器。动态编译不受静态属性不可确定性的影响。
约尔格W¯¯米塔格

如果语言足够不同,则它们没有“功能完全相同的代码”。他们可能具有产生相同输出或用户可观察到的行为的代码,这不是同一回事。所以我不同意你的前提。
einpoklum

@einpoklum我看不到区别。除了一些我认为在这里没有引起兴趣的异常(例如线程同步)之外,语言规范通常允许进行任何保留可见行为的优化。您想到的是用户无法观察到但无法优化的行为?
svick

从理论上讲,可以通过生成由解释器和程序源代码组成的EXE文件来“编译”任何解释语言。
dan04 '17

1

这是一个非常普遍的问题,但是对于您而言,答案可能很简单。C ++编译为机器代码,其中Java编译为Java字节代码,然后在Java虚拟机上运行(尽管还有即时编译,这进一步将Java字节代码编译为本地机器代码)。另一个区别可能是垃圾收集,这是仅Java提供的服务。


2
答案太简单了。在某些设置中,Java速度更快。
拉斐尔

4
还有Java的实现,这些实现可以编译为机器代码,并可以解释为C ++的实现。如果我有一个picoJava CPU可以本地执行JVM字节码,并且我在JVM上运行了JPC x86解释器,那么什么使x86目标代码成为“机器码”而使JVM字节码却没有呢?
约尔格W¯¯米塔格

2
Nitpick:不仅Java提供了垃圾收集功能,而且,即使您仅考虑C ++和Java,某些C ++框架/库程序也具有垃圾收集功能。
einpoklum

编译为本地机器代码通常可以提高性能。但是,在某些有限的情况下,高质量的解释器可以击败天真的即时编译器。
DepressedDaniel

@JörgWMittag诀窍是编译为本地机器代码(主机处理器可以理解的机器代码),然后直接跳转到被调用的代码,以便无需解释即可执行该代码。这将是x86芯片上的x86和picoJava CPU上的JVM字节码。
DepressedDaniel

-5

您的问题过于笼统,因此我无法给出您需要的确切答案。

为了给我最好的解释,让我们看一下C ++和.Net平台。

C ++非常接近于机器代码,并且由于性能原因,它在较早的时间(例如十多年前?)中是首选编程之一。即使使用IDE,也不需要太多资源来开发和执行C ++程序,它们被认为非常轻便而且非常快。您也可以在控制台中编写C ++代码,并从那里开发游戏。在内存和资源方面,我大致忘记了计算机需要多少容量,但是这种容量是您无法与当前一代编程语言相比的。

现在让我们看一下.Net。.NET开发的先决条件是一个巨大的IDE,它不仅包括一种编程语言。即使您只想使用C#进行开发,IDE本身默认也会包含许多编程平台,例如J#,VB,Mobile等。除非您进行自定义安装并且仅安装所需的组件,否则您必须具备足够的经验来进行IDE安装。

除了安装IDE软件本身之外,.Net还具有大量的库和框架,以易于访问开发人员需要和不需要的任何平台。

在.Net中进行开发可能会很有趣,因为默认情况下会提供许多常用功能和组件。您可以在IDE中拖放并使用许多验证方法,文件读取,数据库访问等等。

结果,这是计算机资源和开发速度之间的折衷。这些库和框架占用了内存和资源。在.Net IDE中执行程序时,尝试编译库,组件和所有文件可能会花费大量时间。而且,当您进行调试时,您的计算机需要大量资源来管理IDE中的调试过程。

通常,为了进行.Net开发,您需要一台具有一些Ram和处理器的好的计算机。否则,您可能根本不编程。在这方面,C ++平台比.Net更好。尽管您仍然需要一台好的计算机,但是与.Net相比,容量不会受到太大的关注。

希望我的回答对您的问题有所帮助。如果您想了解更多,请告诉我。

编辑:

只是为了澄清我的主要观点,我主要回答“什么决定编程速度”的问题。

从IDE的角度来看,在相对的IDE中使用C ++或.Net语言不会影响编写代码的速度,但是会影响开发速度,因为Visual Studio编译器需要更长的时间来执行程序,但是C ++ IDE却要轻得多。并且消耗更少的计算机资源。因此,从长远来看,与.Net IDE相比,您可以使用C ++类型的IDE更快地进行编程,而.Net IDE在很大程度上取决于库和框架。如果花费大量时间等待IDE启动,编译,执行程序等,这将影响编程的速度。除非“编程速度”实际上仅关注“编写代码的速度”。

Visual Studio中的库和框架数量也消耗计算机容量。我不确定这是否回答了“内存管理”的问题,但我只想指出,Visual Studio IDE在运行时会占用大量资源,从而降低了整体“编程速度”。当然,这与“编写代码的速度”无关。

正如您可能已经猜到的那样,由于我只是以C ++为例,所以我对C ++不太了解,我的主要观点是有关消耗计算机资源的Visual Studio类型的重型IDE。

如果我有主意,而根本不回答线程启动程序的问题,则对冗长的帖子表示歉意。但是我建议线程启动器使问题更清楚,并确切询问他/她需要了解“更快”和“更慢”的知识


6
仅作记录,.NET开发仅需要.NET SDK(并且对于执行而言,.NET运行时本身)。您可以使用纯文本编辑器并从命令行调用编译器。不需要IDE(我假设您是指Visual Studio),尽管它肯定会帮助任何大型项目(Intellisense,调试器等)。还有更轻量的IDE,例如SharpDevelop,少了一些麻烦。
MetalMikester's

16
您当然可以在没有IDE的情况下在.Net中进行开发。但是我看不到IDE与用某种语言编写的代码速度有什么关系。
svick

8
@MetalMikester:当然,Visual Studio是Windows上C ++开发的首选IDE。
约尔格W¯¯米塔格

3
而且,将C ++编译为.Net代码只是一个编译器开关。假定的鸿沟是在Visual Studio中通过单击鼠标的桥梁。
MSalters

1
我完全不确定我会称现代C ++为“非常接近机器代码”。原始C,是的;它被认为是一种可移植的汇编程序,并且与之非常接近(尽管标准库已经发展壮大,并且各种编译器为该语言提供了附加组件,使它离起源更远了)。原始的C ++(带有类的C),也许;将包含类的C的变体重写为纯C并不困难,此时适用前面的讨论。但是现代C ++具有模板,多态性以及太阳下的其他所有功能吗?这几乎不是“非常接近机器代码”。
CVn
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.