为什么要用汇编编程?[关闭]


86

我对那里所有的核心低级黑客都有疑问。我在博客中碰到了这句话。我并不真正认为消息来源很重要(如果您真的很在意,那就是Haack),因为这似乎是一种常见的说法。

例如,许多现代的3D游戏都有以C ++和Assembly编写的高性能核心引擎。

就汇编而言,这是用汇编语言编写的,因为您不希望编译器发出额外的指令或使用过多的字节,或者您使用的是无法在C中表达的更好的算法(或者在没有编译器迷惑了他们)?

我完全明白了解底层内容很重要。我只想理解为什么理解汇编后的程序。


1
我想类似的问题已经在那里...
Mehrdad Afshari

8
Eeeeehh ..从技术上讲这是一个不同的问题。这些问题都是为什么要学习汇编,这就是为什么要在其中学习程序,我认为这是不同的..?
cgp

4
为什么要用汇编编程?-让我们看一下这些问题的一些不可能的答案:1)使我的代码可维护,2)灵活,3)确保可移植性,4)可测试性,5)可读性,...;)
ivan_ivanovich_ivanoff

9
工作安全........
圣哈辛托2009年

3
因为它很有趣.. :)
RainingComputers '16

Answers:


169

我认为您误读了以下语句:

例如,许多现代的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%组装的形式进行设计或编写的-甚至没有关闭。但是,当人们确实需要速度时,他们会专注于编写代码的关键部分,以使用特定的硬件。


12
惊人的反应。希望我们可以将其放入Wiki!
bdd

6
@Paperino ...可以。关于StackOverflow的问题和答案均为许可的创作共用属性。
亚伦·曼帕

有关了解asm以帮助您编写更好的C / C ++的更多信息,请参见为什么此C ++代码比我的用于测试Collat​​z猜想的手写程序集更快?。我的回答是指出,当编译器没有注意到有用的优化时,读取编译器的asm输出和调整源代码可以有所帮助。所以,你的精神(或实际)在ASM写,然后用手按住编译器做你想要什么,但现在你有面向未来的便携式C.
彼得·柯德斯

42

我已经多年没有使用汇编语言进行编码了,但是我可以给出一些我经常看到的原因:

  • 并非所有的编译器都可以利用某些CPU优化和指令集(例如Intel偶尔添加的新指令集)。等待编译器作者赶上来意味着失去竞争优势。

  • 更容易将实际代码与已知的CPU架构和优化进行匹配。例如,您了解获取机制,缓存等方面的知识。这应该对开发人员是透明的,但事实是事实并非如此,这就是编译器编写者可以优化的原因。

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

  • 对于汇编语言而言,形式化推理有时实际上比对高级语言而言更容易,因为您已经知道代码的最终或几乎最终布局是什么。

  • 在没有API的情况下对某些3D图形卡进行编程(大约在1990年代后期)在汇编语言中通常更为实用和高效,而在其他语言中则有时无法实现。但是同样,这涉及真正基于专家架构的专家级游戏,例如按一定顺序手动移入和移出数据。

我怀疑许多人在高级语言可以使用汇编语言时会使用汇编语言,尤其是当该语言是C时。手动优化大量通用代码是不切实际的。


19

汇编程序编程的一个方面没有其他方面提到过-您感到满意的感觉是,您知道应用程序中的每个字节都是您自己的努力的结果,而不是编译器的努力。我不会再想回到80年代初的时候用汇编器编写整个应用程序了,但是有时我确实会错过这种感觉...


3
嘿,这是组装工作的结果!通常,您在asm中编写很多宏。
Mehrdad Afshari

5
不仅满意,而且对准确性的欣赏。一个简明的过程及其所声明的一切都是值得高兴的。
deau


16

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

到80年代后期,我开始使用C。代码更易于编写,调试和维护。很小的代码片段是用汇编器编写的-对我来说,这是我在自己的RTOS中编写上下文切换时的代码。(除非是“科学项目”,否则您不应该再做任何事情。)

您将在一些Linux内核代码中看到汇编程序片段。最近,我以自旋锁和其他同步代码进行了浏览。这些代码段需要访问原子测试和设置操作,操纵缓存等。

我认为您很难为大多数通用编程而对现代C编译器进行优化。

我同意@altCognito的观点,您最好花更多的时间思考问题并做得更好。出于某种原因,程序员经常专注于微效率,而忽略了宏观效率。汇编语言提高性能是一种微观效率。退一步以更广泛地查看系统可能会暴露系统中的宏观问题。解决宏问题通常可以提高性能。解决了宏观问题后,便会陷入微观层面。

