为啥要在汇编中编程? [关闭]

Posted

技术标签:

【中文标题】为啥要在汇编中编程? [关闭]【英文标题】:Why do you program in assembly? [closed]为什么要在汇编中编程? [关闭] 【发布时间】:2010-10-21 22:47:06 【问题描述】:

我有一个问题要问所有的铁杆低级黑客。我在博客中看到了这句话。我真的不认为来源很重要(如果你真的在乎,那就是 Haack),因为它似乎是一种常见的说法。

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

就程序集而言 - 是用程序集编写的代码,因为您不希望编译器发出额外的指令或使用过多的字节,或者您正在使用无法用 C 表达(或不能)的更好的算法t 表达没有编译器弄乱它们)?

我完全明白理解底层内容很重要。 why 程序在你理解之后我只想理解它。

【问题讨论】:

类似的问题已经存在,我想... Eeeeehh .. 从技术上讲,这是一个不同的问题。这些问题都是为什么要学习汇编,这就是为什么要在其中编程,这..我认为不同....? 为什么要在汇编中编程? -- 让我们看看这些问题的一些不可能的答案:1) 使我的代码可维护,2) 灵活,3) 确保可移植性,4) 可测试性,5) 可读性,... ;) 工作保障........ 因为它很有趣.. :) 【参考方案1】:

我认为你误读了这句话:

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

游戏(以及当今大多数程序)并不是“用汇编语言编写的”,就像它们“用 C++ 编写的”一样。那篇博客并不是说游戏的很大一部分是用汇编设计的,或者一群程序员坐在那里以汇编作为他们的主要语言进行开发。

真正的意思是开发人员首先编写游戏并让它在 C++ 中运行。然后他们对其进行分析,找出瓶颈是什么,如果值得,他们会在组装中优化它们。或者,如果他们已经有经验,他们知道哪些部分会成为瓶颈,并且他们已经从他们构建的其他游戏中获得了优化的部分。

汇编编程的要点一如既往:速度。在汇编器中编写很多代码会很荒谬,但是编译器没有意识到一些优化,并且对于足够小的代码窗口,人类会做得更好。

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

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

游戏示例

Article from Intel 关于使用 SSE 内在函数优化游戏引擎。最终代码使用了intrinsic(不是inline assembler),所以纯汇编的量很小。但他们会查看编译器的汇编器输出,以确定要优化的确切内容。

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% 以汇编语言设计或编写的——甚至没有接近。但当人们真正需要速度时,他们会专注于编写代码的关键部分以在特定硬件上运行。

【讨论】:

惊人的反应。希望我们能把它放在维基上! @Paperino ...你可以。 *** 上的问题和答案是许可的知识共享署名。 有关了解 asm 以帮助您编写更好的 C/C++ 的更多信息,请参阅Why is this C++ code faster than my hand-written assembly for testing the Collatz conjecture?。我的回答指出,当编译器没有注意到有用的优化时,读取编译器 asm 输出并调整源代码会有所帮助。所以你精神上(或实际上)用 asm 编写,然后手持编译器做你想做的事,但现在你有面向未来的可移植 C。【参考方案2】:

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

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

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

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

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

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

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

【讨论】:

【参考方案3】:

汇编程序编程的一个方面是其他人没有提到的——当您知道应用程序中的每个字节都是您自己努力的结果,而不是编译器的结果时,您会感到满足。我一秒钟都不想像 80 年代初那样用汇编程序编写整个应用程序,但有时我确实怀念这种感觉……

【讨论】:

嘿,这是汇编工作的结果!你通常在 asm 中编写很多宏。 不只是满足,而是对精确度的欣赏。一个简洁的过程,其中包含所有内容,令人赏心悦目。【参考方案4】:

通常,外行的组装比 C 慢(由于 C 的优化),但许多游戏(我清楚地记得 Doom)必须在组装中包含游戏的特定部分,这样它才能在普通机器上顺利运行。

Here's the example to which I am referring.

【讨论】:

+1 非常正确。人类非常不擅长编写长 asm 代码。 请记住,在编写汇编程序时,上述工具并不总是可用。【参考方案5】:

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

到 80 年代后期,我已切换到 C。代码更易于编写、调试和维护。非常小的 sn-ps 代码是用汇编程序编写的——对我来说,这是我在自己滚动的 RTOS 中编写上下文切换的时候。 (除非是“科学项目”,否则你不应该再做的事情。)

您会在一些 Linux 内核代码中看到汇编程序 sn-ps。最近我在自旋锁和其他同步代码中浏览过它。这些代码需要访问原子测试和设置操作、操作缓存等。

我认为对于大多数通用编程而言,您很难超越现代 C 编译器的优化。

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

我猜微问题在单个程序员的控制范围内并且在较小的域中。在宏观层面上改变行为需要与更多的人交流——一些程序员会避免这样做。整个牛仔与团队的关系。

【讨论】:

【参考方案6】:

