Почему Clang делает этот трюк с оптимизацией только начиная с Sandy Bridge? - PullRequest
0 голосов
/ 30 января 2019

Я заметил, что Clang делает интересный трюк по оптимизации деления для следующего фрагмента

int64_t s2(int64_t a, int64_t b)
{
    return a/b;
}

Ниже приведен вывод сборки, если указать march как Sandy Bridge или выше

        mov     rax, rdi
        mov     rcx, rdi
        or      rcx, rsi
        shr     rcx, 32
        je      .LBB1_1
        cqo
        idiv    rsi
        ret
.LBB1_1:
        xor     edx, edx
        div     esi
        ret

Вот ссылки Godbolt для подписанной версии и неподписанной версии

Насколько я понимаю, он проверяет, равны ли старшие биты двух операндов нулю, и выполняет ли32-разрядное деление, если это правда

Я проверил эту таблицу и вижу, что задержки для 32/64-разрядного деления на Core2 и Nehalem равны 40/116 и 26/89 соответственно.Следовательно, если операнды действительно часто не широки, то экономия за счет 32-битного деления вместо 64-битного может быть такой же полезной, как на SnB

Так почему же он включен только для SnB и более поздних версий?микроархитектуры?Почему другие компиляторы, такие как GCC или ICC, не делают этого?

1 Ответ

0 голосов
/ 30 января 2019

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

Звучит правильно, из-за прикольного ларька на семействе P6 и разных AMDДелители.


Использование флага в результате сдвига imm8 (не shift-by-implicit-1) в семействе P6 приводит к остановке внешнего интерфейса перед выдачей команды чтения флага, покасдвиг на пенсию .(Поскольку декодеры P6 не проверяют регистр imm8 = 0, чтобы оставить флаги неизмененными, в то время как SnB это делает). Инструкция INC против ADD 1: это имеет значение? .Возможно, именно поэтому clang не использует его для семейства P6.

Возможно, это другой способ проверки соответствующего состояния, которое не вызвало такой останов (например, test rcx,rcx до je, стоило бы того на Core2 / Nehalem). Но если бы разработчики clang не поняли причину, по которой это медленно на семействе P6, они бы не подумали, чтобы это исправить, и просто оставили это не выполненнымдля целей до SnB.(К сожалению, никто не добавил меня в обзор исправлений или список ошибок CC по этому поводу; это первый случай, когда clang проводит такую ​​оптимизацию. Хотя я думаю, что мог бы упомянуть о смещении флагов смещения в комментариях к другому обзору LLVM илиВ любом случае, было бы забавно попробовать добавить test и посмотреть, стоит ли это делать на Nehalem.)


Делители AMD имеют одинаковую производительность div в лучшем случае независимо от размера операнда,предположительно зависит только от фактической величины входов, согласно Agner Fog.Только наихудший случай растет с размером операнда. Так что я думаю безопасно запускать idiv r64 с небольшими входами, расширенными до 128/64-битных для AMD. (div / idiv на AMD - 2 моп для всех размеров операндов(кроме 8-битного, где он один, потому что он должен записывать только один выходной регистр: AH и AL = AX. В отличие от микрокодированного целочисленного деления Intel.)

Intel очень отличается: idiv r32 равен 9 мопам, тогда как idiv r64 составляет 59 мопов, при этом у Haswell пропускная способность в 3 раза хуже, чем у других членов семейства SnB.

Почему другиекомпиляторы, такие как GCC или ICC, делают это?

Возможно, потому что разработчики Clang подумали об этом, а gcc / icc их еще не скопировали. Если вы смотрели выступления Чандлера Каррута о perf, одинНапример, он использовал игру с веткой, чтобы пропустить div. Я предполагаю, что эта оптимизация была его идеей. Выглядит изящно.:)

...