Я пытаюсь преобразовать следующий код в SSE/AVX:
float x1, x2, x3;
float a1[], a2[], a3[], b1[], b2[], b3[];
for (i=0; i < N; i++)
{
if (x1 > a1[i] && x2 > a2[i] && x3 > a3[i] && x1 < b1[i] && x2 < b2[i] && x3 < b3[i])
{
// do something with i
}
}
Здесь N - малая константа, скажем 8. Оператор if (...) больше всего принимает значение false.
Первая попытка:
__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0
for (int i = 0; i < N; i++)
{
__m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
__m128 lt_mask = _mm_cmplt_ps(x, b[i]);
__m128 mask = _mm_and_ps(gt_mask, lt_mask);
if (_mm_movemask_epi8 (_mm_castps_si128(mask)) == 0xfff0)
{
// do something with i
}
}
Это работает и достаточно быстро. Вопрос в том, есть ли более эффективный способ сделать это? В частности, если есть регистр с результатами сравнений SSE или AVX при поплавках (которые помещают 0xffff
или 0x0000
в этот слот), как могут быть результаты всех сравнений (например) и-ed или или - вместе, в общем? Является ли PMOVMSKB
(или соответствующий _mm_movemask
intrinsic) стандартный способ сделать это?
Кроме того, как можно использовать 256-битные регистры AVX вместо SSE в коде выше?
EDIT:
Протестировано и сравнили версию с использованием VPTEST (от _mm_test * intrinsic), как предложено ниже.
__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0
__m128i ref_mask = _mm_set_epi32(0xffff, 0xffff, 0xffff, 0x0000);
for (int i = 0; i < N; i++)
{
__m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
__m128 lt_mask = _mm_cmplt_ps(x, b[i]);
__m128 mask = _mm_and_ps(gt_mask, lt_mask);
if (_mm_testc_si128(_mm_castps_si128(mask), ref_mask))
{
// do stuff with i
}
}
Это также работает и быстро. Бенчмаркинг (Intel i7-2630QM, Windows 7, cygwin 1.7, cygwin gcc 4.5.3 или mingw x86_64 gcc 4.5.3, N = 8) показывает, что это идентичная скорость для кода выше (менее 0,1%) на 64-битной, Любая версия внутреннего цикла работает примерно в 6,8 часах в среднем по данным, которые все находятся в кеше и для которых всегда возвращается значение false.
Интересно, что на 32-битной версии версия _mm_test работает на 10% медленнее. Оказывается, компилятор проливает маски после разворота цикла и должен перечитать их обратно; это, вероятно, не нужно, и его можно избежать в сборке с ручным кодированием.
Какой метод выбрать? Похоже, что нет веских причин предпочитать VPTEST
над VMOVMSKPS
. На самом деле, есть небольшая причина, чтобы предпочесть VMOVMSKPS
, а именно, освобождает регистр xmm, который в противном случае был бы взят маской.