我对那里所有的核心低级黑客都有疑问。我在博客中碰到了这句话。我并不真正认为消息来源很重要(如果您真的很在意,那就是Haack),因为这似乎是一种常见的说法。
例如,许多现代的3D游戏都有以C ++和Assembly编写的高性能核心引擎。
就汇编而言,这是用汇编语言编写的,因为您不希望编译器发出额外的指令或使用过多的字节,或者您使用的是无法在C中表达的更好的算法(或者在没有编译器迷惑了他们)?
我完全明白了解底层内容很重要。我只想理解为什么理解汇编后的程序。
我对那里所有的核心低级黑客都有疑问。我在博客中碰到了这句话。我并不真正认为消息来源很重要(如果您真的很在意,那就是Haack),因为这似乎是一种常见的说法。
例如,许多现代的3D游戏都有以C ++和Assembly编写的高性能核心引擎。
就汇编而言,这是用汇编语言编写的,因为您不希望编译器发出额外的指令或使用过多的字节,或者您使用的是无法在C中表达的更好的算法(或者在没有编译器迷惑了他们)?
我完全明白了解底层内容很重要。我只想理解为什么理解汇编后的程序。
Answers:
我认为您误读了以下语句:
例如,许多现代的3D游戏都有以C ++和Assembly编写的高性能核心引擎。
游戏(以及当今大多数程序)的“组装方式”与“ C ++编写”方式不同。该博客并不是说游戏的很大一部分是用汇编设计的,也不是说有一群程序员坐在那里并以汇编为主要语言开发。
这的真正含义是,开发人员首先编写游戏并使其在C ++中运行。然后他们对它进行概要分析,找出瓶颈所在,如果值得,他们会在组装中优化这些瓶颈。或者,如果他们已经有经验,他们就会知道哪些部分将成为瓶颈,并且已经从自己制作的其他游戏中获得了优化的作品。
汇编编程的要点与往常一样:speed。在汇编器中编写很多代码是很荒谬的,但是有些优化是编译器不知道的,并且对于足够小的代码窗口,人类会做得更好。
例如,对于浮点,编译器往往非常保守,可能不了解您的体系结构的某些更高级的功能。如果您愿意接受一些错误,通常可以比编译器做得更好,并且如果发现花了大量时间在汇编上,则值得在汇编中编写少量代码。
以下是一些更相关的示例:
游戏中的例子
英特尔的文章,内容涉及使用SSE内在函数优化游戏引擎。最终代码使用内部函数(而不是内联汇编器),因此纯汇编的数量非常少。但是他们会查看编译器输出的汇编程序,以准确找出要优化的内容。
雷神之锤的快速反平方根。同样,该例程中没有汇编程序,但是您需要了解一些有关体系结构的知识才能进行这种优化。作者知道哪些运算是快速的(乘法,移位),哪些运算是慢的(除法,sqrt)。因此,他们提出了一个非常棘手的平方根实现,完全避免了缓慢的操作。
高性能计算
在游戏领域之外,科学计算领域的人们经常优化处理过程,以使它们在最新的硬件上快速运行。可以将其视为无法在物理上作弊的游戏。
最近一个很好的例子是莱迪思量子色动力学(Lattice QCD)。 本文介绍了该问题如何归结为一个很小的计算内核,该内核针对IBM Blue Gene / L上的PowerPC 440进行了优化。每个440具有两个FPU,并且它们支持一些特殊的三元运算,这对于编译器利用来说是棘手的。没有这些优化,莱迪思QCD的运行速度就会慢得多,当您的问题在昂贵的计算机上需要数百万个CPU小时时,这样做的成本将会很高。
如果您想知道为什么这很重要,请查看此工作中发表的《科学》杂志上的文章。这些人使用莱迪思QCD,根据第一性原理计算了质子的质量,并在去年证明了90%的质量来自强大的结合力能量,其余的来自夸克。这实际上是E = mc 2。 这是一个摘要。
对于所有上述情况,这些应用程序并非以100%组装的形式进行设计或编写的-甚至没有关闭。但是,当人们确实需要速度时,他们会专注于编写代码的关键部分,以使用特定的硬件。
我已经多年没有使用汇编语言进行编码了,但是我可以给出一些我经常看到的原因:
并非所有的编译器都可以利用某些CPU优化和指令集(例如Intel偶尔添加的新指令集)。等待编译器作者赶上来意味着失去竞争优势。
更容易将实际代码与已知的CPU架构和优化进行匹配。例如,您了解获取机制,缓存等方面的知识。这应该对开发人员是透明的,但事实是事实并非如此,这就是编译器编写者可以优化的原因。
某些硬件级别的访问只有通过汇编语言才可能/可行(例如,在编写设备驱动程序时)。
对于汇编语言而言,形式化推理有时实际上比对高级语言而言更容易,因为您已经知道代码的最终或几乎最终布局是什么。
在没有API的情况下对某些3D图形卡进行编程(大约在1990年代后期)在汇编语言中通常更为实用和高效,而在其他语言中则有时无法实现。但是同样,这涉及真正基于专家架构的专家级游戏,例如按一定顺序手动移入和移出数据。
我怀疑许多人在高级语言可以使用汇编语言时会使用汇编语言,尤其是当该语言是C时。手动优化大量通用代码是不切实际的。
汇编程序编程的一个方面没有其他方面提到过-您感到满意的感觉是,您知道应用程序中的每个字节都是您自己的努力的结果,而不是编译器的努力。我不会再想回到80年代初的时候用汇编器编写整个应用程序了,但是有时我确实会错过这种感觉...
通常,外行的程序集比C慢(由于C的优化),但是许多游戏(我很清楚地记得Doom)必须在Assembly中有游戏的特定部分,这样才能在普通机器上平稳运行。
我的第一份工作(80年代)开始使用汇编语言进行专业编程。对于嵌入式系统,RAM和EPROM的内存需求较低。您可以编写紧凑的代码,这些代码在资源上很容易。
到80年代后期,我开始使用C。代码更易于编写,调试和维护。很小的代码片段是用汇编器编写的-对我来说,这是我在自己的RTOS中编写上下文切换时的代码。(除非是“科学项目”,否则您不应该再做任何事情。)
您将在一些Linux内核代码中看到汇编程序片段。最近,我以自旋锁和其他同步代码进行了浏览。这些代码段需要访问原子测试和设置操作,操纵缓存等。
我认为您很难为大多数通用编程而对现代C编译器进行优化。
我同意@altCognito的观点,您最好花更多的时间思考问题并做得更好。出于某种原因,程序员经常专注于微效率,而忽略了宏观效率。汇编语言提高性能是一种微观效率。退一步以更广泛地查看系统可能会暴露系统中的宏观问题。解决宏问题通常可以提高性能。解决了宏观问题后,便会陷入微观层面。
我猜想微观问题在单个程序员的控制范围内,并且范围较小。在宏级别上改变行为需要与更多的人进行交流,这是某些程序员避免的事情。整个牛仔队与团队的关系。
如今,至少对于顺序代码而言,像样的编译器几乎总是击败甚至是经验丰富的汇编语言程序员。但是对于矢量代码,则是另一回事了。例如,广泛使用的编译器在利用x86 SSE单元的矢量并行功能方面做得并不出色。我是一名编译器作家,利用SSE成为我自己而不愿意信任编译器的主要原因。
在工作中,我有三个或四个汇编程序例程(约20 MB的源代码)。它们都是SSE(2),并且与对(相当大-认为2400x2048及更大)图像的操作有关。
出于爱好,我使用编译器,那里有更多的汇编器。运行时库常常充满了它们,其中大多数与违反正常程序规则的东西(例如异常的助手等)有关。
我的微控制器没有任何汇编程序。大多数现代微控制器都具有如此多的外围硬件(中断控制计数器,甚至整个正交编码器和串行构建块),因此通常不再需要使用汇编器来优化循环。以当前的闪存价格,代码存储器也是如此。此外,通常还存在许多与引脚兼容的设备,因此,如果系统地用完CPU电源或闪存空间,则升频通常不是问题
除非您真的发货了100000台设备,否则编程汇编程序仅通过装配较小类别的闪存芯片就可以真正节省大量资金。但是我不在那一类。
许多人认为嵌入式是汇编程序的借口,但是他们的控制器比Unix开发的机器具有更多的CPU能力。(Microchip带有40和60个MIPS微控制器,价格低于10美元)。
但是,由于改变微芯片架构并不容易,因此很多人都受其遗留。同样,HLL代码在很大程度上取决于体系结构(因为它使用硬件外围设备,寄存器来控制I / O等)。因此,有时有充分的理由继续在汇编器中维护项目(我很幸运能够从头开始在新架构上设置事务)。但是通常人们会自欺欺人,说他们确实需要汇编程序。
我仍然喜欢教授问我们是否可以使用GOTO时给出的答案(但您也可以将其读为ASSEMBLER):“如果您认为有必要写一篇三页的文章说明为什么需要此功能,则可以使用它请把论文和结果一起提交。”
我已将其用作低级功能的指导原则。不要太局促地使用它,但要确保正确地激发它。甚至抛出一两个人为的障碍(如论文),以免混淆推理作为理由。
缺陷倾向于按行运行(语句,代码点等);虽然对于大多数问题而言,汇编使用的行比高级语言要多得多,但在某些情况下,最好的(最简洁,最少的行)映射到了当前的问题。这些案例大多数涉及通常的可疑事件,例如驱动程序和嵌入式系统中的位撞击。
除了在非常小的CPU上的很小的项目之外,我不会开始用汇编语言对整个项目进行编程。但是,通常发现可以通过一些内部循环的策略性手动编码来缓解性能瓶颈。
在某些情况下,真正需要做的只是用无法期望优化器弄清楚如何使用的指令来替换某种语言构造。一个典型的例子是在DSP应用程序中,矢量操作和乘法累加操作对于优化器来说很难发现,但是易于编写代码。
例如,SH4的某些模型包含4x4矩阵和4个矢量指令。通过用适当的指令替换3x3矩阵上的等效C操作,我看到了色彩校正算法的巨大性能改进,而付出的代价很小,就是将校正矩阵扩大到4x4以匹配硬件假设。这是通过编写不超过十二行汇编语言,并对相关数据类型进行匹配调整并将其存储到周围的C代码中的少数几个地方来实现的。
到目前为止,我见过的几乎每个大中型游戏引擎或库都有一些手动优化的汇编版本,可用于矩阵操作(如4x4矩阵级联)。似乎在使用大型矩阵时,编译器不可避免地会错过一些聪明的优化(重用寄存器,以最大效率的方式展开循环,利用机器特定的指令等)。这些矩阵操作函数几乎始终也是概要文件上的“热点”。
我还看到手工编码的程序集大量用于自定义调度-诸如FastDelegate之类的东西,但特定于编译器和计算机。
最后,如果您具有中断服务例程,则asm可以使世界变得与众不同–有些操作您根本不想在中断下进行,并且希望中断处理程序“快速进出”。 ..您几乎完全知道ISR在asm中会发生什么,并且它会鼓励您将血腥的事情保持简短(无论如何都是好习惯)。
许多人喜欢贬低汇编语言,因为他们从未学会过用汇编语言编写代码,只是隐约地遇到了汇编语言,这使他们感到震惊或有些恐惧。真正有才华的程序员将理解,抨击C或Assembly是毫无意义的,因为它们是互补的。实际上,一个人的优势就是另一个人的劣势。C的组织化语法规则提高了清晰度,但同时又放弃了所有汇编程序所拥有的所有结构规则!可以使用C代码指令来创建非阻塞代码,可以说这会增强编程意图的清晰度,但这是一种功耗。在C语言中,编译器不允许在if / elseif / else / end内部跳转。或者,您不允许在彼此重叠的不同变量上编写两个for / end循环,您不能编写自我修改的代码(或不能以无缝的简便方式)等。常规程序员对上述内容感到不满意,并且不知道如何提高这些方法的功能,因为它们已被遵循常规规则。 。事实是这样:今天,我们拥有一台具有计算能力的机器,可以做更多的事来使用它们的应用程序,但是人脑无法在无规则的编码环境(=汇编)中对它们进行编码,并且需要严格的限制性规则减少频谱并简化编码。我自己编写的代码由于上述限制而不能用C代码编写,而不会变得非常低效。而且我还没有谈论速度,大多数人认为这是在汇编中编写文字的主要原因,好吧,如果您只限于在C语言中思考,那么您永远是编译器的奴隶。我一直以为象棋高手是理想的汇编程序员,而C程序员只是在玩“ Dames”。
goto
确实允许在函数内进行非结构化跳转。if()
在同一功能的or循环内包含一个块。例如godbolt.org/z/IINHTg。另请参阅达夫的设备,使用切换/案例进入do{}while()
循环来表示跳入展开的循环。但是在某个时候,如果您陷入那种混乱的境地,可以更清楚地用asm编写。
不再是速度,而是Control。速度有时会来自控制,但这是在汇编中进行编码的唯一原因。其他所有原因归结为控制(即SSE和其他手动优化,设备驱动程序和与设备相关的代码等)。
我曾经接管过一个DSP项目,该项目以前的程序员主要是用汇编代码编写的,除了使用浮点(在定点DSP上)用C语言编写的音调检测逻辑之外。音调检测逻辑以大约实时时间的1/20运行。
我最终从头开始重写了几乎所有内容。除了一些小的中断处理程序和几十行与中断处理和低级频率检测有关的代码外,几乎所有内容都在C语言中,其运行速度是旧代码的100倍以上。
我认为要记住的重要一点是,在许多情况下,使用小型例程进行速度提升的机会要大于使用大型例程进行速度提升的机会,尤其是如果手写汇编程序可以将所有内容都放入寄存器中而编译器则无法做到的话很好管理。如果循环足够大而无法将所有内容都保留在寄存器中,则进行改进的机会就少得多。