GNU内联汇编优化

Ser*_* L. 6 optimization gcc x86-64 inline-assembly icc

我正在尝试为高度优化的x86-64位操作代码编写一个小型库,并且正在摆弄内联asm.

在测试这个特殊情况时引起了我的注意:

unsigned long test = 0;
unsigned long bsr;

// bit test and set 39th bit
__asm__ ("btsq\t%1, %0 " : "+rm" (test) : "rJ" (39) );

// bit scan reverse (get most significant bit id)
__asm__ ("bsrq\t%1, %0" : "=r" (bsr) : "rm" (test) );

printf("test = %lu, bsr = %d\n", test, bsr);
Run Code Online (Sandbox Code Playgroud)

在gcc和icc中编译并运行良好,但是当我检查程序集时,我会得到差异

gcc -S -fverbose-asm -std=gnu99 -O3

movq    $0, -8(%rbp)
## InlineAsm Start
btsq    $39, -8(%rbp) 
## InlineAsm End
movq    -8(%rbp), %rax
movq    %rax, -16(%rbp)
## InlineAsm Start
bsrq    -16(%rbp), %rdx
## InlineAsm End
movq    -8(%rbp), %rsi
leaq    L_.str(%rip), %rdi
xorb    %al, %al
callq   _printf
Run Code Online (Sandbox Code Playgroud)

我想知道为什么这么复杂?我正在编写高性能代码,其中指令的数量至关重要.我特别想知道为什么gcc test在将它传递给第二个内联asm之前复制了我的变量?

使用icc编译的相同代码可以获得更好的结果:

    xorl      %esi, %esi                                    # test = 0
    movl      $.L_2__STRING.0, %edi                         # has something to do with printf
    orl       $32832, (%rsp)                                # part of function initiation
    xorl      %eax, %eax                                    # has something to do with printf
    ldmxcsr   (%rsp)                                        # part of function initiation
    btsq      $39, %rsi                                     #106.0
    bsrq      %rsi, %rdx                                    #109.0
    call      printf                                        #111.2
Run Code Online (Sandbox Code Playgroud)

尽管gcc决定将我的变量保留在堆栈而不是寄存器中,但我不明白为什么test在将它传递给第二个asm之前复制一下?如果我test在第二个asm中输入输入/输出变量

__asm__ ("bsrq\t%1, %0" : "=r" (bsr) , "+rm" (test) );

然后那些线条消失了.

movq    $0, -8(%rbp)
## InlineAsm Start
btsq    $39, -8(%rbp) 
## InlineAsm End
## InlineAsm Start
bsrq    -8(%rbp), %rdx
## InlineAsm End
movq    -8(%rbp), %rsi
leaq    L_.str(%rip), %rdi
xorb    %al, %al
callq   _printf
Run Code Online (Sandbox Code Playgroud)

这个gcc搞砸了优化还是我错过了一些重要的编译器开关?我的生产系统确实有icc,但如果我决定在某个时候分发源代码,那么它也必须能够用gcc编译.

使用的编译器:

gcc版本4.2.1(基于Apple Inc. build 5658)(LLVM build 2336.1.00)

icc版本12.0.2

Fra*_*kH. 4

我在Linux上尝试过你的例子,如下所示(通过在:)中强制test使用堆栈引用/定位来使其变得“邪恶” :&testprintf

#include <stdio.h>
int main(int argc, char **argv)
{
    unsigned long test = 0;
    unsigned long bsr;
// bit test and set 39th bit
    asm ("btsq\t%1, %0 " : "+rm" (test) : "rJ" (39) );
// bit scan reverse (get most significant bit id)
    asm ("bsrq\t%1, %0" : "=r" (bsr) : "rm" (test) );
    printf("test = %lu, bsr = %d, &test = %p\n", test, bsr, &test);
    return 0;
}
Run Code Online (Sandbox Code Playgroud) 并使用各种版本的...进行编译,gcc -O3得到以下结果:

代码生成的gcc版本
=================================================== ================================
  400630: 48 83 ec 18 子 $0x18,%rsp 4.7.2,
  400634: 31 c0 异或 %eax,%eax 4.6.2,
  400636:bf 50 07 40 00 mov $0x400750,%edi 4.4.6
  40063b: 48 8d 4c 24 08 lea 0x8(%rsp),%rcx
  400640: 48 0f ba e8 27 bts $0x27,%rax
  400645: 48 89 44 24 08 移动%rax,0x8(%rsp)
  40064a: 48 89 c6 mov %rax,%rsi
  40064d: 48 0f bd d0 bsr %rax,%rdx
  400651: 31 c0 异或 %eax,%eax
  400653:e8 68 fe ff ff callq 4004c0
[...]
-------------------------------------------------- -------------------------------------------
  4004f0: 48 83 ec 18 子 $0x18,%rsp 4.1
  4004f4: 31 c0 异或 %eax,%eax
  4004f6: bf 28 06 40 00 mov $0x400628,%edi
  4004fb: 48 8d 4c 24 10 lea 0x10(%rsp),%rcx
  400500: 48 c7 44 24 10 00 00 00 00 movq $0x0,0x10(%rsp)
  400509: 48 0f ba e8 27 bts $0x27,%rax
  40050e: 48 89 44 24 10 移动%rax,0x10(%rsp)
  400513: 48 89 c6 mov %rax,%rsi
  400516: 48 0f bd d0 bsr %rax,%rdx
  40051a: 31 c0 异或 %eax,%eax
  40051c:e8 c7 fe ff ff callq 4003e8
[...]
-------------------------------------------------- -------------------------------------------
  400500: 48 83 ec 08 子 $0x8,%rsp 3.4.5
  400504: bf 30 06 40 00 mov $0x400630,%edi
  400509: 31 c0 异或 %eax,%eax
  40050b: 48 c7 04 24 00 00 00 00 movq $0x0,(%rsp)
  400513: 48 89 e1 移动%rsp,%rcx
  400516: 48 0f ba 2c 24 27 btsq $0x27,(%rsp)
  40051c: 48 8b 34 24 移动 (%rsp),%rsi
  400520: 48 0f bd 1​​4 24 bsr (%rsp),%rdx
  400525:e8 fe fe ff ff callq 400428
[...]
-------------------------------------------------- -------------------------------------------
  4004e0: 48 83 ec 08 子 $0x8,%rsp 3.2.3
  4004e4: bf 10 06 40 00 mov $0x400610,%edi
  4004e9: 31 c0 异或 %eax,%eax
  4004eb: 48 c7 04 24 00 00 00 00 movq $0x0,(%rsp)
  4004f3: 48 0f ba 2c 24 27 btsq $0x27,(%rsp)
  4004f9: 48 8b 34 24 移动 (%rsp),%rsi
  4004fd: 48 89 e1 移动%rsp,%rcx
  400500: 48 0f bd 1​​4 24 bsr (%rsp),%rdx
  400505:e8 ee fe ff ff callq 4003f8
[...]

虽然创建的代码存在显着差异(包括是否作为寄存器或内存bsr进行访问test),但所有测试的版本都不会重新创建您所显示的程序集。我怀疑您在 MacOSX 上使用的 4.2.x 版本中存在错误,但我既没有您的测试用例,也没有可用的特定编译器版本。

编辑:test上面的代码在强制入栈的意义上明显不同;如果没有这样做,那么我测试过的所有“普通”gcc 版本都会直接配对bts $39, %rsi/ bsr %rsi, %rdx。

不过,我发现这clang会创建不同的代码:

140: 50 推 %rax
 141: 48 c7 04 24 00 00 00 00 movq $0x0,(%rsp)
 149: 31 f6 异或%esi,%esi
 14b:48 0f ba ee 27 bts $0x27,%rsi
 150: 48 89 34 24 移动 %rsi,(%rsp)
 154: 48 0f bd d6 bsr %rsi,%rdx
 158: bf 00 00 00 00 移动 $0x0,%edi
 15d: 30 c0 异或 %al,%al
 15f: e8 00 00 00 00 调用 printf@plt>
所以区别似乎确实在于 clang/llvm 和“gcc props”的代码生成器之间。