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

なぜそんなに複雑なのだろうか?命令数が重要な高性能コードを書いています。test2 番目のインライン asm に渡す前に、gcc が変数のコピーを作成するのはなぜですか?

同じコードを 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 がレジスタではなくスタックに変数を保持することを決定したという事実にもかかわらず、私が理解できないのはtest、2 番目の asm に渡す前にコピーを作成する理由です。test2番目の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. ビルド 5658 に基づく) (LLVM ビルド 2336.1.00)

ICC バージョン 12.0.2

4

1 に答える 1

4

私はこのようにLinuxであなたの例を試しました( :)でtest使用&testするためにスタックref/locを強制することで「悪」にします:printf

#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、次の結果が得られます。

コード生成された gcc バージョン
================================================== ==============================
  400630: 48 83 ec 18 サブ $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 ビット $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 サブ $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 ビット $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 サブ $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 移動 (%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 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 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 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」の違いは確かにあるようです。

于 2012-09-26T20:02:05.107 に答える