48

我记得在某处读过,为了真正优化和加速代码的某些部分,程序员用汇编语言编写该部分。我的问题是——

  1. 这种做法还在做吗?以及如何做到这一点?
  2. 用汇编语言编写是不是有点太麻烦和过时了?
  3. 当我们编译 C 代码(带或不带 -O3 标志)时,编译器会进行一些代码优化并链接所有库并将代码转换为二进制目标文件。因此,当我们运行程序时,它已经是最基本的形式,即二进制。那么引入“汇编语言”有什么帮助呢?

我正在尝试理解这个概念,非常感谢任何帮助或链接。

更新:根据 dbemerlin 的要求改写第 3 点 - 因为您可能能够编写比编译器生成的更有效的汇编代码,但除非您是汇编专家,否则您的代码可能会运行得更慢,因为编译器通常会比大多数人更好地优化代码.

4

14 回答 14

30

恢复到汇编语言唯一有用的时候是

...和...

  • 使用这些 CPU 指令将为瓶颈代码提供一些显着且有用的性能提升。

简单地使用内联汇编来执行可以很容易地用 C++ 表达的操作——比如添加两个值或在字符串中搜索——会适得其反,因为:

  • 编译器知道如何同样好地做到这一点
    • 要验证这一点,请查看其汇编输出(例如gcc -S)或反汇编机器代码
  • 您人为地限制了它在寄存器分配、CPU 指令等方面的选择,因此为 CPU 寄存器准备执行硬编码指令所需的值可能需要更长的时间,然后才能为未来的指令恢复最佳分配
    • 编译器优化器可以在指定不同寄存器的等效性能指令之间进行选择,以最大程度地减少它们之间的复制,并且可以选择寄存器以使单个内核可以在一个周期内处理多条指令,而强制通过特定寄存器将所有指令序列化
      • 公平地说,GCC 有办法表达对特定类型寄存器的需求,而无需将 CPU 限制为精确的寄存器,仍然允许进行此类优化,但它是我见过的唯一解决此问题的内联程序集
  • 如果明年推出的新 CPU 型号带有另一条指令,该指令对于相同的逻辑操作要快 1000%,那么编译器供应商更有可能更新他们的编译器以使用该指令,因此您的程序在重新编译后会受益,而不是您(或当时维护软件的人)
  • 编译器将为它所告知的目标架构选择一种最佳方法:如果您对一个解决方案进行硬编码,那么它将需要是您平台的最低公分母或#ifdef-ed
  • 汇编语言不像 C++ 那样可移植,跨 CPU 和跨编译器,即使你看似移植了一条指令,也有可能犯错误重新注册对破坏安全的寄存器、参数传递约定等。
  • 其他程序员可能不知道或不熟悉汇编

我认为值得牢记的一个观点是,当 C 被引入时,它必须赢得许多对生成的机器代码大惊小怪的铁杆汇编语言程序员。那时机器的 CPU 能力和 RAM 更少,你可以打赌人们会为最微小的事情大惊小怪。优化器变得非常复杂并不断改进,而像 x86 这样的处理器的汇编语言变得越来越复杂,它们的执行管道、缓存和其他与性能有关的因素也变得越来越复杂。您不能再从每条指令的周期表中添加值。编译器编写者花时间考虑所有这些微妙的因素(尤其是那些为 CPU 制造商工作的因素,但这也增加了其他编译​​器的压力)。它' 现在,对于汇编程序员来说,在任何非平凡的应用程序上平均代码效率比一个好的优化编译器生成的代码效率要高得多,而且他们极有可能做得更糟。因此,组件的使用应仅限于真正产生可衡量和有用的差异的时间,值得耦合和维护成本。

于 2010-11-17T09:22:40.250 回答
14

首先,您需要对您的程序进行概要分析。然后优化 C 或 C++ 代码中最常用的路径。除非优势很明显,否则不要在 assembler 中重写。使用汇编器会使您的代码更难维护且可移植性更低——除非在极少数情况下,否则不值得这样做。

