Как сделать msvc векторизации с плавающей точкой? - PullRequest
1 голос
/ 09 октября 2019

У меня есть этот код:

constexpr size_t S = 4;
void add(std::array<float, S>& a, std::array<float, S> b)
{
    for (size_t i = 0; i < S; ++i)
        a[i] += b[i];
}

Оба, clang и gcc, понимают, что вместо выполнения 4-х отдельных дополнений они могут сделать одно упакованное дополнение, используя инструкцию addps. Например, clang генерирует это:

movups  xmm2, xmmword ptr [rdi]
movlhps xmm0, xmm1              # xmm0 = xmm0[0],xmm1[0]
addps   xmm0, xmm2
movups  xmmword ptr [rdi], xmm0
ret

Как вы можете видеть на godbolt , gcc немного отстает от clang, так как ему нужно больше ходов. Но это нормально. Моя проблема в msvc, которая намного хуже, как вы можете видеть:

mov     eax, DWORD PTR _a$[esp-4]
movups  xmm2, XMMWORD PTR _b$[esp-4]
movss   xmm1, DWORD PTR [eax+4]
movaps  xmm0, xmm2
addss   xmm0, DWORD PTR [eax]
movss   DWORD PTR [eax], xmm0
movaps  xmm0, xmm2
shufps  xmm0, xmm2, 85                          ; 00000055H
addss   xmm1, xmm0
movaps  xmm0, xmm2
shufps  xmm0, xmm2, 170                   ; 000000aaH
shufps  xmm2, xmm2, 255                   ; 000000ffH
movss   DWORD PTR [eax+4], xmm1
movss   xmm1, DWORD PTR [eax+8]
addss   xmm1, xmm0
movss   xmm0, DWORD PTR [eax+12]
addss   xmm0, xmm2
movss   DWORD PTR [eax+8], xmm1
movss   DWORD PTR [eax+12], xmm0
ret     0

Я пробовал разные уровни оптимизации, но /O2 кажется лучшим. Я также попытался вручную развернуть цикл, но без изменений для msvc.

Итак, есть ли способ заставить msvc выполнить такую ​​же оптимизацию, используя один addps вместо четырех addss? Или, может быть, есть веская причина, по которой msvc этого не делает?

Редактировать

Добавляя флаг /Qvec-report:2, как предложил Шон в комментариях (спасибо!) Я обнаружил, что msvc считает, что цикл слишком мал, чтобы извлечь какую-то выгоду из его векторизации. У Clang и GCC разные мнения, но все в порядке. И действительно, если я изменю S на 16, msvc создаст векторизованную версию, даже несмотря на то, что она все еще обеспечивает не векторизованную ветвь (на мой взгляд, совершенно ненужную, поскольку S известна во время компиляции). В целом, код msvc выглядит как беспорядок по сравнению с gcc и clang, см. здесь .

1 Ответ

2 голосов
/ 09 октября 2019

Я протестировал код, который вы разместили в Microsoft Visual Studio 2017, и он работает со мной. Когда я вызываю вашу функцию add с выровненными и не псевдонимами параметрами, ваша функция add компилируется в инструкцию addps, а не addss. Может быть, вы используете более старую версию Visual Studio?

Однако я смог воспроизвести вашу проблему, сознательно предоставив функции невыровненные или псевдонимы параметров. Для этого я заменил параметры функции указателями массива в стиле C (потому что я не знаю, как именно реализован std::array) и намеренно вызвал функцию с указателями-псевдонимами, сделав два массива перекрывающимися. В этом случае сгенерированный код вызывает addss четыре раза вместо addps один раз. Преднамеренная передача не выровненного указателя имела тот же эффект.

Такое поведение также имеет смысл. Чтобы векторизация имела смысл, компилятор должен быть уверен, что массивы не перекрываются и что они правильно выровнены. Я полагаю, что выравнивание является меньшей проблемой для AVX, чем для SSE.

Конечно, компилятор должен быть в состоянии определить, возможны ли проблемы с алиасами или выравниванием во время компиляции, а не во время выполнения. Поэтому, возможно, проблема в том, что вы вызываете функцию таким образом, что компилятор не может быть уверен во время компиляции, являются ли параметры псевдонимами и выровнены ли параметры. Компиляторы иногда не очень умны в определении этих вещей. Однако, как вы указали в разделе комментариев, так как вы передаете один параметр по значению, компилятор должен быть в состоянии определить, что опасности перекрытия нет. Поэтому я предполагаю, что это проблема выравнивания, так как компилятор не уверен во время компиляции, как выравнивается содержимое std:array. Поскольку я не могу воспроизвести вашу проблему, используя std::array, вы можете разместить свой код о том, как вы вызываете функцию.

Вы также можете принудительно применить векторизацию, явно вызвав соответствующий компилятор intrinsic _mm_add_ps для инструкции addps.

...