“是的”。但是,请理解,在大多数情况下,用汇编程序编写代码的好处并不值得付出努力。在汇编中编写它所获得的回报往往比简单地专注于更努力地思考问​​题并花时间思考更好的做事方式所获得的回报。

主要负责编写 Quake 的 John Carmack 和 Michael Abrash 以及进入 IDs 游戏引擎的所有高性能代码在 book 中详细介绍了这一点。

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

【讨论】:

【参考方案7】:

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

【讨论】:

在这种情况下,我会使用编译器内在函数。 还是不一样。这就像一个没有寄存器优化器的编译器 这取决于你的 asm 程序员有什么样的调味料。如果您已经阅读并摸索agner.org/optimize 以了解您正在调整的微体系结构,请击败编译器仅适用于短序列 is often easy。在查看小函数的编译器输出时,至少有一半的时间我看到错过的小优化。编译器的优势在于通过内联和常量传播对大型代码库进行优化。【参考方案8】:

SSE 代码在汇编中比编译器内在函数更有效,至少在 MSVC 中是这样。 (即不创建额外的数据副本)

【讨论】:

好点子,你需要一个能很好地处理内在函数的编译器。 Intel 和 Gnu 编译器相当不错,我不知道 PGI 和 PathScale 的最新版本是否有竞争力,他们以前没有。【参考方案9】:

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

出于爱好,我在编译器上工作,而你有更多的汇编器。运行时库通常充满了它们,其中大多数与违反正常程序制度的东西有关(例如异常的帮助器等)

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

除非您真的出货了 100000 台设备,并且编程汇编器可以通过在闪存芯片中安装更小的类别来真正节省大量成本。但我不属于那个类别。

很多人认为嵌入式是汇编程序的借口,但他们的控制器比 Unix 开发的机器拥有更多的 CPU 能力。 (微芯片来了 40 和 60 个 MIPS 微控制器用于 USD 10)。

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

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

我将其用作低级功能的指导原则。不要太拥挤而无法使用它,但要确保你正确地激励它。甚至设置一两个人为的障碍(如文章)以避免将复杂的推理作为理由。

【讨论】:

我喜欢essay-test;我可能需要更频繁地使用它;)【参考方案10】:

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

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

【讨论】:

您可以在 C 中使用位运算计算溢出标志。 @swegi:我敢打赌这速度慢了很多。 多久有用一次?如果是这样,它不可能是进入汇编程序的唯一原因。【参考方案11】:

缺陷倾向于逐行运行(语句、代码点等);虽然对于大多数问题,汇编确实会比高级语言使用更多的行,但有时它是最好的(最简洁,最少的行)映射到手头的问题。这些案例中的大多数都涉及到常见的嫌疑人,例如嵌入式系统中的驱动程序和 bit-banging。

【讨论】:

【参考方案12】:

如果您参与了所有 Y2K 修复工作,那么如果您了解 Assembly,您可能会赚很多钱。里面还有很多遗留代码,这些代码偶尔需要维护。

【讨论】:

【参考方案13】:

另一个原因可能是可用的编译器对于架构来说不够好,并且程序中所需的代码量没有那么长或复杂,以至于程序员会迷失在其中。尝试为嵌入式系统编写微控制器,通常组装会容易得多。

【讨论】:

【参考方案14】:

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

其他人喜欢非常小的可执行文件,在 20-60KB 的范围内,例如检查 HiEditor,它是由 HiEdit 控件的作者实现的,用于 Windows 的超级强大的编辑控件,语法高亮和标签仅在 ~50kb) .在我的收藏中,我有超过 20 个这样的黄金控件,从 Excell 像 ssheets 到 html 渲染。

【讨论】:

【参考方案15】:

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

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

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

但是,嘿,单纯的事实不应该阻碍真正的黑客为支持组装而进行的十字军东征。 ;)

【讨论】:

【参考方案16】:

如果您正在编写具有 128 字节 RAM 和 4K 程序存储器的低端 8 位微控制器,那么您在使用汇编方面没有太多选择。有时,尽管在使用功能更强大的微控制器时,您需要在准确的时间执行某些操作。汇编语言就派上用场了,因为您可以计算指令并测量代码使用的时钟周期。

【讨论】:

【参考方案17】:

游戏非常需要性能,尽管同时优化器非常好,但“大师级程序员”仍然能够通过在汇编中手动编码正确的部分来获得更多性能。

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

【讨论】:

【参考方案18】:

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

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

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

【讨论】:

【参考方案19】:

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

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

【讨论】:

【参考方案20】:

迄今为止,我见过的几乎所有中型到大型游戏引擎或库都有一些手动优化的汇编版本,可用于 4x4 矩阵连接等矩阵运算。在处理大型矩阵时,编译器似乎不可避免地会错过一些巧妙的优化(重用寄存器、以最有效的方式展开循环、利用特定于机器的指令等)。这些矩阵操作函数也几乎总是配置文件上的“热点”。

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