于 2010-11-17T08:37:29.620 回答
10

(1) 是的,最简单的尝试方法是使用内联汇编,这取决于编译器,但通常看起来像这样:

__asm
{
    mov eax, ebx
}

(2) 这是非常主观的

(3) 因为您可能能够编写比编译器生成的更有效的汇编代码。

于 2010-11-17T08:42:34.480 回答
6

您应该阅读Michael Abrash的经典著作Zen of Code Optimization和后续著作。Zen of Graphics Programming

在第一本书中,他简要地解释了如何将汇编编程推向极限。在后续的文章中,他解释说程序员应该使用一些更高级的语言,比如 C 语言,并且只尝试使用汇编来优化非常具体的地方,如果有必要的话。

这种改变想法的一个动机是,他看到与从高级语言编译的代码相比(可能是使用新指令的编译器),在同一处理器系列的下一代中,针对一代处理器高度优化的程序可能会(有点)慢例如,或现有处理器的性能和行为从一代处理器更改为另一代)。

另一个原因是编译器非常好并且现在积极优化,通常有更多的性能来获得将 C 代码转换为汇编的算法的工作。即使对于 GPU(图形卡处理器)编程,您也可以使用 cuda 或 OpenCL 使用 C 来完成。

当您应该/必须使用汇编时,仍然存在一些(罕见的)情况,通常是为了对硬件进行非常精细的控制。但即使在操作系统内核代码中,它通常也是非常小的部分,而且代码不多。

于 2010-11-17T09:04:51.207 回答
4

如今,使用汇编语言的理由很少,即使是像 SSE 和较旧的 MMX 这样的低级结构在 gcc 和 MSVC 中都有内置的内在函数(我敢打赌 icc 也是,但我从未使用过它)。

