你为什么要使用汇编语言进行编程?

89

我有一个问题想问所有热衷于低级黑客技术的人。我在一篇博客中看到了这句话。我认为源并不重要(如果你真的在意,它是Haack),因为这似乎是一个常见的说法。

例如,许多现代三维游戏都使用C++和汇编语言编写其高性能核心引擎。

至于汇编代码-是因为您不希望编译器发出额外的指令或使用过多的字节,还是因为您在使用更好的算法,这些算法无法用C语言表达(或无法表达而不被编译器干扰)?

我完全理解了解低级别的东西的重要性。我只是想了解为什么在了解了它之后要在汇编中编程。


1
已经有类似的问题了,我想... - Mehrdad Afshari
8
嗯,严格来说这是一个不同的问题。那些问题都是关于为什么要学习汇编语言,而这个问题是为什么要用它来编程,我认为这是不同的……? - cgp
4
你为什么使用汇编语言进行编程?--让我们来看一些不可能的答案:1)为了使我的代码可维护,2)灵活性,3)确保可移植性,4)可测试性,5)可读性... - ivan_ivanovich_ivanoff
9
工作保障 - San Jacinto
3
因为这很有趣.. :) - RainingComputers
显示剩余4条评论
30个回答

173
我认为你误读了这句话:
例如,许多现代3D游戏的高性能核心引擎都是用C++和汇编语言编写的。
现在的游戏(以及大多数程序)并不是像“使用C++编写”一样“使用汇编语言编写”的。那篇博客并没有说游戏的很大一部分是用汇编语言设计的,或者一个团队的程序员坐在那里用汇编语言作为他们的主要语言开发。
实际上,这意味着开发人员首先用C++编写游戏并使其正常工作。然后,他们对其进行剖析,找出瓶颈所在,并且如果值得,他们会在汇编语言中进行高度优化。或者,如果他们已经有经验,他们知道哪些部分将成为瓶颈,并且他们已经从构建其他游戏中获得了优化过的代码片段。
汇编语言编程的重点与以往一样:速度。在汇编语言中编写大量代码是荒谬的,但是编译器不知道某些优化,对于足够小的代码窗口,人类将做得更好。
例如,对于浮点数,编译器往往比较保守,可能不知道您的体系结构的一些更高级特性。如果您愿意接受一些误差,通常可以比编译器做得更好,并且如果发现大量时间花费在其中,则值得在汇编语言中编写那段小代码。
以下是一些更相关的例子:
游戏中的示例
  • 介绍如何使用SSE指令集优化游戏引擎的英特尔文章。最终代码使用了指令集(而非内联汇编),因此纯汇编的量非常小。但他们查看编译器生成的汇编输出以确定要进行优化的内容。

  • 快速反平方根算法在Quake中的应用。同样,该函数没有内联汇编,但需要了解一些架构知识才能进行这种优化。作者知道哪些运算很快(乘法、移位)和哪些很慢(除法、平方根)。因此,他们设计了一种非常巧妙的平方根实现,完全避免了慢操作。

高性能计算

  • 在游戏领域之外,科学计算的人们经常会将事情优化到极致,以使其在最新的硬件上运行更快。可以把这看作是无法在物理上作弊的游戏。

    一个很好的例子是格点量子色动力学(Lattice QCD)这篇论文描述了这个问题基本上归结为一个非常小的计算核心,在IBM Blue Gene/L上对PowerPC 440进行了大量优化。每个440都有两个浮点单元,并且它们支持一些特殊的三元操作,这些操作对编译器来说很棘手。如果没有这些优化,Lattice QCD将运行得更慢,当你的问题需要在昂贵的机器上运行数百万个CPU小时时,这是很昂贵的。

    如果你想知道为什么这很重要,请查看Science杂志中出现的这项工作的文章。使用Lattice QCD,这些人从第一原理计算出了质子的质量,并在去年展示了90%的质量来自于强相互作用能量,其余的来自于夸克。这就是E=mc2的作用。这里是一个摘要

针对上述所有情况,这些应用程序并非100%使用汇编语言编写或设计 - 甚至远远不够。但是当人们真正需要速度时,他们会专注于编写代码的关键部分,以在特定硬件上运行。


