clang: есть ли способ указать инструкции низкого уровня, используемые в атомарных операциях c11? - PullRequest
0 голосов
/ 18 октября 2019

Могу ли я сказать clang «использовать инструкции xxx» или «не использовать инструкции yyy» для встроенных или атомарных операций C11 (предположим, есть альтернатива)?

[править] микробенчмарк, который может воспроизвести регрессию производительности: наивный рулок .

Все началось с недавнего обновления до clang 9.0. В моей программе наблюдается некоторое снижение производительности, поэтому я попытался проверить сгенерированные инструкции. Удивительно, но clang сильно изменил то, какие инструкции использовать для атомарных функций (c11 atomic_fetch_{add,sub} на x86_64).

С clang 8 все это генерирует lock addl и lock subl. Но clang 9 переключился на lock incl и lock decl для +1 и -1, и lock xadd для других чисел (по крайней мере, для сложения. Я не проверял, был ли xsub, или subl все еще там). Инструкции inc / xadd / add обычно имеют одинаковую скорость, но в некоторых сценариях я наблюдал лучшую эффективность с lock addl. В любом случае, я не собираюсь обсуждать, может ли один insn быть быстрее другого. Поскольку clang 8 использовал один из них, лучше было бы продолжать его использовать.

Я пытался использовать встроенную сборку, чтобы принудительно использовать lock addl, но сложно полностью использовать эффективность, поскольку встроенная сборка предотвращает некоторые оптимизации,Идеальное решение - попросить компилятор сделать это по-старому.

Я не публикую здесь никакого кода, потому что вопрос не в повышении производительности. Но я хотел бы поделиться кодом для всех, кто желает проверить проблему с производительностью.

[обновление] Я протестировал его на Broadwell и Skylake. Спад производительности виден только при определенных рабочих нагрузках. Я не уверен, действительно ли это связано с инструкциями или с тем, как clang8 / 9 выполняет оптимизацию.

1 Ответ

1 голос
/ 18 октября 2019

Вы можете сообщить о снижении производительности на баг-трекере clang, особенно если вы можете создать MCVE / [mcve], который медленнее с GCC9 по той же причине, что и ваш основной код.

Обычно вы не можете форсироватьварианты выбора инструкций компилятора, полностью не делая это самостоятельно с встроенным asm.

Но параметры настройки могут иметь значение , например, -march=native для настройки вашего оборудования. (-march=haswell или -march=native на имеет значение, например устанавливает -mtune=haswell, а также включает все имеющиеся у вас расширения ISA.)

например, gcc или clang иногда избегаютinc в целом, но используйте его при компиляции для конкретного процессора, у которого нет проблем с ним. (Не говоря о версии lock, просто inc reg).

Похоже, что так и есть: использование -march=skylake приводит к clang9.0 для использования lock inc вместо lock add [mem], 1, но не с clang8.0 на Godbolt. (С просто -O3, clang9.0 по-прежнему использует lock add / sub)

inc reg хорошо для современного x86 (за исключением Silvermont / KNL), но inc mem (без блокировки) стоит дополнительный урок на процессорах Intel: нет микросинтеза нагрузки + добавления мопов, только часть магазина (https://agner.org/optimize/ и https://uops.info/). ИДК, если оно также хуже с lock inc против lock add или если вы видите какой-то другой эффект. Инструкция INC против ADD 1: это имеет значение?

Согласно https://uops.info/, lock add m32, imm8 имеет идентичный счетчик мопов (8), задержка и пропускная способность до lock inc m32 на Skylake. В Haswell есть один бэк-энд моп, как разница без префикса блокировки. Но вряд ли это влияет на пропускную способность. Я не проверял другие uarches, и вы не сказали, какой у вас процессор.

Яне очень рекомендую -march=skylake -mtune=generic: это может решить эту проблему с генерацией кода, но может привести к худшим настройкам в остальной части вашего кода. За исключением того, что он даже не работает, я думаю, что Clang отличается от GCC тем, как он обрабатывает параметры арки и настройки. Я предполагаю, что вы могли бы полностью избежать параметров марша и оставить -mtune по умолчанию, и просто включить -mavx2 -mfma -mpopcnt -mbmi -mbmi2 -maes -mcx16 и любые другие соответствующие расширения ISA, которые есть у вашего процессора.


и lock xadd длядругие числа (по крайней мере, для сложения ...

Вы уверены, что все еще включили оптимизацию для нового компилятора?

Когда результат --*p или atomic_fetch_add(p, -2) не используетсяClang 9.0 по-прежнему использует lock dec или lock sub. Я могу заставить Clang использовать lock xadd, только если отключу оптимизацию, превращая окружающий код в общий мусор.

Или с включенной оптимизацией, возвращаярезультат. IDK, может быть, в более сложных функциях, clang9.0 изменил что-то, что означает, что он не находит те же оптимизации в вашем коде, и использует lock xadd, чтобы получить старое значение в регистр. Например, может быть, чтобы вернуть его вызывающемуигнорирует его, если решено не использовать его агрессивно.

lock xadd определенно медленнее, чем lock add или lock sub, но clang не использует его, если не обязан (или если вы отключилиle оптимизация).

вывод asm для clang8.0 -O3 -march=skylake против clang9.0 -O3 -march=skylake (не включая ret) ( Godbolt )

#include <stdatomic.h>

void incmem(int *p) { ++*p; }
    clang8:     addl   $1, (%rdi)       clang9:  incl    (%rdi)

void atomic_inc(_Atomic int *p) { ++*p; }
    clang8: lock addl  $1, (%rdi)       clang9: lock incl (%rdi)

void atomic_dec(_Atomic int *p) { --*p; }
    clang8: lock subl  $1, (%rdi)       clang9: lock decl (%rdi)

void atomic_dec2(_Atomic int *p) {
    atomic_fetch_add(p, -2);
}
    clang8: lock addl  $-2, (%rdi)      clang9: lock addl $-2, (%rdi)


// returns the result
int fetch_dec(_Atomic int *p) { return --*p; }
    clang8:                                    clang9:
        movl  $-1, %eax                            movl  $-1, %eax
        lock xaddl   %eax, (%rdi)                  lock  xaddl %eax, (%rdi)
        addl  $-1, %eax                            decl    %eax
        retq

С отключенной оптимизацией мы получаем, что clang 8 и 9 создают буквально идентичный код с -O0 -march=skylake:

# both clang8 and 9 with  -O0 -march=skylake
atomic_dec2:
        pushq   %rbp
        movq    %rsp, %rbp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    $-2, -12(%rbp)
        movl    -12(%rbp), %ecx
        lock    xaddl   %ecx, (%rax)      # even though result is unused
        movl    %ecx, -16(%rbp)
        popq    %rbp
        retq
...