我猜想微观问题在单个程序员的控制范围内,并且范围较小。在宏级别上改变行为需要与更多的人进行交流,这是某些程序员避免的事情。整个牛仔队与团队的关系。


10

“是”。但是,请理解,在大多数情况下,用汇编器编写代码的好处是不值得的。以汇编形式编写它所获得的回报往往比仅仅专注于更认真地思考问题并花时间思考更好的做事方式所获得的回报要小。

约翰卡马克(John Carmack)和迈克尔·阿布拉什(Michael Abrash)主要负责编写Quake,ID游戏引擎中使用的所有高性能代码都在本书中详细介绍。

我也同意ÓlafurWaage的观点,即当今的编译器非常聪明,并且经常采用许多利用隐藏的体系结构增强功能的技术。


9

如今,至少对于顺序代码而言,像样的编译器几乎总是击败甚至是经验丰富的汇编语言程序员。但是对于矢量代码,则是另一回事了。例如,广泛使用的编译器在利用x86 SSE单元的矢量并行功能方面做得并不出色。我是一名编译器作家,利用SSE成为我自己而不愿意信任编译器的主要原因。


在这种情况下,我将使用内部编译器。
Mehrdad Afshari

还是不一样。就像没有寄存器优化器的编译器
Marco van de Voort

这取决于您的asm程序员有什么样的经验。如果您已阅读并抱怨agner.org/optimize以了解您要调整的微体系结构,那么仅以短序列 击败编译器通常很容易。至少有一半的时间,我在查看编译器输出的小函数时错过了次要的优化。编译器很棒的地方是通过内联和恒定传播对大型代码库进行优化。
彼得·科德斯

8

至少在MSVC中,SSE代码在汇编中比编译器内部函数更好地工作。(即不会创建额外的数据副本)


好一点,您需要使用内在函数做得不错的编译器。英特尔和Gnu编译器非常出色,我不知道PGI和PathScale的最新版本是否具有竞争力,它们过去从未如此。
杰德

6

在工作中,我有三个或四个汇编程序例程(约20 MB的源代码)。它们都是SSE(2),并且与对(相当大-认为2400x2048及更大)图像的操作有关。

出于爱好,我使用编译器,那里有更多的汇编器。运行时库常常充满了它们,其中大多数与违反正常程序规则的东西(例如异常的助手等)有关。

我的微控制器没有任何汇编程序。大多数现代微控制器都具有如此多的外围硬件(中断控制计数器,甚至整个正交编码器和串行构建块),因此通常不再需要使用汇编器来优化循环。以当前的闪存价格,代码存储器也是如此。此外,通常还存在许多与引脚兼容的设备,因此,如果系统地用完CPU电源或闪存空间,则升频通常不是问题

除非您真的发货了100000台设备,否则编程汇编程序仅通过装配较小类别的闪存芯片就可以真正节省大量资金。但是我不在那一类。

许多人认为嵌入式是汇编程序的借口,但是他们的控制器比Unix开发的机器具有更多的CPU能力。(Microchip带有40和60个MIPS微控制器,价格低于10美元)。

但是,由于改变微芯片架构并不容易,因此很多人都受其遗留。同样,HLL代码在很大程度上取决于体系结构(因为它使用硬件外围设备,寄存器来控制I / O等)。因此,有时有充分的理由继续在汇编器中维护项目(我很幸运能够从头开始在新架构上设置事务)。但是通常人们会自欺欺人,说他们确实需要汇编程序。

我仍然喜欢教授问我们是否可以使用GOTO时给出的答案(但您也可以将其读为ASSEMBLER):“如果您认为有必要写一篇三页的文章说明为什么需要此功能,则可以使用它请把论文和结果一起提交。”

我已将其用作低级功能的指导原则。不要太局促地使用它,但要确保正确地激发它。甚至抛出一两个人为的障碍(如论文),以免混淆推理作为理由。


1
我喜欢作文测试;我可能需要更频繁地使用它;)
前nihilo

5

有些指令/标志/控制根本不在C级别上。

例如,检查x86上的溢出是简单的溢出标志。此选项在C中不可用。


您可以使用位运算来计算C中的溢出标志。
swegi

@swegi:我敢打赌,这慢得多。
布赖恩

多久有用一次?并且当它成为可能时,它可能不是放入汇编器的唯一原因。

5

缺陷倾向于按行运行(语句,代码点等);虽然对于大多数问题而言,汇编使用的行比高级语言要多得多,但在某些情况下,最好的(最简洁,最少的行)映射到了当前的问题。这些案例大多数涉及通常的可疑事件,例如驱动程序和嵌入式系统中的位撞击。


3

