何时将在C/C 源中优化汇编代码

When will compilers optimize assembly code in C/C++ source?

本文关键字:优化 汇编 代码 何时      更新时间:2023-10-16

大多数编译器都不优化内联汇编代码(VS2015,GCC),它允许我们编写新指令不支持。

但是C/C 编译器何时应实现内联装配优化?

永远不会。那将打败直列组装的目的,那就是要准确地获得您的要求。

如果要以编译器可以理解和优化的方式使用目标CPU指令集的全部功能,则应使用内在功能,而不是Inline ASM

例如。而不是用于popcnt的Inline ASM,使用int count = __builtin_popcount(x);(用-mpopcnt编译的GNU C中)。Inline-ASM也是特定于编译器的,因此,如果有任何固有的东西更便携,尤其是如果您使用Intel的X86 Intrinsics,这些X86 intersics在所有可以针对X86的主要编译器中都得到了支持。使用#include <x86intrin.h>,您可以使用int _popcnt32 (int a)可靠地获取popcnt X86指令。请参阅英特尔的内在查找器/指南,以及x86标签Wiki中的其他链接。


int count(){ 
  int total = 0;
  for(int i=0 ; i<4 ; ++i)
    total += popc(i);
  return total;
}

用gcc6.3编译的#define popc _popcnt32

    mov     eax, 4
    ret

clang 3.9在Godbolt编译器探险器上具有popc的内联定义:

    xor     eax, eax
    popcnt  eax, eax
    mov     ecx, 1
    popcnt  ecx, ecx
    add     ecx, eax
    mov     edx, 2
    popcnt  edx, edx
    add     edx, ecx
    mov     eax, 3
    popcnt  eax, eax
    add     eax, edx
    ret

这是内联ASM击败常数传播的一个经典示例,如果可以避免的话,为什么不应该将其用于性能:https://gcc.gnu.org/wiki/wiki/dontuseinlinlinlinlinlinlinlinelineasm。


这是我用于此测试的内联定义:

int popc_asm(int x) {
  // force use of the same register because popcnt has a false dependency on its output, on Intel hardware
  // this is just a toy example, though, and also demonstrates how non-optimal constraints can lead to worse code
  asm("popcnt %0,%0" : "+r"(x));
  return x;
}

如果您不知道popcnt对其在英特尔硬件上的输出寄存器有错误的依赖性,这就是您应尽可能将其留给编译器的另一个原因。


使用编译器不知道的特殊说明是Inline ASM的一个用例,但是如果编译器不知道它,则肯定无法优化它。在编译器擅长优化内在(例如,用于SIMD指令)之前,此类内容的内联ASM更为普遍。但是现在我们已经有很多年了,而且编译器通常具有内在的效果,即使对于诸如ARM等非X86架构。

通常,编译器不会优化内联汇编的内容。也就是说,它们不会在您的组件块中删除或更改指令。特别是,gcc只需通过未改变的内联装配的主体到基础组件(在这种情况下为gas)。

但是,好的编译器可能会在您的内联装配中优化,在某些情况下,甚至可以完全省略执行内联合代码!例如,如果海湾合作委员会确定组件的声明输出已死,则可以做到这一点。它还可以将一个组件块从循环中提升,或将多个呼叫组合到一个呼叫中。因此,它永远不会弄乱块内的说明,但是更改将执行块的次数的次数是完全合理的。当然,如果块具有其他重要的副作用,也可以禁用此行为。

扩展的ASM语法上的GCC文档有一些很好的例子。