为什么我们必须以抽象为代价?


11

为什么从速度上看,高级语言似乎永远无法到达低级语言?高级语言的示例包括Python,Haskell和Java。定义低级语言会比较棘手,但可以说C。可以在Internet上找到比较结果,他们都同意C的速度要快很多,有时会提高10倍甚至更多。1

是什么导致如此巨大的性能差异?为什么高级语言无法赶上?

最初,我认为这是所有编译器的错,并且将来会有所改善,但是一些最受欢迎的高级语言已经存在了几十年,而在速度方面它们仍然落后。他们难道不能简单地编译成类似于C的语法树,然后遵循生成机器代码的相同过程吗?还是与语法本身有关?


1范例:


5
“而且他们都同意C的速度要快得多”,这肯定是错误的。
拉斐尔

2
无论如何,我认为最好是用我对一个同样构思错误的问题的答案来回答。重复?
拉斐尔

2
另请参阅此处此处。当心垃圾的答案。
拉斐尔

相关消息:stackoverflow.com/questions/6964392/…所有“高级语言”缓慢的神话是非常可悲的。
xji 2016年

我同意其他人所说的抽象==慢,但是恐怕还有一个更大的问题,计算机科学老师(我当时是其中之一)还没有意识到。那就是对于真实的程序(1000行代码及以上),有许多方法可以执行它们,并且它们的性能可能会相差一个数量级。只考虑big-O完全错了重点。在这里检查。
Mike Dunlavey

Answers:


19

揭穿一些神话

  1. 没有快速语言。一种语言通常可以产生快速的代码,但是不同的语言将在不同的基准上表现出色。我们可以在一组特定的有缺陷的基准上对语言进行排名,但不能在真空中对语言进行排名。

  2. C代码趋向于更快,因为需要每一寸性能的人都使用C。C的速度“快10倍”的统计数据可能是不准确的,因为使用Python的人可能根本不在乎关于速度,并且没有编写最佳的Python代码。我们尤其在Haskell之类的语言中看到了这一点。如果您真的很努力,可以编写与C相当的Haskell。但是大多数人并不需要那种性能,因此我们有很多有缺陷的比较。

  3. 有时,使C变得快速是不安全的,而不是抽象的。缺少数组边界和空指针检查节省了时间,并且多年来一直是造成大量安全漏洞的原因。

  4. 语言不是很快,实现是很快。许多抽象语言起步缓慢,因为速度不是他们的目标,但是随着越来越多的优化功能的加入,速度越来越快。

抽象与速度之间的权衡可能是不准确的。我建议一个更好的比较:

简单,速度,抽象:选择两个。

如果我们使用不同的语言运行相同的算法,那么速度问题将归结为“要在运行时完成这项工作,我们需要做多少工作?”的问题。

在非常简单的高度抽象的语言(例如Python或JavaScript)中,因为直到运行时我们才知道数据的表示形式,所以最终会在运行时发生大量指针取消引用和动态检查的情况,这很慢。

同样,需要进行很多检查以确保不会损坏计算机。当您在python中调用函数时,它需要确保您所调用的对象实际上是一个函数,因为否则您可能最终会执行执行可怕事情的随机代码。

最后,大多数抽象语言都有垃圾回收的开销。大多数程序员都同意,必须跟踪动态分配的内存的生存期很痛苦,他们宁愿让垃圾回收器在运行时为它们做这些。这需要时间,因此C程序无需花在GC上。

摘要并不代表缓慢

但是,有些语言既抽象又快速。这个时代最主要的是Rust。通过引入借用检查器和复杂的类型系统,它可以使用抽象代码,并使用编译时信息来减少我们在运行时需要做的工作量(即垃圾收集)。

任何静态类型的语言都可以通过减少运行时检查的次数来节省我们的时间,并通过要求我们在编译时取悦类型检查器来引入复杂性。类似地,如果我们使用一种语言来编码值在其类型系统中是否可以为null,则可以通过编译时空指针检查来节省时间。


2
“ C代码趋向于更快,因为需要每一寸性能的人都使用C”-恰好。我希望看到“具有X年工作经验的X年学生/专业编写的代码的平均运行时间”形式的基准。我希望对于小型X和Y,C的答案通常是“代码不正确”。非常有趣的是,您需要利用C承诺的性能潜力还需要多少经验/专业知识。
拉斐尔

Haskell实际上是证明规则的例外。;)我认为C ++已经发现了大约GC一个相当合理的中间地带其智能指针,只要你不嵌套共享指针,并会以最快的速度C.
卡韦赫

0

这里有一些关键思想。

  • 一个简单的比较/案例研究是Java vs C ++,它使wrt抽象与速度相对应。java旨在抽象化c ++的某些较低方面,例如内存管理。在早期(大约是1990年代中期语言的发明),java垃圾检测不是很快,但是经过几十年的研究,垃圾收集器经过了非常精细的调整/快速/优化,因此,垃圾收集器在很大程度上被淘汰了。 Java上的性能消耗。例如,甚至可以看一下1998年的这个标题:性能测试显示Java的速度与C ++ / javaworld 一样快

  • 编程语言及其长期演进具有固有的“金字塔/层次结构”,作为一种超越性的设计模式。金字塔的顶部控制着金字塔的其他下部。换句话说,构建块是由构建块制成的。这也可以在API结构中看到。从这个意义上讲,更好的抽象总会导致金字塔顶部的某些新组件控制其他组件。因此,从某种意义上说,它并不仅仅是所有语言都处于同一水平,而是语言需要其他语言的例程。例如,许多脚本语言(python / ruby​​)经常调用C或C ++库,数字或矩阵例程就是典型的例子。因此,存在较高级别的语言和较低级别的语言,而高级语言可以说是较低级别的语言。从这个意义上说,测量相对速度并不是真正可比的。

  • 可能有人会说,一直在发明新的语言来尝试优化抽象/速度折衷,即其主要设计目标。也许与其说是这样,不如说更多的抽象总是牺牲速度,而新的设计总是在寻求更好的平衡。例如,Google Go在许多方面都经过了专门权衡优化,同时兼顾了高级和高性能。参见例如Google Go:为什么Google的编程语言可以在企业 /技术世界中与Java竞争


