Проверка того, что два регистра 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 ++, вы не можете контролировать отдельные регистры ЦП. Если вы хотите полного контроля, вы должны использовать ассемблер.

Другие вопросы по тегам