另一个原因可能是,可用的编译器对于某种体系结构而言还不够好,并且程序中所需的代码量不至于使程序员迷失其中的时间如此之长或复杂。尝试为嵌入式系统的微控制器编程,通常组装起来会容易得多。


3

除了上面提到的其他事情,所有高级语言都有一定的局限性。因此,有些人选择使用ASM进行编程,以完全控制自己的代码。

其他享受很小的可执行文件,在20-60KB的范围内,例如检查HiEditor,这是由HiEdit控制的作者实施,适用于Windows精湛强大的编辑控件与语法高亮和标签中只有约50KB)。在我的收藏中,我有20多个这样的黄金控件,例如Excel从ssheets到html renders。


3

我认为许多游戏开发人员会对这些信息感到惊讶。

我知道的大多数游戏都尽可能少地使用汇编程序。在某些情况下,一无所有,最坏的情况是一两个循环或函数。

这个说法过于笼统,远没有十年前那么真实。

但是,仅凭事实不应该妨碍真正的黑客对汇编的支持。;)


3

如果您要对具有128字节RAM和4K程序存储器的低端8位微控制器进行编程,那么关于使用汇编的选择就不多了。有时,尽管使用功能更强大的微控制器,您需要在准确的时间采取某些措施。汇编语言很有用,因为您可以计算指令数,因此可以测量代码使用的时钟周期。


2

如果您愿意参加所有的Y2K修复工作,那么如果您了解Assembly,您可能会赚很多钱。仍然有很多遗留的旧代码在其中编写,并且这些代码有时需要维护。


2

除了在非常小的CPU上的很小的项目之外,我不会开始用汇编语言对整个项目进行编程。但是,通常发现可以通过一些内部循环的策略性手动编码来缓解性能瓶颈。

在某些情况下,真正需要做的只是用无法期望优化器弄清楚如何使用的指令来替换某种语言构造。一个典型的例子是在DSP应用程序中,矢量操作和乘法累加操作对于优化器来说很难发现,但是易于编写代码。

例如,SH4的某些模型包含4x4矩阵和4个矢量指令。通过用适当的指令替换3x3矩阵上的等效C操作,我看到了色彩校正算法的巨大性能改进,而付出的代价很小,就是将校正矩阵扩大到4x4以匹配硬件假设。这是通过编写不超过十二行汇编语言,并对相关数据类型进行匹配调整并将其存储到周围的C代码中的少数几个地方来实现的。


2

它似乎没有被提及,因此我想添加一下:在现代游戏开发中,我认为至少编写的某些程序集根本不适合CPU。它以着色器程序的形式用于GPU 。

出于各种原因可能需要这样做,有时仅仅是因为所使用的任何高级着色语言都不允许以所需的确切数量的指令来表示确切的操作,以适应某些大小限制,速度或任何组合。我猜想和汇编语言编程一样。


2

到目前为止,我见过的几乎每个大中型游戏引擎或库都有一些手动优化的汇编版本,可用于矩阵操作(如4x4矩阵级联)。似乎在使用大型矩阵时,编译器不可避免地会错过一些聪明的优化(重用寄存器,以最大效率的方式展开循环,利用机器特定的指令等)。这些矩阵操作函数几乎始终也是概要文件上的“热点”。

我还看到手工编码的程序集大量用于自定义调度-诸如FastDelegate之类的东西,但特定于编译器和计算机。

最后,如果您具有中断服务例程,则asm可以使世界变得与众不同–有些操作您根本不想在中断下进行,并且希望中断处理程序“快速进出”。 ..您几乎完全知道ISR在asm中会发生什么,并且它会鼓励您将血腥的事情保持简短(无论如何都是好习惯)。


2

游戏非常渴望性能,尽管在此期间优化程序非常出色,但“主程序员”仍然可以通过手工编码正确的组件来挤出更多性能。

在没有进行概要分析之前,切勿开始优化程序。分析后应该能够识别瓶颈,如果找不到更好的算法之类的东西了,您可以尝试在汇编中编写一些代码。


2

我只是和一位开发人员谈过他对汇编的使用。他正在开发用于处理便携式mp3播放器控件的固件。进行组装工作有两个目的:

  1. 速度:延迟必须最小。
  2. 成本:通过减少代码量,运行它所需的硬件功能可能会稍差一些。当批量生产数百万个单位时,这可以加起来。

2

我继续做的唯一汇编代码是针对资源匮乏的嵌入式硬件。正如leander提到的那样,汇编仍然非常适合ISR,在ISR中需要快速且易于理解的代码。

对我而言,第二个原因是保持我对装配体的了解。能够检查并理解CPU进行我的投标的步骤感觉很好。


2

我上一次在汇编器中编写的时间是我无法说服编译器生成无libc,位置无关的代码。