老实说,如今的优化器非常激进,以至于大多数人在汇编代码中编写代码的性能甚至无法达到一半。您可以更改数据在内存中的排序方式(对于局部性)或告诉编译器更多关于您的代码的信息(通过#pragma),但实际上编写汇编代码......怀疑您会从中获得任何额外的东西。

@VJo,请注意,在高级 C 代码中使用内在函数可以让您进行相同的优化,而无需使用单个汇编指令。

对于它的价值,已经讨论了下一个 Microsoft C++ 编译器,以及他们将如何从中删除内联汇编。这充分说明了对它的需求。

于 2010-11-17T08:45:39.533 回答
4

我认为您没有指定处理器。不同的答案取决于处理器和环境。一般的答案是肯定的,它仍然完成,它肯定不是过时的。一般的原因是编译器,有时它们在一般优化方面做得很好,但对于特定目标却不是很好。有些人在一个目标上非常擅长,而在其他目标上却不太擅长。大多数时候它已经足够好了,大多数时候你想要可移植的 C 代码而不是不可移植的汇编程序。但是您仍然会发现 C 库仍然会手动优化 memcpy 和其他编译器根本无法弄清楚有一种非常快速的方法来实现它的例程。部分原因是这种极端情况不值得花时间让编译器优化,只需在汇编程序中解决它,如果该目标使用 C 如果该目标使用 asm,则构建系统有很多如果该目标使用 C,如果该目标使用 asm。所以它仍然会发生,我认为必须在某些领域永远持续下去。

X86 是自己的野兽,有很多历史,我们正处于这样一个阶段,你真的不能以实际的方式编写一个总是更快的汇编程序,你绝对可以优化特定机器上特定处理器上的例程一天,然后执行编译器。除了某些特定情况外,它通常是徒劳的。有教育意义,但总的来说不值得花时间。还要注意处理器不再是瓶颈,所以一个草率的通用 C 编译器就足够了,在别处找到性能。

其他平台,通常意味着嵌入式、arm、mips、avr、msp430、pic 等。您可能正在运行操作系统,也可能不运行操作系统,您可能运行也可能不运行缓存或桌面具有的其他类似的东西。所以编译器的弱点就会显现出来。另请注意,编程语言继续远离处理器而不是向它们发展。即使在 C 被认为是低级语言的情况下,它也不匹配指令集。总会有一些时候,您可以生成优于编译器的汇编程序段。不一定是您的瓶颈部分,但在整个程序中,您通常可以在这里和那里进行改进。您仍然必须检查这样做的价值。在嵌入式环境中,它可以而且确实决定了产品的成败。

真正的嵌入式是一个拥有专业工程师的专业市场。另一个嵌入式市场,你的嵌入式 linux roku、tivo 等。嵌入式手机等都需要有便携式操作系统才能生存,因为你需要第三方开发人员。所以这个平台必须更像一个桌面而不是一个嵌入式系统。埋在提到的 C 库或操作系统中可能会有一些汇编程序优化,但是对于桌面,您想尝试投入更多硬件,以便软件可以移植而不是手动优化。如果第三方成功需要汇编程序,您的产品线或嵌入式操作系统将失败。

我最担心的是这些知识正在以惊人的速度丢失。因为没有人检查汇编程序,因为没有人用汇编程序编写代码,等等。没有人注意到编译器在生成代码时没有得到改进。开发人员通常认为他们必须购买更多硬件,而不是意识到通过了解编译器或如何更好地编程,他们可以使用相同的编译器将性能提高 5% 到数百%,有时使用相同的源代码。5-10% 通常使用相同的源代码和编译器。gcc 4 并不总是产生比 gcc 3 更好的代码,我保留两者,因为有时 gcc3 做得更好。目标特定的编译器可以(并不总是)围绕 gcc 运行,有时使用相同的源代码不同的编译器可以看到百分之几的改进。这一切从何而来?仍然费心寻找和/或使用汇编程序的人。其中一些人在编译器后端工作。前端和中间当然是有趣和有教育意义的,但后端是你决定或破坏最终程序的质量和性能的地方。即使您从不编写汇编程序,而只是不时查看编译器的输出(gcc -O2 -s myprog.c),它也会使您成为更好的高级程序员并保留其中的一些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。这一切从何而来?仍然费心寻找和/或使用汇编程序的人。其中一些人在编译器后端工作。前端和中间当然是有趣和有教育意义的,但后端是你决定或破坏最终程序的质量和性能的地方。即使您从不编写汇编程序,而只是不时查看编译器的输出(gcc -O2 -s myprog.c),它也会使您成为更好的高级程序员并保留其中的一些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。这一切从何而来?仍然费心寻找和/或使用汇编程序的人。其中一些人在编译器后端工作。前端和中间当然是有趣和有教育意义的,但后端是你决定或破坏最终程序的质量和性能的地方。即使您从不编写汇编程序,而只是不时查看编译器的输出(gcc -O2 -s myprog.c),它也会使您成为更好的高级程序员并保留其中的一些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。前端和中间当然是有趣和有教育意义的,但后端是你决定或破坏最终程序的质量和性能的地方。即使您从不编写汇编程序,而只是不时查看编译器的输出(gcc -O2 -s myprog.c),它也会使您成为更好的高级程序员并保留其中的一些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。前端和中间当然是有趣和有教育意义的,但后端是你决定或破坏最终程序的质量和性能的地方。即使您从不编写汇编程序,而只是不时查看编译器的输出(gcc -O2 -s myprog.c),它也会使您成为更好的高级程序员并保留其中的一些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。c) 它会让你成为一个更好的高级程序员,并且会保留一些这些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。c) 它会让你成为一个更好的高级程序员,并且会保留一些这些知识。如果没有人愿意知道和编写汇编程序,那么根据定义,我们已经放弃编写和维护用于高级语言和软件的编译器一般将不复存在。

