我已经注意到,某些基于编程语言(例如C ++ / Rust)构建的应用程序或算法比在同一台机器上运行的基于Java / Node.js的应用程序或算法运行得更快或更灵活。我对此有一些疑问:
- 为什么会这样?
- 是什么决定了编程语言的“速度”?
- 这和内存管理有关系吗?
如果有人为我解决了这个问题,我将不胜感激。
我已经注意到,某些基于编程语言(例如C ++ / Rust)构建的应用程序或算法比在同一台机器上运行的基于Java / Node.js的应用程序或算法运行得更快或更灵活。我对此有一些疑问:
如果有人为我解决了这个问题,我将不胜感激。
Answers:
在编程语言的设计和实现中,有很多选择会影响性能。我只说几句。
每种语言最终都必须通过执行机器代码来运行。诸如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
他们都没有使用一种语言。
是什么决定了编程语言的“速度”?
没有编程语言的“速度”。由特定程序编写的特定程序的速度只有在特定环境中运行的特定执行引擎的特定实现的特定版本才能执行。
使用不同的实现在同一台计算机上运行用同一语言编写的相同代码可能会有巨大的性能差异。甚至使用同一实现的不同版本。例如,使用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(通常被认为是“慢语言”),所以我举两个例子:Hash
Rubinius中的类(Ruby中的中央数据结构之一,键值字典)。 Ruby实现是用100%纯Ruby编写的,其性能与Hash
YARV(使用最广泛的实现)中的类,是用C编写的。还有一个图像处理库,它是YARV的C扩展编写的,对于没有实现的实现,也有一个(缓慢的)纯Ruby“后备版本”不支持使用大量高动态性和反射性Ruby技巧的C;JRuby的一个实验分支利用Oracle Labs的Truffle AST解释器框架和Graal JIT编译框架,可以执行纯Ruby“后备版本”,就像YARV可以执行原始的高度优化的C版本一样快。这只是(有些东西除外)由一些非常聪明的人通过动态运行时优化,JIT编译和部分评估来做一些非常聪明的事情而实现的。
int
尽管出于性能考虑,C选择了固定大小s,尽管事实上无限制的整数(如Python使用的整数)在数学上更为自然。在硬件中实现无界整数不会像固定大小的整数那样快。试图隐藏实现细节的语言需要复杂的优化才能接近朴素的C实现。
从理论上讲,如果您编写的代码在两种语言中具有完全相同的功能,并且都使用“完美”的编译器进行编译,则两者的性能应该相同。
在实践中,性能会有所不同的原因有几个:
有些语言更难优化。
这特别包括使代码更具动态性的功能(动态键入,虚拟方法,函数指针),还包括例如垃圾回收。
有多种方法可以使使用此类功能的代码快速运行,但是与不使用它们相比,它通常最终至少会慢一些。
一些语言实现必须在运行时进行一些编译。
这尤其适用于具有虚拟机的语言(例如Java)和执行源代码的语言,而没有二进制的中间步骤(例如JavaScript)。
这样的语言实现必须在运行时做更多的工作,这会影响性能,尤其是启动后的时间/启动后的性能。
语言实现有意地花费了更少的时间进行优化。
所有编译器都关心编译器本身的性能,而不仅仅是生成的代码。这对于运行时编译器(JIT编译器)尤其重要,因为运行时编译器花费的时间太长,从而降低了应用程序的执行速度。但是它也适用于提前编译器,例如C ++一样。例如,寄存器分配是一个NP完全问题,因此不能完美地解决它,而是使用在合理的时间内执行的启发式方法。
不同语言中的成语差异。
使用通用库以某种语言通用格式编写的代码(惯用代码)可能会导致性能差异,因为这种惯用代码在每种语言中的行为都略有不同。
例如,请考虑vector[i]
使用C ++,list[i]
C#和list.get(i)
Java。C ++代码可能不执行范围检查,也不执行虚拟调用,C#代码执行范围检查,但不执行虚拟调用,而Java代码执行范围检查,这是一个虚拟调用。三种语言都支持虚拟和非虚拟方法,并且C ++和C#在访问基础数组时都可以包括范围检查或避免使用范围检查。但是这些语言的标准库决定以不同的方式编写这些等效的函数,因此,它们的性能将有所不同。
一些编译器可能缺少一些优化。
编译器作者的资源有限,因此即使忽略其他问题,他们也无法实现所有可能的优化。他们拥有的资源可能会比其他领域更专注于一个优化领域。因此,即使没有根本原因使一种语言比另一种语言更快速或更容易优化,使用不同语言编写的代码由于其编译器的差异而可能具有不同的性能。
这是一个非常普遍的问题,但是对于您而言,答案可能很简单。C ++编译为机器代码,其中Java编译为Java字节代码,然后在Java虚拟机上运行(尽管还有即时编译,这进一步将Java字节代码编译为本地机器代码)。另一个区别可能是垃圾收集,这是仅Java提供的服务。
您的问题过于笼统,因此我无法给出您需要的确切答案。
为了给我最好的解释,让我们看一下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。
如果我有主意,而根本不回答线程启动程序的问题,则对冗长的帖子表示歉意。但是我建议线程启动器使问题更清楚,并确切询问他/她需要了解“更快”和“更慢”的知识