Оптимизация встроенной сборки GNU

Я пытаюсь написать небольшую библиотеку для высокооптимизированного кода операции x86-64 bit и возился со встроенным 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 делает копию моей переменной 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

несмотря на то, что 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

Это gcc облажался с оптимизацией или мне не хватает некоторых важных переключателей компилятора? У меня есть icc для моей производственной системы, но если я решу распространить исходный код в какой-то момент, он также должен будет скомпилироваться с gcc.

используемые компиляторы:

gcc версии 4.2.1 (на основе сборки 5658 Apple Inc.) (сборка LLVM 2336.1.00)

icc Версия 12.0.2


person Sergey L.    schedule 26.09.2012    source источник
comment
Почему бы просто не использовать стандартные встроенные функции для этих операций? Таким образом, компилятор сделает большую часть работы за вас (и, вероятно, он тоже сделает свою работу лучше).   -  person Paul R    schedule 26.09.2012
comment
Ну, во-первых, это не единственные инструкции, которые я собираюсь использовать, это всего лишь примеры. gcc имеет встроенные функции для большинства из них, icc, к счастью, не наделен встроенными функциями, и у меня есть хорошее увеличение производительности на 20-30% от простой компиляции с помощью icc.   -  person Sergey L.    schedule 26.09.2012
comment
Хорошо, но если нет чего-то особенного, что нельзя сделать с помощью встроенных функций, я все же верю, что компилятор будет работать лучше, чем вы, если вы ему позволите.   -  person Paul R    schedule 26.09.2012
comment
Помимо того факта, что я не ищу встроенные функции компилятора (я знаю, где их найти), они выдают гораздо больше инструкций, чем я бы использовал, и разные результаты в разных компиляторах. __builtin_clzll(0) равно 64 при компиляции с помощью gcc и 63 в icc. У меня вопрос в этой теме о том, как оптимизировать встраивание встроенного asm.   -  person Sergey L.    schedule 26.09.2012
comment
Не могли бы вы предоставить полный пример исходного кода, который заставляет gcc генерировать код ассемблера, который вы показали? Я пробовал встроить ваш фрагмент в main() и скомпилировать его с различными версиями gcc, начиная с 3.2.3 и заканчивая 4.7.2, и ни одна из них не воспроизводит код, который помещает test в стек на уровне -O3 opt. Пожалуйста, дайте больше контекста.   -  person FrankH.    schedule 26.09.2012
comment
Это все тело моего main(). Это действительно похоже на проблему с комплектом разработчика Apple gcc, потому что gcc в нашем кластере Linux не имеет этой проблемы. См. Мой комментарий ниже.   -  person Sergey L.    schedule 27.09.2012


Ответы (1)


Я пробовал ваш пример в Linux следующим образом (сделав его «злым», заставив стек ref / loc для test с помощью &test в _3 _:): _ 4_ и скомпилировал его с различными версиями 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 как регистр или память), ни одна из протестированных версий не воссоздает сборку, которую вы показали. Я бы подозревал, что это ошибка в версии 4.2.x, которую вы использовали в MacOSX, но тогда у меня нет ни вашего тестового примера, ни этой конкретной версии компилятора.

Изменить: приведенный выше код, очевидно, отличается в том смысле, что он помещает test в стек; если это не сделано, то все "простые" версии gcc, которые я тестировал, используют прямую пару bts $39, %rsi / bsr %rsi, %rdx.

I have found, though, that clang creates different code there:

 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>
so the difference seems to be indeed between the code generators of clang/llvm and "gcc proper".

person FrankH.    schedule 26.09.2012
comment
Да, кажется, это странный комплект разработчика яблок gcc. Я также попытался скомпилировать его в нашем кластере с помощью gcc, и я получил код, очень похожий на то, что создает icc. Я считаю, что мне нужен переключатель оптимизатора -fforward-propagate, который даже не входит в комплект разработчика gcc для Apple. Я оставлю эту проблему, зная, что мой MacBook Pro не является производственной системой. - person Sergey L.; 27.09.2012