你为什么要组装程序?

我有一个问题,所有的硬核低级黑客在那里。 我在博客中碰到了这个句子。 我真的不认为这个来源很重要(如果你真的关心,那就是哈克),因为这似乎是一个普遍的说法。

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

就assembly而言 – 是用汇编编写的代码,因为你不希望编译器发出额外的指令或使用过多的字节,或者你使用了更好的algorithm,而这些algorithm在C中不能expression(或者不能用编译器把他们扼杀起来)?

我完全明白,理解低层次的东西是很重要的。 我只是想了解为什么在程序集合了解之后。

我认为你误解了这个说法:

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

游戏(现在大多数程序)并不像“用C ++编写”那样是用汇编语言编写的。 那个博客并不是说这个游戏的很大一部分是在汇编中devise的,也不是说程序员团队围坐在一起,并把汇编作为他们的主要语言。

真正意味着开发人员首先编写游戏并使用C ++工作。 然后他们分析它,找出瓶颈是什么,如果这是值得的,他们在组装中优化它们。 或者,如果他们已经有了经验,他们就知道哪些部分将会成为瓶颈,并且他们已经从他们已经build立的其他游戏中获得了优化的部分。

程序集中的重点与以往一样: 速度 。 在汇编程序中编写大量的代码是很荒谬的,但编译器并没有意识到有一些优化,而对于足够小的代码窗口,人类将会做的更好。

例如,对于浮点,编译器往往是相当保守的,可能不知道你的架构的一些更高级的function。 如果你愿意接受一些错误,你通常可以比编译器做得更好,如果你发现大量的代码花费在汇编上,那么在汇编代码中写一点代码是值得的。

这里有一些更相关的例子:

游戏示例

  • 英特尔关于使用SSE内在函数优化游戏引擎的文章。 最终的代码使用内在函数(不是内联汇编器),所以纯汇编的数量非常小。 但是他们看着编译器输出的汇编器来确定要优化的东西。

  • Quake的快速平方根 。 程序中再也没有汇编程序,但是你需要了解一些关于体系结构的内容来做这种优化。 作者知道哪些操作是快速的(乘法,移位),哪些是慢的(除法,sqrt)。 所以他们提出了一个非常棘手的平方根实现,完全避免了缓慢的操作。

高性能计算

  • 在游戏领域之外,科学计算领域的人们经常对事物进行优化,使其在最新的硬件上快速运行。 把这个想象成游戏,你不能在物理上作弊。

    一个最近的例子是格子量子色动力学(格子QCD) 。 本文描述了这个问题是如何归结为一个非常小的计算内核,这个内核在IBM Blue Gene / L上对PowerPC 440进行了大量优化。 每个440有两个FPU,它们支持一些特殊的三元操作,这对编译器来说是非常棘手的。 如果没有这些优化,莱迪思QCD的运行速度将会非常慢,当您的问题需要昂贵的机器上数百​​万个CPU小时时,这是非常昂贵的。

    如果你想知道为什么这很重要,请查看这篇文章中的科学文章 。 使用格子QCD,这些人从第一原理计算质子的质量,并且去年显示了90%的质量来自强大的结合能,其余的来自夸克。 这就是E = mc 2的行动。 这是一个总结 。

对于上述所有情况,应用程序的devise或编写都不是百分百的 – 甚至不是很接近。 但是,当人们真的需要速度的时候,他们专注于编写代码的关键部分,以便在特定的硬件上运行。

我多年来没有用汇编语言编写代码,但我可以给出几个我经常看到的原因:

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

  • 更容易匹配实际的代码到已知的CPU架构和优化。 例如,你知道的关于提取机制,caching等等的东西。这对开发人员来说应该是透明的,但事实是,这不是,这就是编译器编写者可以优化的原因。

  • 某些硬件级别的访问只能通过汇编语言来实现(例如,在写入设备驱动程序时)。

  • 对于汇编语言来说,forms推理有时候比高级语言更容易,因为你已经知道代码的最终或几乎最终的布局是什么。

  • 在没有API的情况下编程某些3Dgraphics卡(大约在20世纪90年代后期)在汇编语言中通常更加实用和高效,有时在其他语言中是不可能的。 但是,这又涉及到基于加速器体系结构的真正的专家级游戏,如按照特定顺序手动移入和移出数据。

我怀疑许多人在使用汇编语言时会使用汇编语言,特别是当语言是C语言的时候。手工优化大量的通用代码是不切实际的。

通常,外行的程序集比C慢(由于C的优化),但很多游戏(我清楚地记得Doom )必须在程序集中有特定的游戏部分,所以在普通机器上运行会很顺畅。

这就是我所指的例子。

其他人没有提到的汇编程序编程有一个方面 – 满意的感觉是你知道应用程序中的每个字节都是你自己努力的结果,而不是编译器的结果。 我不想再像80年代初期那样用汇编语言编写整个应用程序了,但是我有时候会错过这种感觉…

我在第一份工作(80年代)中开始使用汇编语言进行专业编程。 对于embedded式系统,内存要求(RAM和EPROM)很低。 你可以编写简单的代码,在资源上很容易。

