在C / C ++中使用汇编语言

时间:2010-11-17 08:34:00

标签: c++ c optimization compiler-optimization assembly

我记得在哪里读过要真正优化的东西。加速代码的某些部分,程序员用汇编语言编写该部分。我的问题是 -

  1. 这种做法还在吗?如何做到这一点?
  2. 不是用汇编语言写的有点太麻烦了陈旧?
  3. 当我们编译C代码(带或不带-O3标志)时,编译器会进行一些代码优化&链接所有图书馆和将代码转换为二进制对象文件。因此,当我们运行程序时,它已经是最基本的形式,即二进制。那么如何引导'汇编语言'有所帮助呢?
  4. 我正在努力理解这个概念&非常感谢任何帮助或链接。

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

14 个答案:

答案 0 :(得分:27)

恢复汇编语言的唯一时间是

... AND ...

  • 使用这些CPU指令可以为瓶颈代码提供一些重要且有用的性能提升。

简单地使用内联汇编来执行一个可以用C ++轻松表达的操作 - 比如添加两个值或搜索字符串 - 会起到反作用,因为:

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

我认为值得记住的一个观点是,当C被引入时,它必须赢得许多硬核汇编语言程序员,他们对生成的机器代码感到困惑。机器当时拥有较少的CPU功率和RAM,你可以打赌人们对最微小的东西感到困惑。优化器变得非常复杂并且不断改进,而像x86这样的处理器的汇编语言变得越来越复杂,执行管道,缓存和其他因素也越来越复杂。您不能再添加每个指令周期表中的值。编译器编写者花时间考虑所有这些微妙因素(特别是那些为CPU制造商工作的因素,但这也增加了其他编译​​器的压力)。现在,对于汇编程序员来说,平均 - 在任何非平凡的应用程序上 - 显着提高代码效率比使用良好的优化编译器产生的效率更加不切实际,并且它们极可能会变得更糟。因此,组装的使用应限制在真正产生可衡量和有用的差异的时间,值得耦合和维护成本。

答案 1 :(得分:14)

首先,您需要对程序进行概要分析。然后,您可以在C或C ++代码中优化最常用的路径。 Unless advantages are clear you don't rewrite in assembler。使用汇编程序会使代码更难维护而且便携性更低 - 除非在极少数情况下,否则它是不值得的。

答案 2 :(得分:10)

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

__asm
{
    mov eax, ebx
}

(2)这是非常主观的

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

答案 3 :(得分:4)

现在使用汇编语言的原因很少,即使像SSE这样的低级构造和旧的MMX在gcc和MSVC中都有内置的内在函数(icc我打赌但我从未使用它)。

老实说,如今优化者非常咄咄逼人,以至于大多数人甚至无法匹配他们在集会中编写代码的一半表现。您可以更改数据在内存中的排序方式(用于本地)或告诉编译器更多关于您的代码(通过#pragma),但实际上编写汇编代码...怀疑您将从中获得额外的任何内容。

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

值得一提的是,有关下一代Microsoft C ++编译器的讨论,以及它们将如何从中删除内联汇编。这充分说明了它的必要性。

答案 4 :(得分:3)

您应该阅读经典书籍Zen of Code Optimization以及Michael Abrash后续Zen of Graphics Programming

总结在第一本书中,他解释了如何将装配编程推向极限。在后续文章中,他解释说程序员应该使用像C这样的更高级别的语言,并且只在必要时尝试使用汇编来优化一些非常具体的点。

这种改变的一个动机是他看到,与从高级语言编译的代码(maube编译器)相比,下一代同一处理器系列中针对一代处理器的高度优化程序可能会(稍微)变慢。例如,使用新指令。

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

还有一些(罕见的)应该使用汇编的情况,通常是为了对硬件进行非常精细的控制。但即使在OS内核代码中,它通常也是非常小的部分而且代码不多。

答案 5 :(得分:3)

答案 6 :(得分:2)

看看here,那个家伙使用汇编代码改进了6次性能。所以,答案是:它仍在完成,但编译器做得非常好。

答案 7 :(得分:2)

这取决于。在某些情况下它仍在(仍然)完成,但在大多数情况下,它是不值得的。现代CPU非常复杂,为它们编写高效的汇编代码同样复杂。因此,大多数情况下,手动编写的程序集最终会比编译器为您生成的程序集慢。

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

这里的评论和答案中很多人都在谈论他们在集会中重写了“N次加速”,但这本身并不意味着太多。通过重写C函数来评估C 中的流体动力学方程,通过应用许多相同的优化,如果你在汇编中编写它,通过了解硬件,我得到了13倍的加速,通过剖析。最后,它足够接近CPU的理论峰值性能,在组装中重写它会有无点。通常,这不是限制因素的语言,而是您编写的实际代码。只要你没有使用编译器有困难的“特殊”指令,就很难打败编写良好的C ++代码。

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

答案 8 :(得分:1)

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

但对于PC软件,我认为它不是很有用。

答案 9 :(得分:1)

我有一个我已经完成的装配优化的例子,但它又是一个嵌入式目标。你也可以看到一些用于PC的汇编编程的例子,它创建了非常小而快的程序,但通常不值得努力(查找“用于windows的程序集”,你可以找到一些非常小而漂亮的程序)。

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

答案 10 :(得分:1)

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

答案 11 :(得分:1)

  1. “这种做法仍在进行吗?” ->在图像处理,信号处理,AI(例如有效矩阵乘法)等中完成。我敢打赌,我的Macbook触控板上滚动手势的处理也是部分汇编代码,因为它是立即执行的。 ->甚至可以在C#应用程序中完成(请参见https://blogs.msdn.microsoft.com/winsdk/2015/02/09/c-and-fastcall-how-to-make-them-work-together-without-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/

答案 12 :(得分:0)

  1. 是。使用内联汇编或链接汇编对象模块。您应该使用哪种方法取决于您需要编写多少汇编代码。通常可以在几行中使用内联汇编,如果它是多个函数,则切换到单独的对象模块。
  2. 当然,但有时候是必要的。这里突出的例子是编程操作系统。
  3. 如今,大多数编译器都使用高级语言优化您编写的代码,这比任何编写汇编代码都要好得多。人们大多使用它来编写代码,否则无法用C语言等高级语言编写代码。如果有人将其用于其他任何事情意味着他要么优于现代编译器(我怀疑那样)或者只是简单的愚蠢,例如他不知道要使用什么编译器标志或函数属性。

答案 13 :(得分:-1)

使用此:

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

__asm__也可以是asm。

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