最后,如果你有中断服务例程,asm 可以改变世界——有些操作你只是不想在中断下发生,你希望你的中断处理程序“进出快速”...您几乎确切地知道如果它在 asm 中,ISR 中会发生什么,它鼓励您将血腥的事情保持简短(无论如何这是一个好习惯)。

【讨论】:

【参考方案21】:

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

    速度:需要尽量减少延迟。 成本:由于代码最少,运行它所需的硬件可能会稍微不那么强大。当批量生产数百万台时,这可以加起来。

【讨论】:

【参考方案22】:

我继续做的唯一汇编程序编码是用于资源匮乏的嵌入式硬件。正如leader 所提到的,汇编仍然非常适合ISRs,其中代码需要快速且易于理解。

对我来说,第二个原因是保持我对汇编功能的了解。能够检查和理解 CPU 执行我的指令所采取的步骤感觉很好。

【讨论】:

【参考方案23】:

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

下次可能也是同样的原因。

当然,我以前还有其他reasons。

【讨论】:

【参考方案24】:

很多人喜欢诋毁汇编语言,因为他们从未学会用它编写代码,只是模糊地遇到过它,这让他们感到震惊或有些害怕。真正有才华的程序员会明白,抨击 C 或汇编是毫无意义的,因为它们是互补的。事实上,一个的优点就是另一个的缺点。 C 的有组织的语法规则提高了清晰度,但同时放弃了所有强大的集合,因为没有任何结构规则! C 代码指令用于创建非阻塞代码,这可能会强制明确编程意图,但这是一种功耗。在 C 中,编译器不允许在 if/elseif/else/end 内跳转。或者您不允许在相互重叠的不同变量上编写两个 for/end 循环,您不能编写自修改代码(或不能以无缝简单的方式编写)等等。传统程序员被上述内容吓坏了,并且会甚至不知道如何使用这些方法的力量,因为它们已经被提出来遵循传统规则。 这是事实:今天我们的机器具有计算能力,可以做比我们使用它们的应用程序更多的事情,但是人脑无法在无规则的编码环境(=汇编)中对它们进行编码,并且需要限制性规则,这极大地减少频谱并简化编码。 由于上述限制,我自己编写的代码无法用 C 代码编写而不会变得非常低效。而且我还没有谈到速度,大多数人认为这是用汇编编写的主要原因,如果你的思想仅限于用 C 语言思考,那么你永远是编译器的奴隶。我一直认为国际象棋大师是理想的汇编程序员,而 C 程序员只是玩“Dames”。

【讨论】:

自修改代码对大多数现代 CPU 的性能没有用处,在 JIT-once/run-many 场景之外。但是将常量填充为立即数是一种有趣的可能性。不过,C goto 确实允许在函数内进行非结构化跳转。包含到 if() 内的块中或在同一函数中循环。例如godbolt.org/z/IINHTg。另请参阅 Duff 的设备,使用 switch/case 进入dowhile() 循环来表示跳转到展开的循环。但在某些时候,如果你陷入那种混乱程度,用 asm 写会变得更清楚。 (当然,Duff 的设备仅在具有后增量寻址的机器上有用,否则展开循环内的那些入口点只会破坏大部分优化目的。)【参考方案25】:

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

【讨论】:

【参考方案26】:

如果我能够超越 GCC 和 Visual C++ 2008(也称为 Visual C++ 9.0),那么人们就会有兴趣采访我,了解它是如何实现的。

这就是为什么目前我只是在汇编中阅读内容并在需要时仅编写 __asm int 3。

希望对你有所帮助……

【讨论】:

【参考方案27】:

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

事物的挑战!我经历了几个月的岁月 以前我在x86 assembly 中写所有东西(DOS 和 Windows 的日子 3.1)。它基本上教会了我一大堆低级操作,硬件I/O等。 在某些情况下,它的大小保持较小(再次在 DOS 和 Windows 3.1 编写 TSRs 时)

我一直在关注编码组装,这只不过是这件事的挑战和乐趣。我没有其他理由这样做:-)

【讨论】:

【参考方案28】:

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

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

我认为要记住的重要一点是,在许多情况下,使用小型例程来提高速度的机会比使用大型例程要大得多,特别是如果手写汇编程序可以将所有内容都放入寄存器中,但编译器除外不会很管理。如果循环足够大以至于它无论如何都无法将所有内容都保存在寄存器中,那么改进的机会就会少得多。

【讨论】:

【参考方案29】:

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

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

【讨论】:

【参考方案30】:

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

【讨论】:

以上是关于为啥要在汇编中编程? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

为啥这么多汇编语法都包含逗号? [关闭]

为啥要在 Python 中隐式检查是不是为空? [关闭]

为啥我们需要进出组装说明? [关闭]

为啥头文件在其他编程语言中没有流行起来? [关闭]

为啥我应该在 PHP 中使用模板系统? [关闭]

哪些编程语言不被视为高级语言? [关闭]