以 gcc 为例,编译器的输出是汇编,它被传递给汇编器,汇编器将其转换为目标代码。C 编译器通常不会生成二进制文件。对象组合到最终二进制文件中时,由链接器完成,链接器是另一个由编译器调用的程序,而不是编译器的一部分。编译器将 C 或 C++ 或 ADA 或其他任何东西转换为汇编器,然后汇编器和链接器工具将其完成剩下的工作。动态重新编译器,例如 tcc,必须能够以某种方式动态生成二进制文件,但我认为这是例外而不是规则。LLVM 有自己的运行时解决方案,如果您将其用作交叉编译器,则可以非常明显地显示内部代码到目标代码到二进制路径的高级别的。

所以回到正题,是的,它已经完成了,比你想象的更频繁。主要与不直接与指令集比较的语言有关,然后编译器并不总是产生足够快的代码。如果您可以说在 malloc 或 memcpy 等大量使用的功能上得到数十倍的改进。或者想在没有硬件支持的情况下在手机上拥有高清视频播放器,平衡汇编器的优缺点。真正的嵌入式市场仍然相当多地使用汇编程序,有时全是 C,但有时软件完全用汇编程序编码。对于桌面 x86,处理器不是瓶颈。处理器是微编码的。即使您在表面上制作漂亮的汇编程序,它也不会在所有系列的 x86 处理器上运行得非常快,草率的、足够好的代码更有可能全面运行大致相同的代码。

我强烈建议学习用于非 x86 ISA 的汇编程序,例如 arm、thumb/thumb2、mips、msp430、avr。具有编译器的目标,尤其是具有 gcc 或 llvm 编译器支持的目标。学习汇编程序,学习理解 C 编译器的输出,并通过实际修改输出和测试来证明你可以做得更好。这些知识将有助于使您的桌面高级代码在没有汇编程序的情况下变得更好、更快、更可靠。

于 2010-11-17T20:06:46.757 回答
3

这取决于。在某些情况下(仍然)正在这样做,但在大多数情况下,这是不值得的。现代 CPU 异常复杂,为它们编写高效的汇编代码同样复杂。所以大多数时候,你手工编写的程序集最终会比编译器为你生成的程序要慢。

假设在过去几年中发布了一个不错的编译器,您通常可以调整您的 C/C++ 代码以获得与使用汇编相同的性能优势。

这里的评论和答案中的很多人都在谈论他们在汇编中重写某些东西所获得的“N 倍加速”,但这本身并没有太多意义。通过在 C 中重写评估流体动力学方程的C 函数,我得到了 13 倍的加速,通过应用许多与在汇编中编写它时相同的优化,通过了解硬件和分析。最后,它已经足够接近 CPU 的理论峰值性能,因此在汇编中重写它是没有意义的。通常,限制因素不是语言,而是您编写的实际代码。只要您不使用编译器难以处理的“特殊”指令,它'

组装并不神奇地更快。它只是将编译器带出循环。这通常是一件坏事,除非你真的知道自己在做什么,因为编译器会执行很多手动操作真的很痛苦的优化。但在极少数情况下,编译器只是不理解您的代码,无法为它生成有效的汇编,然后,自己编写一些程序集可能很有​​用。除了驱动程序开发等(您需要直接操作硬件)之外,我能想到的唯一值得编写程序集的地方是,如果您遇到无法从中生成高效 SSE 代码的编译器内在函数(例如 MSVC)。即使在那里,我仍然会开始在 C++ 中使用内在函数,并对其进行分析并尝试尽可能地对其进行调整,但是由于编译器在这方面不是很擅长,因此重写该代码最终可能是值得的在装配中。

于 2010-11-17T12:21:39.860 回答
2

看看这里,这个家伙使用汇编代码将性能提高了 6 次。所以,答案是:它仍在完成,但编译器做得很好。

