首页 文章

GNU内联汇编优化

提问于
浏览
6

我正在尝试为高度优化的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);

在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

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

使用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

尽管gcc决定将我的变量保留在堆栈而不是寄存器中,但我不明白为什么在将它传递给第二个asm之前复制 test ?如果我把 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

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

使用的编译器:

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

icc版本12.0.2

1 回答

  • 4

    我在Linux上试过这样的例子(通过在 printf 中使用 &test 强制 test 的堆栈引用/ loc来使其成为"evil":):

    #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;
    }
    

    并使用各种版本的 gcc -O3 编译它...以下结果:

    code generated                                                     gcc version
    ================================================================================
      400630:       48 83 ec 18             sub    $0x18,%rsp          4.7.2,
      400634:       31 c0                   xor    %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          mov    %rax,0x8(%rsp)
      40064a:       48 89 c6                mov    %rax,%rsi
      40064d:       48 0f bd d0             bsr    %rax,%rdx
      400651:       31 c0                   xor    %eax,%eax
      400653:       e8 68 fe ff ff          callq  4004c0 
    [ ... ]
    ---------------------------------------------------------------------------------
      4004f0:       48 83 ec 18             sub    $0x18,%rsp          4.1
      4004f4:       31 c0                   xor    %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          mov    %rax,0x10(%rsp)
      400513:       48 89 c6                mov    %rax,%rsi
      400516:       48 0f bd d0             bsr    %rax,%rdx
      40051a:       31 c0                   xor    %eax,%eax
      40051c:       e8 c7 fe ff ff          callq  4003e8 
    [ ... ]
    ---------------------------------------------------------------------------------
      400500:       48 83 ec 08             sub    $0x8,%rsp           3.4.5
      400504:       bf 30 06 40 00          mov    $0x400630,%edi
      400509:       31 c0                   xor    %eax,%eax
      40050b:       48 c7 04 24 00 00 00 00         movq   $0x0,(%rsp)
      400513:       48 89 e1                mov    %rsp,%rcx
      400516:       48 0f ba 2c 24 27       btsq   $0x27,(%rsp)
      40051c:       48 8b 34 24             mov    (%rsp),%rsi
      400520:       48 0f bd 14 24          bsr    (%rsp),%rdx
      400525:       e8 fe fe ff ff          callq  400428 
    [ ... ]
    ---------------------------------------------------------------------------------
      4004e0:       48 83 ec 08             sub    $0x8,%rsp           3.2.3
      4004e4:       bf 10 06 40 00          mov    $0x400610,%edi
      4004e9:       31 c0                   xor    %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             mov    (%rsp),%rsi
      4004fd:       48 89 e1                mov    %rsp,%rcx
      400500:       48 0f bd 14 24          bsr    (%rsp),%rdx
      400505:       e8 ee fe ff ff          callq  4003f8 
    [ ... ]
    

    虽然创建的代码存在显着差异(包括 bsr 加入 test 作为寄存器或内存),但是没有一个经过测试的版本重新创建了您怀疑MacOSX上使用的4.2.x版本中的错误的程序集,但是那么我既没有你的测试用例也没有特定的编译器版本 .

    Edit: 上面的代码在强制 test 进入堆栈的意义上明显不同;如果没有这样做,那么我测试过的所有"plain" gcc版本都会直接对 bts $39, %rsi / bsr %rsi, %rdx .

    但我发现 clang 在那里创建了不同的代码:

    140:   50                      push   %rax
     141:   48 c7 04 24 00 00 00 00         movq   $0x0,(%rsp)
     149:   31 f6                   xor    %esi,%esi
     14b:   48 0f ba ee 27          bts    $0x27,%rsi
     150:   48 89 34 24             mov    %rsi,(%rsp)
     154:   48 0f bd d6             bsr    %rsi,%rdx
     158:   bf 00 00 00 00          mov    $0x0,%edi
     15d:   30 c0                   xor    %al,%al
     15f:   e8 00 00 00 00          callq  printf@plt>
    

    所以差别似乎确实在clang / llvm和"gcc proper"的代码生成器之间 .

相关问题