下次可能是出于相同的原因。

当然,我以前有其他原因


2

许多人喜欢贬低汇编语言,因为他们从未学会过用汇编语言编写代码,只是隐约地遇到了汇编语言,这使他们感到震惊或有些恐惧。真正有才华的程序员将理解,抨击C或Assembly是毫无意义的,因为它们是互补的。实际上,一个人的优势就是另一个人的劣势。C的组织化语法规则提高了清晰度,但同时又放弃了所有汇编程序所拥有的所有结构规则!可以使用C代码指令来创建非阻塞代码,可以说这会增强编程意图的清晰度,但这是一种功耗。在C语言中,编译器不允许在if / elseif / else / end内部跳转。或者,您不允许在彼此重叠的不同变量上编写两个for / end循环,您不能编写自我修改的代码(或不能以无缝的简便方式)等。常规程序员对上述内容感到不满意,并且不知道如何提高这些方法的功能,因为它们已被遵循常规规则。 。事实是这样:今天,我们拥有一台具有计算能力的机器,可以做更多的事来使用它们的应用程序,但是人脑无法在无规则的编码环境(=汇编)中对它们进行编码,并且需要严格的限制性规则减少频谱并简化编码。我自己编写的代码由于上述限制而不能用C代码编写,而不会变得非常低效。而且我还没有谈论速度,大多数人认为这是在汇编中编写文字的主要原因,好吧,如果您只限于在C语言中思考,那么您永远是编译器的奴隶。我一直以为象棋高手是理想的汇编程序员,而C程序员只是在玩“ Dames”。


1
自修改代码对于JIT一次/多次运行场景之外的大多数现代CPU的性能没有帮助。但是,将常量作为立即数填充是一种有趣的可能性。但是,Cgoto确实允许在函数内进行非结构化跳转。if()在同一功能的or循环内包含一个块。例如godbolt.org/z/IINHTg。另请参阅达夫的设备,使用切换/案例进入do{}while()循环来表示跳入展开的循环。但是在某个时候,如果您陷入那种混乱的境地,可以更清楚地用asm编写。
彼得·科德斯

1
(当然,Duff的设备仅在具有后递增地址的机器上有用,否则展开循环内的那些入口点将失去优化的大部分目的。)
Peter Cordes

1

不再是速度,而是Control。速度有时会来自控制,但这是在汇编中进行编码的唯一原因。其他所有原因归结为控制(即SSE和其他手动优化,设备驱动程序和与设备相关的代码等)。


1

如果我能够胜过GCC和Visual C ++ 2008(也称为Visual C ++ 9.0),那么人们将有兴趣就如何实现这一目标采访我。

这就是为什么现在我只阅读汇编语言,并在需要时仅编写__asm int 3的原因。

希望对您有帮助...


1

我已经几年没有写汇编语言了,但是我以前的两个原因是:

  • 事情的挑战!几年前,我经历了几个月的时间,当时我用x86汇编语言编写所有内容(DOS和Windows 3.1时代)。它基本上教会了我很多底层操作,硬件I / O等。
  • 在某些方面,它保持较小的尺寸(编写TSR时再次使用DOS和Windows 3.1

我继续关注编码汇编,这无非是挑战和乐趣。我没有其他理由这样做:-)


1

我曾经接管过一个DSP项目,该项目以前的程序员主要是用汇编代码编写的,除了使用浮点(在定点DSP上)用C语言编写的音调检测逻辑之外。音调检测逻辑以大约实时时间的1/20运行。

我最终从头开始重写了几乎所有内容。除了一些小的中断处理程序和几十行与中断处理和低级频率检测有关的代码外,几乎所有内容都在C语言中,其运行速度是旧代码的100倍以上。

我认为要记住的重要一点是,在许多情况下,使用小型例程进行速度提升的机会要大于使用大型例程进行速度提升的机会,尤其是如果手写汇编程序可以将所有内容都放入寄存器中而编译器则无法做到的话很好管理。如果循环足够大而无法将所有内容都保留在寄存器中,则进行改进的机会就少得多。


0

解释Android手机上Java应用程序的字节码的Dalvik VM使用调度程序的汇编程序。这部电影(大约31分钟,但值得观看整部电影!)说明了如何

“在某些情况下,人类可以比编译器做得更好。”


0

我没有,但是我至少应该尝试一下,并在将来的某个时候努力尝试(希望不久后)。当我使用高级语言进行编程时,了解更多的低级内容以及幕后工作原理并不是一件坏事。不幸的是,作为开发人员/顾问和父母全职工作很难获得时间。但是,我会在适当的时候给予帮助,这是肯定的。

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.