为什么要在汇编中编程?

时间:2009-04-26 20:21:13

标签: c performance low-level assembly

我对所有核心低级别黑客都有疑问。我在博客中遇到了这句话。我并不认为这些来源很重要(如果你真的在意,那就是哈克),因为这似乎是一个常见的陈述。

  

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

就程序集而言 - 是用汇编编写的代码,因为你不希望编译器发出额外的指令或使用过多的字节,或者你是否使用了你无法在C中表达的更好的算法(或者可以' t表达没有编译器把它们搞砸了吗?

我完全明白了解低级别的东西很重要。我只是想在理解之后理解汇编中的 why 程序。

30 个答案:

答案 0 :(得分:166)

我认为你误读了这句话:

  

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

游戏(现在大多数程序)都不像“用C ++编写”那样“以汇编语言”编写。该博客并不是说游戏的很大一部分是在汇编中设计的,或者是一个程序员团队坐在那里并以汇编作为主要语言进行开发。

这个真正意味着开发人员首先编写游戏并使其在C ++中运行。然后他们对它进行分析,弄清楚瓶颈是什么,以及是否值得他们在装配中优化它们。或者,如果他们已经有经验,他们知道哪些部分会成为瓶颈,并且他们已经从他们构建的其他游戏中获得了优化的部分。

装配中编程的与以往一样:速度。在汇编程序中编写 lot 代码是荒谬的,但编译器并不知道有一些优化,而对于足够小的代码窗口,人类会做得更好。

例如,对于浮点数,编译器往往非常保守,可能不了解您的架构的一些更高级的功能。如果你愿意接受一些错误,你通常可以比编译器做得更好,如果你发现在它上花了很多时间,那么在汇编中写一点代码是值得的。

以下是一些更相关的例子:

游戏中的示例

  • Article from Intel关于使用SSE内在函数优化游戏引擎。最终代码使用内在函数(不是内联汇编程序),因此纯汇编的数量非常少。但他们会查看编译器的汇编输出,以确定要优化的内容。

  • Quake的fast inverse square root。同样,例程中没有汇编程序,但您需要了解一些有关架构的知识才能进行此类优化。作者知道哪些操作是快速的(乘法,移位),哪些是慢的(除法,sqrt)。因此,他们提出了一个非常棘手的平方根实现,可以完全避免缓慢的操作。

高性能计算

  • 在游戏领域之外,科学计算领域的人们经常优化垃圾,让他们在最新的硬件上快速运行。把它想象成你不能在物理上作弊的游戏。

    最近一个很好的例子是Lattice Quantum Chromodynamics (Lattice QCD)This paper描述了问题如何归结为一个非常小的计算内核,该内核在IBM Blue Gene/L上针对PowerPC 440进行了大量优化。每个440都有两个FPU,它们支持一些特殊的三元运算,这些运算对于编译器来说很难开发。如果没有这些优化,Lattice QCD的运行速度会慢得多,当您的问题需要昂贵的机器上数百​​万的CPU时间时,这会很昂贵。

    如果您想知道为什么这很重要,请查看本作品中出现的article in Science。使用Lattice QCD,这些人从第一原理计算质子的质量,并且显示去年90%的质量来自强力结合能,其余来自夸克。这是E=mc2在行动。 Here's a summary

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

答案 1 :(得分:42)

我多年没有用汇编语言编写代码,但我可以给出一些我经常看到的理由:

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

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

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

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

  • 在没有API的情况下编程某些3D图形卡(大约在20世纪90年代后期)在汇编语言中通常更加实用和有效,有时在其他语言中是不可能的。但同样,这涉及基于加速器架构的专家级游戏,例如以特定顺序手动移入和移出数据。

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

答案 2 :(得分:19)

汇编程序编程的一个方面是其他人没有提到的 - 你知道应用程序中的每个字节都是你自己努力的结果,而不是编译器的结果。我不会像往常一样在80年代早期回到编写汇编程序中的整个应用程序,但我有时会想念这种感觉......

答案 3 :(得分:16)

通常情况下,外行人的装配比C慢(由于C的优化),但很多游戏(我清楚地记得Doom)必须在装配中具有游戏的特定部分,因此它可以在普通机器上平稳运行。

Here's the example to which I am referring.

答案 4 :(得分:15)

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

到80年代末,我已经切换到C.代码更容易编写,调试和维护。非常小的代码片段是用汇编语言编写的 - 对我而言,当我在自己动手的RTOS中编写上下文切换时。 (除非是“科学项目”,否则你不应该做的事情。)

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

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

我同意@altCognito的意见,你的时间可能更好地花在思考问题上并做得更好。出于某种原因,程序员经常关注微观效率并忽视宏观效率。汇编语言提高性能是一种微观效率。退回以获得更广泛的系统视图可能会暴露系统中的宏问题。解决宏观问题往往可以带来更好的性能提升。 一旦宏观问题得到解决,就会崩溃到微观层面。

我认为微问题在单个程序员的控制范围内,在较小的域中。改变宏观层面的行为需要与更多人沟通 - 这是程序员避免的事情。整个牛仔与团队的关系。

答案 5 :(得分:10)

“是”。但是,要理解在大多数情况下,在汇编程序中编写代码的好处并不值得付出努力。在汇编中收到的回报往往比简单地专注于更加努力地思考问​​题,并花时间考虑更好的做法。

John McCmack和Michael Abrash主要负责编写Quake以及ID游戏引擎的所有高性能代码,详细介绍了book

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

答案 6 :(得分:9)

现在,至少对于顺序代码来说,一个体面的编译器几乎总是胜过一个经验丰富的汇编语言程序员。但对于矢量代码而言,这是另一个故事。例如,广泛部署的编译器在利用x86 SSE单元的矢量并行功能方面做得不是很好。我是一名编译器编写者,利用SSE 是我自己的原因列表,而不是信任编译器。

答案 7 :(得分:8)

SSE代码在汇编方面比编译器内在函数更好,至少在MSVC中如此。 (即不创建额外的数据副本)

答案 8 :(得分:5)

缺陷倾向于每行运行(语句,代码点等);虽然对于大多数问题来说,汇编会比使用更高级别的语言使用更多的行,但偶尔也会出现最好(最简洁,最少的行)映射到手头问题的情况。这些案例大多涉及通常的嫌疑,例如驱动程序和嵌入式系统中的位冲击。

答案 9 :(得分:5)

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

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

答案 10 :(得分:4)

我的工作源中有三到四个汇编程序(大约20 MB源代码)。所有这些都是SSE(2),与(相当大的 - 想想2400x2048和更大)图像的操作有关。

对于业余爱好,我在编译器上工作,你有更多的汇编程序。运行时库经常充满它们,大多数都与那些违反正常程序制度的东西有关(比如异常帮助器等)。

我的微控制器没有任何汇编程序。大多数现代微控制器具有如此多的外围硬件(中断控制计数器,甚至整个quadrature encoder和串行构建模块),通常不再需要使用汇编器来优化环路。对于当前的闪存价格,代码存储器也是如此。此外,通常存在引脚兼容设备的范围,因此如果系统地耗尽cpu功率或闪存空间,则升级通常不是问题

除非您真正发运100000个设备并且编程汇编程序可以通过在闪存芯片中安装更小的类别来实现重大节省。但我不在那个类别。

很多人认为嵌入式是汇编程序的借口,但是它们的控制器比开发的Unix机器具有更多的CPU能力。 (Microchip即将推出 在MIPS下使用40和60 USD微控制器。

然而,由于改变微芯片架构并不容易,很多人仍然坚持传统。 HLL代码也非常依赖于体系结构(因为它使用硬件外围设备,寄存器来控制I / O等)。因此,有时候有充分的理由继续在汇编程序中维护项目(我很幸运能够从头开始在新架构上设置事务)。但是,人们常常自欺欺人,说他们确实需要汇编程序。

当我们问我们是否可以使用GOTO时,我仍然喜欢教授给出的答案(但你也可以将其视为汇编者):“如果你认为值得写一篇3页的文章,说明为什么你需要功能,你可以使用它。请提交你的结果文章。“

我已将其作为低级功能的指导原则。不要太吝啬使用它,但要确保你正确地激励它。甚至抛出一两个人为的障碍(如文章),以避免错综复杂的推理作为理由。

答案 11 :(得分:3)

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

我所知道的大多数游戏都尽可能少使用。在某些情况下,根本没有,最糟糕的是,有一个或两个循环或函数。

这句话过于笼统,远不及十年前那么真实。

但是,嘿,仅仅是事实不应该妨碍真正的黑客支持集会的讨伐。 ;)

答案 12 :(得分:3)

除了其他提及的内容之外,所有更高级的语言都有一定的局限性。这就是为什么有些人选择在ASM中编程,以完全控制他们的代码。

其他人享受非常小的可执行文件,范围为20-60KB,例如check HiEditor,这是由HiEdit控件的作者实现的,Windows的超级强大编辑控件,只有语法高亮和标签〜 50KB)。在我的收藏中,我有超过20个来自Excell的黄金控件,如ssheets到html渲染。