12
反应太棒了。希望我们能把这放进维基百科! - bdd
6
@Paperino ... 如果可以的话,StackOverflow上的问题和回答都是基于创作共用署名许可证。 - Aaron Maenpaa
要了解汇编语言以帮助您编写更好的C/C++代码,请参阅为什么这个测试Collatz猜想的C++代码比我手写的汇编代码快?。我在那里的答案指出,阅读编译器的汇编输出并调整源代码可以帮助编译器注意到有用的优化。因此,您可以在脑海中(或实际上)编写汇编语言,然后手动引导编译器执行您想要的操作,但现在您拥有具有未来性的可移植C代码。 - Peter Cordes
现在的编译器非常优秀,生成的汇编代码可能比任何人手写的汇编代码都要好。只有很少的情况下需要使用汇编语言,并且我认为这与优化无关,或者至少很少涉及到优化问题。 - noun

44

我多年来没有使用汇编语言编程了,但我可以给出一些经常见到的原因:

  • 并非所有编译器都能利用某些CPU优化和指令集(例如,英特尔时不时添加的新指令集)。等待编译器作者跟进意味着失去竞争优势。

  • 更容易将实际代码匹配到已知的CPU架构和优化。例如,你所知道的获取机制、缓存等。这应该是对开发人员透明的,但事实是它不是,这就是为什么编译器作者可以进行优化的原因。

  • 某些硬件级别的访问只能/实际上只有通过汇编语言才能实现(例如写设备驱动程序时)。

  • 与高级语言相比,有时在汇编语言中进行正式推理实际上更容易,因为你已经知道代码的最终或几乎最终布局。

  • 在缺乏API的情况下编程某些3D图形卡(大约在1990年代末)通常更实用且效率更高的是汇编语言,并且有时其他语言无法实现。但同样需要真正专家级的基于加速器架构的游戏,如手动按特定顺序移动数据。

我怀疑许多人在高级语言可以胜任工作时不会使用汇编语言,特别是当这种语言是C语言时。对大量的通用代码进行手动优化是不切实际的。


19
有一点汇编程序设计方面的内容,其他人没有提到——你知道每个应用程序中的每一个字节都是你自己努力的结果,而不是编译器的结果,这种满足感是无与伦比的。我不会想回到80年代初期我写整个应用程序的汇编语言时代,但我有时确实会想念那种感觉...

3
嘿,这就是汇编工作的结果!通常在汇编语言中会编写很多宏。 - Mehrdad Afshari
6
不仅是满意,还有对精确度的欣赏。一个将其所有内容都声明清楚的简洁流程让人喜悦。 - deau

18

通常情况下,一个业余爱好者编写的汇编程序比C语言慢(因为C语言被优化了),但许多游戏(我清楚地记得毁灭战士)必须使用汇编语言编写游戏特定部分,以便在普通计算机上平稳运行。

这里是我所提到的例子。


2
+1 非常正确。人类在编写长的汇编代码方面非常糟糕。 - Dead account
请记住,在编写汇编程序时,并非总是有这些工具可用。 - Marco van de Voort

17

我在我的第一份工作中(80年代)开始使用汇编语言进行专业编程。对于嵌入式系统来说,内存需求- RAM和EPROM-很低。您可以编写紧凑的代码,节约资源。

到了80年代末期,我转而使用C语言。这种代码更易于编写、调试和维护。非常小的代码片段使用汇编语言编写-对我来说,当我编写自己的RTOS中的上下文切换时就是这样。 (除非它是“科学项目”,否则不应再这样做。)

您将在某些Linux内核代码中看到汇编语言片段。最近,我已经在自旋锁和其他同步代码中浏览过它。这些代码片段需要访问原子测试和设置操作,操纵高速缓存等。

我认为,对于大多数一般编程,您很难超越现代C编译器进行优化。

我同意@altCognito的看法,您花费时间思考问题并做得更好可能更值得推荐。由于某种原因,程序员经常关注微效率而忽略宏效率。使用汇编语言来提高性能是一种微效率。从更广泛的系统视角回顾问题可以揭示系统中的宏问题。解决宏问题通常可以产生更好的性能增益。一旦解决了宏问题,然后再回到微观层面。

我想微观问题在单个程序员和较小的领域内得到控制。在宏层面上更改行为需要与更多人进行沟通-这是一些程序员回避的事情。整个牛仔vs团队的事情。


11