0

我喜欢考虑性能的方式是“橡胶在路上相遇”。计算机执行指令,而不是抽象。

我想看到的是:执行的每条指令是否都通过对最终结果做出重大贡献来“赚钱”?作为一个过于简单的示例,请考虑在包含1024个条目的表中查找一个条目。那是一个10位的问题,因为程序必须在知道答案之前先“学习” 10位。如果该算法是二进制搜索,则每次迭代都会贡献1位信息,因为它将不确定性缩小2倍。因此,它需要进行10次迭代,每位迭代一次。

另一方面,线性搜索最初效率很低,因为第一次迭代将不确定性缩小了很小的比例。因此,他们对于所付出的努力并没有学到太多。

是的,所以如果编译器可以允许用户以“抽象”的方式包装好的指令,那很好。


0

通过其本质,抽象减少了程序员和系统较低层(编译器,库和运行时系统)的信息交流。有利于抽象,这通常允许较低的层假定程序员不关心任何未指定的行为,从而在提供指定的行为方面提供了更大的灵活性

从“无关”方面获得潜在好处的一个例子是数据布局。在C(低抽象度)中,编译器在数据布局优化方面受到更多限制。即使编译器可以(例如,通过配置文件信息)识别出热/冷或错误共享避免优化将是有益的,但通常也无法应用这种优化。(在指定“好像”时有一定的自由度,即更抽象地处理规范,但是派生所有潜在的副作用给编译器增加了负担。)

更加抽象的规范对于权衡和使用方面的更改也更加健壮。在为新的系统特性或新用途重新优化程序时,较低层的约束较少。必须由程序员重写更具体的规范,或者较低层必须做出额外的努力以保证“好像”行为。

信息隐藏抽象的性能阻碍方面是“无法表达”,下层通常将其处理为“不知道”。这意味着下层必须从其他手段(例如典型的一般用途,目标用途或特定的配置文件信息)中识别出对优化有用的信息。

信息隐藏的影响也在另一个方向起作用。通过不必考虑和指定每个细节,程序员可以提高生产率,但是程序员可能对高层设计选择的影响了解较少。

另一方面,当代码更加具体(不那么抽象)时,系统的较低层可以更轻松地完成被告知要做的事情。如果代码针对其目标用途编写得当,则将非常适合其目标用途。较不抽象的语言(或编程范例)允许程序员通过详细的设计和使用信息来优化实现,这些信息不容易以给定语言传达给下层。

如前所述,当额外的程序员技能和精力可以产生有价值的结果时,较少的抽象语言(或编程技术)就很有吸引力。如果应用更多的程序员精力和技能,结果通常会更好。此外,一种语言系统在性能关键型应用程序中使用较少(而是强调开发工作或可靠性-边界检查和垃圾回收不仅与程序员的工作效率有关,而且与正确性有关,抽象可以减轻程序员的心理负担,从而可以提高可靠性)改进性能的压力将较小。

特异性也违反了不要重复自己的原则,因为通常可以通过针对特定用途定制代码来实现优化。这具有明显的可靠性和编程工作意义。

语言提供的抽象也可能包括不需要的或不必要的工作,而没有选择重量较轻的抽象的方法。尽管有时可以通过较低的层发现并删除不必要的工作(例如,可以从循环主体中提取边界检查,在某些情况下可以完全删除边界检查),但确定这是有效的优化需要付出更多的“技巧和精力”编译器。

语言的年龄和流行度也是熟练程序员的可用性以及系统较低层的质量(包括成熟的库和代码示例)的重要因素。

这种比较中的另一个混淆因素是提前编译和即时编译之间有些正交的差异。尽管即时编译可以更轻松地利用概要文件信息(不依赖程序员来提供概要文件运行)和特定于系统的优化(提前编译可能会针对更广泛的兼容性),但积极的优化开销被认为是:运行时性能的一部分。可以缓存JIT结果,从而减少了常用代码的开销。(二进制重新优化的替代方法可以提供JIT编译的一些优势,但是传统的二进制分发格式会丢弃大多数源代码信息,从而可能迫使系统尝试从特定实现中识别意图。)

(由于抽象语言偏重于程序员控制,因此它们倾向于使用提前编译。尽管链接时实现选择可以提供更好的程序员控制能力,但可以容忍安装时编译。JIT编译牺牲了重要的控制能力。 )

还有基准测试方法的问题。同样的努力/技能实际上是不可能建立的,但是即使达到了同样的努力/技能,语言目标也会使结果产生偏差。如果需要较少的最大编程时间,则与使用抽象语言的简单惯用语表达相比,使用不太抽象语言的程序甚至可能无法完全编写。如果允许较高的最大编程时间/精力,则较低抽象性的语言将具有优势。呈现最大努力结果的基准自然会偏向于使用较少抽象的语言。

有时可能会以一种不太惯用的方式用一种语言进行编程以获得其他编程范例的优势,但是即使在具有表达能力的情况下,这样做的权衡也可能不利。

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.