答案 13 :(得分:3)

另一个原因可能是当可用的编译器对于体系结构来说不够好时,程序中所需的代码量不会那么长或太复杂,因为程序员会迷失它。尝试为嵌入式系统编程微控制器,通常组装会更容易。

答案 14 :(得分:3)

如果您正在编程一个具有128字节RAM和4K程序存储器的低端8位微控制器,那么您对使用汇编没有太多选择。有时虽然使用功能更强大的微控制器时,您需要在确切的时间执行某项操作。汇编语言非常有用,因为您可以对指令进行计数,从而测量代码使用的时钟周期。

答案 15 :(得分:2)

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

答案 16 :(得分:2)

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

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

当然,我曾经有过其他reasons

答案 17 :(得分:2)

我只是亲自与一位开发人员谈过他对装配的使用。 他正在研究处理便携式mp3播放器控件的固件。 在汇编中完成工作有两个目的:

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

答案 18 :(得分:2)

游戏性能非常好,虽然同时优化器非常好,但“主程序员”仍然能够通过手动编写正确的部件组件来提高性能。

永远不要在没有首先进行分析的情况下开始优化程序。在分析之后应该能够识别瓶颈,如果找到更好的算法之类的东西不再削减它,你可以尝试在汇编中手工编写一些东西。