“是的”,但是要明白,大多数情况下,使用汇编语言编写代码带来的好处并不值得付出这样的努力。与仅仅集中精力思考问题和花时间想出更好的解决方法相比,用汇编语言编写代码所获得的回报往往比较小。

约翰·卡马克(John Carmack)和迈克尔·阿布拉什(Michael Abrash)在撰写 Quake 和所有进入 ID 游戏引擎的高性能代码方面负有重要责任,两人在这本中详细阐述了这一点。

我也同意Ólafur Waage的看法,即现在编译器相当聪明,并且经常采用许多利用隐藏的架构提升的技术。


10

目前来说,对于顺序代码而言,一个不错的编译器几乎总是能够击败即使是经验丰富的汇编语言程序员。但是对于向量化代码,情况就不同了。例如,广泛使用的编译器并不能很好地利用x86 SSE单元的向量并行能力。作为一名编译器编写者,充分利用SSE 是我坚持自己动手而不是信任编译器的原因之一。


在这种情况下,我会使用编译器内置函数。 - Mehrdad Afshari
仍然不一样。这就像没有寄存器优化器的编译器。 - Marco van de Voort
这取决于汇编程序员的调味品种。如果你已经阅读并理解了http://agner.org/optimize/以了解你正在调整的微架构,那么仅对于短序列来说,打败编译器通常很容易。至少一半的时间,当我查看小函数的编译器输出时,我会发现错过了一些次要的优化。编译器的优势在于通过内联和常量传播来优化大型代码库。 - Peter Cordes

8

SSE代码在汇编中比编译器内置函数更有效,至少在MSVC中是这样的。(即不会创建额外的数据副本)


好的,你需要一个能够很好处理内嵌函数的编译器。英特尔和GNU编译器非常不错,我不知道PGI和PathScale的最新版本是否有竞争力,以前它们并没有。 - Jed

6
我在工作中的源代码中有三到四个汇编程序(大约20 MB),它们都是SSE(2)相关的,用于对相当大的图像(例如2400x2048及更大)进行操作。
在业余时间,我从事编译器开发,其中包含更多的汇编程序。运行时库通常充满了它们,其中大部分与无法遵循正常过程规则的内容有关(例如异常辅助程序等)。
我的微控制器没有任何汇编程序。现代微控制器拥有如此多的外围硬件(中断控制计数器,甚至整个象限编码器和串行构建块),以至于通常不再需要使用汇编语言来优化循环。随着当前闪存价格的下降,同样适用于代码内存。此外,通常存在一系列引脚兼容设备,因此如果您系统地耗尽CPU功率或闪存空间,则通常不会出现升级问题。
除非您真的要运输100,000台设备,并且编程汇编语言可以通过仅适合一个更小类别的闪存芯片实现真正的节省。但我不属于那个类别。
很多人认为嵌入式是使用汇编语言的借口,但他们的控制器比Unix开发时的机器拥有更多的CPU功率。(Microchip推出40和60 MIPS微控制器,售价不到USD10)。
然而,由于更改Microchip架构并不容易,很多人被困在了遗留问题上。此外,高级语言代码非常依赖于体系结构(因为它使用硬件周边、寄存器来控制I/O等)。因此,有时保持用汇编语言维护项目是有好处的(我很幸运能够从头开始设置新架构的事务)。但是,人们经常自欺欺人地认为他们真的需要汇编语言。
我仍然喜欢一位教授的回答,当我们问他是否可以使用GOTO(但你也可以将其解读为ASSEMBLER)时,他说:“如果你认为值得写一篇三页的文章来解释为什么需要这个功能,那么你可以使用它。请提交带有结果的文章。”

我已将此作为低级特性的指导原则。不要过于拘泥于使用它,但一定要恰当地激励它。甚至可以设置一两个人工障碍(比如文章)来避免复杂的推理作为正当理由。


1
我喜欢论述式测试;也许我需要更经常地使用它 ;) - ad absurdum

5

有些指令/标志/控制在C语言层面上并不存在。

例如,在x86架构中检查溢出的简单方法是使用溢出标志。但是这个选项在C语言中不可用。


您可以使用位操作在C语言中计算溢出标志。 - swegi
@swegi:我敢打赌,那肯定慢得不值一提。 - Brian
这有多少用处呢?即使有用,也不可能是转向汇编的唯一原因。 - user172783

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接