到了80年代末,我已经转向C.代码更容易编写,debugging和维护。 非常小的代码片段是用汇编语言编写的 – 对于我来说,是在我自己的RTOS中编写上下文切换的时候。 (除非它是一个“科学计划”,否则你不应该再去做了。)

你会在一些Linux内核代码中看到汇编代码片段。 最近我用自旋锁和其他同步代码浏览过它。 这些代码片段需要访问primefacestesting和设置操作,操作caching等。

我认为你会很难为大多数通用编程优化现代C编译器。

我同意@altCognito的意见,你的时间可能会更好地思考问题,做更好的事情。 出于某种原因,程序员经常关注微观效率,忽视macros观效率。 汇编语言提高性能是一种微观效率。 为了更广泛地观察系统,可以暴露系统中的macros观问题。 解决macros观问题往往能带来更好的性能收益。 一旦macros观问题得到解决,就会陷入微观层面。

我想微观问题是在一个程序员的控制之下,在一个较小的领域。 改变macros观层面的行为需要与更多的人交stream – 一些程序员避免的。 整个牛仔队比赛的事情。

“是”。 但是,要明白在绝大多数情况下,在汇编程序中编写代码的好处是不值得的。 在汇编中收到的回报往往比仅仅关注思考问题更困难,花时间思考更好的行事方式。

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

我也同意ÓlafurWaage的观点,今天,编译器非常聪明,并且经常采用许多利用隐藏的架构提升的技术。

SSE代码在编译时比编译器内在函数更好,至less在MSVC中。 (即不会创build额外的数据副本)

现在,至less对于顺序代码来说,一个体面的编译器几乎总是能打败一个经验丰富的汇编语言程序员。 但是对于vector代码,这是另一回事。 例如,广泛部署的编译器并不能很好地利用x86 SSE单元的向量并行function。 我是一个编译器编写者, 利用SSE ,让我自己去列举一些理由,而不是相信编译器。

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

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

缺陷倾向于按行(语句,代码点等)运行; 对于大多数问题来说,确实如此,组装会使用比高级语言多得多的线,偶尔也会遇到最好的(最简洁,最less的线)。 这些案件中的大部分涉及通常的嫌疑人,如embedded式系统中的驱动程序和位撞事件。

另一个原因可能是当可用的编译器对体系结构不够好而且程序所需的代码量不是很长或者很复杂时,程序员就会迷失方向。 尝试为embedded式系统编程一个微控制器,通常组装将会容易得多。

除了其他提到的东西,所有更高级的语言都有一定的限制。 这就是为什么有些人select在ASM编程,以完全控制他们的代码。

其他人可以使用非常小的可执行文件,范围在20-60KB之间,例如查看HiEditor ,HiEdit控件的作者实现的HiEditor,用于Windows的超级强大编辑控件,语法高亮,标签只有〜50kb)。 在我的collections中,我有超过20个这样的黄金控件Excell喜欢ssheets到html渲染。

我想很多游戏开发者会对这些信息感到惊讶。

我所知道的大部分游戏尽可能使用小组件。 在某些情况下根本没有,最坏的情况是一个或两个循环或function。

这句话是过度泛化的,远不及十年前那样真实。

但是,嘿,单纯的事实不应该阻碍一个真正的黑客讨伐集会。 ;)

如果你正在编程一个128字节的RAM和4K的程序存储器的低端8位微控制器,你没有太多的select使用大会。 有时,当使用更强大的微控制器时,您需要在确切的时间进行一定的操作。 汇编语言有用,因为您可以对指令进行计数,并测量代码使用的时钟周期。

我在工作中有三到四个汇编程序(约20 MB源代码)。 所有这些都是上证所(2) ,并且涉及(相当大 – 认为2400×2048和更大的图像)的操作。

对于业余爱好,我在编译器上工作,在那里你有更多的汇编器。 运行时库往往充满了他们,他们中的大多数都与那些违背正常程序机制的东西(例如像例外的助手一样)

我没有任何我的微控制器的汇编程序。 大多数现代微控制器都有很多外围硬件(中断控制计数器,甚至整个正交编码器和串行构build模块),使用汇编器来优化环路通常不再需要。 以目前的闪存价格,代码存储器也是如此。 另外,通常还有引脚兼容器件的范围,所以如果系统地耗尽CPU功率或闪存空间,升级通常不成问题

除非你真的在运送100000个器件,而编程汇编器可以通过将闪存芯片装入一个更小的类别来真正节省大量的成本。 但我不在这个范畴

很多人认为embedded式是汇编器的借口,但是他们的控制器比Unix开发的机器更具有CPUfunction。 (Microchip以40 美元和60 美元的微控制器价格低于10 美元 )。

然而,很多人都遗留下来,因为改变芯片架构并不容易。 HLL代码也是非常依赖于架构的(因为它使用硬件外设,寄存器来控制I / O等)。 所以有时候有很好的理由继续在汇编程序中维护一个项目(我很幸运能够从头构build一个新的架构的事务)。 但是人们常常自欺欺人地认为他们确实需要汇编程序。

