Проверка того, что два регистра SSE не равны нулю, не уничтожая их
Я хочу проверить, не являются ли два регистра SSE ненулевыми, не уничтожив их.
Вот код, который у меня сейчас есть:
uint8_t *src; // Assume it is initialized and 16-byte aligned
__m128i xmm0, xmm1, xmm2;
xmm0 = _mm_load_si128((__m128i const*)&src[i]); // Need to preserve xmm0 & xmm1
xmm1 = _mm_load_si128((__m128i const*)&src[i+16]);
xmm2 = _mm_or_si128(xmm0, xmm1);
if (!_mm_testz_si128(xmm2, xmm2)) { // Test both are not zero
}
Это лучший способ (с использованием до SSE 4.2)?
2 ответа
Из этого вопроса я узнал кое-что полезное. Давайте сначала посмотрим на некоторый скалярный код
extern foo2(int x, int y);
void foo(int x, int y) {
if((x || y)!=0) foo2(x,y);
}
Скомпилируйте это так gcc -O3 -S -masm=intel test.c
и важная сборка
mov eax, edi ; edi = x, esi = y -> copy x into eax
or eax, esi ; eax = x | y and set zero flag in FLAGS if zero
jne .L4 ; jump not zero
Теперь давайте посмотрим на тестирование SIMD-регистров на ноль. В отличие от скалярного кода, здесь нет регистра SIMD FLAGS. Однако в SSE4.1 есть тестовые инструкции SIMD, которые могут установить нулевой флаг (и флаг переноса) в скалярном регистре FLAGS.
extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
__m128i z = _mm_or_si128(x,y);
if (!_mm_testz_si128(z,z)) foo2(x,y);
}
Компилировать с c99 -msse4.1 -O3 -masm=intel -S test_SSE.c
и важная сборка
movdqa xmm2, xmm0 ; xmm0 = x, xmm1 = y, copy x into xmm2
por xmm2, xmm1 ; xmm2 = x | y
ptest xmm2, xmm2 ; set zero flag if zero
jne .L4 ; jump not zero
Обратите внимание, что для этого требуется еще одна инструкция, поскольку упакованное побитовое ИЛИ не устанавливает нулевой флаг. Также обратите внимание, что и скалярная версия, и версия SIMD должны использовать дополнительный регистр (eax
в скалярном случае и xmm2
в случае SIMD). Поэтому, чтобы ответить на ваш вопрос, ваше текущее решение - лучшее, что вы можете сделать.
Однако, если у вас нет процессора с SSE4.1 или выше, вам придется использовать _mm_movemask_epi8
, Другой альтернативой, которая требует только SSE2, является использование _mm_movemask_epi8
extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
if (_mm_movemask_epi8(_mm_or_si128(x,y))) foo2(x,y);
}
Важная сборка
movdqa xmm2, xmm0
por xmm2, xmm1
pmovmskb eax, xmm2
test eax, eax
jne .L4
Обратите внимание, что для этого нужна еще одна инструкция, чем для SSE4.1. ptest
инструкция.
До сих пор я использую pmovmaskb
инструкция, потому что задержка лучше на процессорах до Sandy Bridge, чем с ptest
, Тем не менее, я понял это до Haswell. На Haswell латентность pmovmaskb
хуже, чем латентность ptest
, Они оба имеют одинаковую пропускную способность. Но в этом случае это не очень важно. Что важно (чего я раньше не понимал), так это pmovmaskb
не устанавливает регистр FLAGS и поэтому требует другой инструкции. Так что теперь я буду использовать ptest
в моем критическом цикле. Спасибо Вам за Ваш вопрос.
Редактировать: как предложено OP, есть способ сделать это без использования другого регистра SSE.
extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
if (_mm_movemask_epi8(x) | _mm_movemask_epi8(y)) foo2(x,y);
}
Соответствующая сборка от GCC:
pmovmskb eax, xmm0
pmovmskb edx, xmm1
or edx, eax
jne .L4
Вместо использования другого регистра xmm используются два скалярных регистра.
Обратите внимание, что меньшее количество инструкций не обязательно означает лучшую производительность. Какое из этих решений лучше? Вы должны проверить каждый из них, чтобы узнать.
Если вы используете C / C ++, вы не можете контролировать отдельные регистры ЦП. Если вы хотите полного контроля, вы должны использовать ассемблер.