我有一个问题想问所有热衷于低级黑客技术的人。我在一篇博客中看到了这句话。我认为源并不重要(如果你真的在意,它是Haack),因为这似乎是一个常见的说法。
例如,许多现代三维游戏都使用C++和汇编语言编写其高性能核心引擎。
至于汇编代码-是因为您不希望编译器发出额外的指令或使用过多的字节,还是因为您在使用更好的算法,这些算法无法用C语言表达(或无法表达而不被编译器干扰)?
我完全理解了解低级别的东西的重要性。我只是想了解为什么在了解了它之后要在汇编中编程。
我有一个问题想问所有热衷于低级黑客技术的人。我在一篇博客中看到了这句话。我认为源并不重要(如果你真的在意,它是Haack),因为这似乎是一个常见的说法。
例如,许多现代三维游戏都使用C++和汇编语言编写其高性能核心引擎。
至于汇编代码-是因为您不希望编译器发出额外的指令或使用过多的字节,还是因为您在使用更好的算法,这些算法无法用C语言表达(或无法表达而不被编译器干扰)?
我完全理解了解低级别的东西的重要性。我只是想了解为什么在了解了它之后要在汇编中编程。
介绍如何使用SSE指令集优化游戏引擎的英特尔文章。最终代码使用了指令集(而非内联汇编),因此纯汇编的量非常小。但他们查看编译器生成的汇编输出以确定要进行优化的内容。
快速反平方根算法在Quake中的应用。同样,该函数没有内联汇编,但需要了解一些架构知识才能进行这种优化。作者知道哪些运算很快(乘法、移位)和哪些很慢(除法、平方根)。因此,他们设计了一种非常巧妙的平方根实现,完全避免了慢操作。
高性能计算
在游戏领域之外,科学计算的人们经常会将事情优化到极致,以使其在最新的硬件上运行更快。可以把这看作是无法在物理上作弊的游戏。
一个很好的例子是格点量子色动力学(Lattice QCD)。这篇论文描述了这个问题基本上归结为一个非常小的计算核心,在IBM Blue Gene/L上对PowerPC 440进行了大量优化。每个440都有两个浮点单元,并且它们支持一些特殊的三元操作,这些操作对编译器来说很棘手。如果没有这些优化,Lattice QCD将运行得更慢,当你的问题需要在昂贵的机器上运行数百万个CPU小时时,这是很昂贵的。
如果你想知道为什么这很重要,请查看Science杂志中出现的这项工作的文章。使用Lattice QCD,这些人从第一原理计算出了质子的质量,并在去年展示了90%的质量来自于强相互作用能量,其余的来自于夸克。这就是E=mc2的作用。这里是一个摘要。
针对上述所有情况,这些应用程序并非100%使用汇编语言编写或设计 - 甚至远远不够。但是当人们真正需要速度时,他们会专注于编写代码的关键部分,以在特定硬件上运行。
我多年来没有使用汇编语言编程了,但我可以给出一些经常见到的原因:
并非所有编译器都能利用某些CPU优化和指令集(例如,英特尔时不时添加的新指令集)。等待编译器作者跟进意味着失去竞争优势。
更容易将实际代码匹配到已知的CPU架构和优化。例如,你所知道的获取机制、缓存等。这应该是对开发人员透明的,但事实是它不是,这就是为什么编译器作者可以进行优化的原因。
某些硬件级别的访问只能/实际上只有通过汇编语言才能实现(例如写设备驱动程序时)。
与高级语言相比,有时在汇编语言中进行正式推理实际上更容易,因为你已经知道代码的最终或几乎最终布局。
在缺乏API的情况下编程某些3D图形卡(大约在1990年代末)通常更实用且效率更高的是汇编语言,并且有时其他语言无法实现。但同样需要真正专家级的基于加速器架构的游戏,如手动按特定顺序移动数据。
我怀疑许多人在高级语言可以胜任工作时不会使用汇编语言,特别是当这种语言是C语言时。对大量的通用代码进行手动优化是不切实际的。
通常情况下,一个业余爱好者编写的汇编程序比C语言慢(因为C语言被优化了),但许多游戏(我清楚地记得毁灭战士)必须使用汇编语言编写游戏特定部分,以便在普通计算机上平稳运行。
我在我的第一份工作中(80年代)开始使用汇编语言进行专业编程。对于嵌入式系统来说,内存需求- RAM和EPROM-很低。您可以编写紧凑的代码,节约资源。
到了80年代末期,我转而使用C语言。这种代码更易于编写、调试和维护。非常小的代码片段使用汇编语言编写-对我来说,当我编写自己的RTOS中的上下文切换时就是这样。 (除非它是“科学项目”,否则不应再这样做。)
您将在某些Linux内核代码中看到汇编语言片段。最近,我已经在自旋锁和其他同步代码中浏览过它。这些代码片段需要访问原子测试和设置操作,操纵高速缓存等。
我认为,对于大多数一般编程,您很难超越现代C编译器进行优化。
我同意@altCognito的看法,您花费时间思考问题并做得更好可能更值得推荐。由于某种原因,程序员经常关注微效率而忽略宏效率。使用汇编语言来提高性能是一种微效率。从更广泛的系统视角回顾问题可以揭示系统中的宏问题。解决宏问题通常可以产生更好的性能增益。一旦解决了宏问题,然后再回到微观层面。
我想微观问题在单个程序员和较小的领域内得到控制。在宏层面上更改行为需要与更多人进行沟通-这是一些程序员回避的事情。整个牛仔vs团队的事情。
“是的”,但是要明白,大多数情况下,使用汇编语言编写代码带来的好处并不值得付出这样的努力。与仅仅集中精力思考问题和花时间想出更好的解决方法相比,用汇编语言编写代码所获得的回报往往比较小。
约翰·卡马克(John Carmack)和迈克尔·阿布拉什(Michael Abrash)在撰写 Quake 和所有进入 ID 游戏引擎的高性能代码方面负有重要责任,两人在这本书中详细阐述了这一点。
我也同意Ólafur Waage的看法,即现在编译器相当聪明,并且经常采用许多利用隐藏的架构提升的技术。
目前来说,对于顺序代码而言,一个不错的编译器几乎总是能够击败即使是经验丰富的汇编语言程序员。但是对于向量化代码,情况就不同了。例如,广泛使用的编译器并不能很好地利用x86 SSE单元的向量并行能力。作为一名编译器编写者,充分利用SSE 是我坚持自己动手而不是信任编译器的原因之一。
SSE代码在汇编中比编译器内置函数更有效,至少在MSVC中是这样的。(即不会创建额外的数据副本)
我已将此作为低级特性的指导原则。不要过于拘泥于使用它,但一定要恰当地激励它。甚至可以设置一两个人工障碍(比如文章)来避免复杂的推理作为正当理由。
有些指令/标志/控制在C语言层面上并不存在。
例如,在x86架构中检查溢出的简单方法是使用溢出标志。但是这个选项在C语言中不可用。