如果你是为了所有的千年虫治理工作而努力的话,如果你知道大会,你可能赚了很多钱。 仍然有大量的遗留代码被写入,代码偶尔需要维护。

除了非常小的CPU上的非常小的项目,我不打算永远编程整个项目。 然而,通常发现一些内部环路的策略性手工编码可以缓解性能瓶颈。

在某些情况下,真正需要的是用一个优化器不能指望如何使用的指令replace某个语言结构。 一个典型的例子是在DSP应用中,vector运算和乘法累加运算对于优化器难以发现是困难的,但易于手工编码。

例如,SH4的某些模型包含4x4matrix和4个vector指令。 通过用相应的指令replace3×3matrix上的等效C运算,我发现在色彩校正algorithm中性能有了巨大的改进,但要以将校正matrix放大到4×4的微小代价来满足硬件假设。 这是通过编写不超过十几行程序集,并将相关数据types和存储的匹配调整到周围C代码中的less数几个地方来实现的。

这似乎没有提到,所以我想我会补充一点:在现代游戏开发中,我认为至less有一些正在编写的程序集根本不适用于CPU。 这是针对GPU,以着色器程序的forms。

这可能是出于各种原因需要的,有时候仅仅是因为无论使用哪种更高级的着色语言,都不允许准确的操作以所需的指令的数量来expression,以适应一些尺寸限制,速度或任何组合。 就像往常一样,汇编语言编程,我想。

几乎我所见过的每个中到大型游戏引擎或库都有一些手动优化的程序集版本,可用于matrix运算,如4x4matrix级联。 似乎编译器在处理大型matrix时,不可避免地会错过一些巧妙的优化(重用寄存器,以最有效的方式展开循环,利用特定于机器的指令等)。 这些matrix处理函数在configuration文件中几乎总是“热点”。

我还看到手工编码的程序集用于自定义调度 – 像FastDelegate,但编译器和机器特定。

最后,如果你有中断服务程序,asm可以在世界上有所作为 – 有些操作你不希望在中断的情况下发生,你希望你的中断处理程序“快速进出”。 ..你几乎知道ISR中会发生什么,如果它是在ASM中,它鼓励你保持血腥的东西短(这是一个很好的做法)。

游戏性能相当不错,虽然优化器相当不错,但“主程序员”仍然能够通过手工编写正确的部件来获得更高的性能。

永远不要开始优化你的程序,而不要先分析它。 分析后应该能够找出瓶颈,如果find更好的algorithm等不要再削减它,你可以尝试手工编写一些东西在组装。

我只是亲自和一位开发者谈过他使用程序集。 他正在研究处理便携式MP3播放器控制的固件。 集会中的工作有两个目的:

  1. 速度:延迟需要最小化。
  2. 成本:通过代码最小化,运行它所需的硬件可能稍微不那么强大。 当批量生产数百万单位时,这可以加起来。

我继续做的唯一汇编编码是用于资源不足的embedded式硬件。 正如Leander所提到的,程序集仍然非常适合ISR ,代码需要被快速理解。

我的第二个原因是保持我的assemblyfunction知识。 能够检查和理解CPU为我的出价所采取的步骤只是感觉良好。

上次我在汇编程序中编写的时候,我无法说服编译器生成无libc,位置独立的代码。

下一次可能会出于同样的原因。

当然,我曾经有其他的原因 。

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

如果我能胜过GCC和Visual C ++ 2008(也被称为Visual C ++ 9.0),那么人们会有兴趣就我如何可能的进行访谈。

这就是为什么现在我刚刚阅读汇编中的东西,只是在需要时写入__asm int 3。

我希望这个帮助…

我几年没有汇编,但我曾经的两个理由是:

  • 事情的挑战! 几年前我经历了几个月的时间,在x86汇编 ( DOS和Windows 3.1的时代)写入所有东西。 它基本上教会了我一些低级操作,硬件I / O等。
  • 对于一些东西,它保持规模小(同时写入TSR的DOS和Windows 3.1 )

我一直都在看编码组合,只不过是事物的挑战和喜悦罢了。 我没有其他理由这样做:-)

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

我从头开始重写了几乎所有的东西。 除了一些小的中断处理程序和几十行与中断处理和低级频率检测相关的代码之外,几乎所有东西都是C语言,其运行速度比旧代码快100倍。

我认为需要记住的一件重要的事情是,在很多情况下,使用小程序比使用大程序提高速度的机会要多得多,特别是如果手写汇编程序可以将所有内容都放在寄存器中,但是编译器不会相当pipe理。 如果一个循环足够大,无法将所有内容都保存在寄存器中,那么改进的机会就会less得多。

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

“还有一些情况是人类可以比编译器做得更好”。

我不这样做,但我已经提出了一个观点,至less可以尝试一下,在这个问题的某个时刻努力尝试(希望很快)。 当我用高级语言进行编程时,了解更多低层次的东西以及幕后的情况并不是一件坏事。 不幸的是,作为一名开发者/顾问和一名家长,全职工作很难得到。 但是我会在适当的时候放弃,这是肯定的。