于 2010-11-17T08:38:49.467 回答
2
  1. “这修炼还做吗?” --> 在图像处理、信号处理、人工智能(例如高效矩阵乘法)等领域完成。我敢打赌,我的 macbook 触控板上的滚动手势处理也是部分汇编代码,因为它是即时的。--> 它甚至在 C# 应用程序中完成(参见https://blogs.msdn.microsoft.com/winsdk/2015/02/09/c-and-fastcall-how-to-make-them-work-together-没有-ccli-shellcode/ )

  2. “用汇编语言编写是不是有点太麻烦和过时了?” --> 它是一种类似于锤子或螺丝刀的工具,有些任务需要钟表匠螺丝刀。

    1. “当我们编译 C 代码(带有或不带有 -O3 标志)时,编译器会进行一些代码优化……那么引入‘汇编语言’有什么帮助呢?” --> 我喜欢@jalf 所说的,以编写汇编的方式编写C 代码已经可以产生高效的代码。但是要做到这一点,您必须考虑如何用汇编语言编写代码,例如。了解复制数据的所有地方(每次不必要时都会感到痛苦)。使用汇编语言,您可以确定生成了哪些指令。即使您的 C 代码是高效的,也不能保证生成的程序集对于每个编译器都是高效的。(见https://lucasmeijer.com/posts/cpp_unity/) --> 使用汇编语言,当您分发二进制文件时,您可以测试 cpu 并根据针对 AVX 或仅针对 SSE 优化的 cpu 功能创建不同的分支,但您只需要分发一个二进制文件。使用内在函数,这在 C++ 或 .NET Core 3 中也是可能的。(请参阅https://devblogs.microsoft.com/dotnet/using-net-hardware-intrinsics-api-to-accelerate-machine-learning-scenarios/
于 2019-05-19T10:51:24.373 回答
1

在我的工作中,我使用嵌入式目标(微控制器)上的程序集进行低级访问。

但是对于一个PC软件来说,我觉得用处不大。

于 2010-11-17T08:38:33.203 回答
1

我有一个我已经完成的程序集优化示例,但它再次位于嵌入式目标上。你也可以看到一些用于 PC 的汇编编程示例,它创建了非常小而快速的程序,但通常不值得付出努力(寻找“用于 windows 的汇编”,你可以找到一些非常小而漂亮的程序)。

我的例子是当我写一个打印机控制器时,有一个函数应该每 50 微秒调用一次。它必须或多或少地重新洗牌。使用 C 我已经能够在大约 35 微秒内完成它,而通过汇编我已经在大约 8 微秒内完成了它。这是一个非常具体的程序,但仍然是真实和必要的。

于 2010-11-17T08:47:16.453 回答
1

在某些嵌入式设备(电话和 PDA)上,它很有用,因为编译器还不是很成熟,并且可以生成极慢甚至不正确的代码。我个人不得不解决或编写汇编代码来修复基于 ARM 的嵌入式平台的几种不同编译器的错误输出。

于 2010-11-17T16:50:07.520 回答
0
  1. 是的。使用内联汇编或链接汇编对象模块。您应该使用哪种方法取决于您需要编写多少汇编代码。通常可以对几行使用内联汇编,如果它有多个函数,则切换到单独的对象模块一次。
  2. 当然,但有时它是必要的。这里突出的例子是对操作系统进行编程。
  3. 今天的大多数编译器优化你用高级语言编写的代码比任何人编写的汇编代码都要好得多。人们大多使用它来编写无法用 C 等高级语言编写的代码。如果有人将它用于其他任何事情,则意味着他要么比现代编译器更擅长优化(我对此表示怀疑),要么就是愚蠢,例如他不知道要使用什么编译器标志或函数属性。
于 2010-11-17T09:03:57.493 回答
-1

用这个:

__asm__ __volatile__(/*assembly code goes here*/);

__asm__也可以是asm 。

__volatile__阻止编译器进行进一步优化。

于 2020-12-16T13:20:19.753 回答