答案 19 :(得分:2)

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

我的第二个原因是保持我对装配功能的了解。能够检查和理解CPU为我的出价采取的步骤感觉很好。

答案 20 :(得分:2)

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

我也看到手工编码的程序集大量用于自定义调度 - 比如FastDelegate,但是编译器和机器特定的。

最后,如果您有中断服务例程,asm可以在世界上发挥重要作用 - 您可能不希望在中断期间发生某些操作,并且您希望中断处理程序“进入和退出”快速“......你知道几乎完全你的ISR会发生什么事情,如果它在asm中,它鼓励你保持血腥的东西短(这是好的做法)。

答案 21 :(得分:2)

似乎没有提及,所以我想我会添加它:在现代游戏开发中,我认为至少有一些正在编写的程序集根本不适用于CPU。这是GPU的形式,shader programs

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

答案 22 :(得分:2)

如果您参与所有Y2K补救工作,如果您了解大会,您本可以赚很多钱。还有大量的遗留代码写在其中,而且代码偶尔也需要维护。

答案 23 :(得分:2)

除了非常小的CPU上的非常小的项目,我不打算在程序集中编写整个项目。但是,通常会发现一些内部循环的战略手动编码可以缓解性能瓶颈。

在某些情况下,所有真正需要的是将一些语言结构替换为不能指望优化器能够弄清楚如何使用的指令。一个典型的例子是在DSP应用中,优化器很难发现向量运算和乘法累加运算,但易于编写代码。

例如,SH4的某些模型包含4x4矩阵和4个向量指令。我通过用适当的指令替换3x3矩阵上的等效C操作,在色彩校正算法中看到了巨大的性能改进,只需将校正矩阵扩大到4x4以匹配硬件假设。这是通过编写不超过十几行程序集,并对相关数据类型和存储进行匹配调整到周围C代码中的少数几个位置来实现的。

答案 24 :(得分:1)

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

答案 25 :(得分:1)

如果我能够胜过GCC和Visual C ++ 2008(也称为Visual C ++ 9.0),那么人们会有兴趣采访我如何做到这一点。

这就是为什么我刚刚在汇编中读取内容并在需要时写入__asm int 3。

我希望这有帮助...

答案 26 :(得分:1)

我在集会上写了几年,但我以前的两个原因是:

  • 事情的挑战!我经历了几个月的岁月 以前,当我在x86 assemblyDOS和Windows的日子里写下所有内容时 3.1)。它基本上教会了我一大块低级操作,硬件I/O等等。
  • 对于某些东西,它的尺寸很小(写Windows 3.1时再次是DOS和TSR

我一直在关注编码装配,而这只不过是对事物的挑战和喜悦。我没有其他理由这样做: - )

答案 27 :(得分:1)

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

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

我认为,要记住的一件重要事情是,在许多情况下,使用小程序而不是大程序会增加速度的机会,特别是如果手写汇编程序可以适合寄存器中的所有内容但编译器不太会管理。如果一个循环足够大,无论如何都无法将所有内容保存在寄存器中,那么改进的机会就会少得多。

答案 28 :(得分:0)

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

  

“仍然存在人类可以比编译器做得更好的情况”。

答案 29 :(得分:0)

我没有,但我已经说明了至少尝试,并在未来的某个时刻努力(很快就会有希望)。当我用高级语言编程时,了解更多低级别的东西以及幕后工作的效果并不是一件坏事。不幸的是,作为开发人员/顾问和父母的全职工作很难获得时间。但我会在适当的时候放弃,这是肯定的。