Как эффективно сочетать сравнения в SSE?
Я пытаюсь преобразовать следующий код в 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, который в противном случае был бы взят маской.
Ответы
Ответ 1
Если вы работаете с поплавками, вы обычно хотите использовать MOVMSKPS
(и соответствующую инструкцию AVX VMOVMSKPS
) вместо PMOVMSKB
.
В стороне, да, это один из стандартных способов сделать это; вы также можете использовать PTEST
(VPTEST
) для непосредственного обновления флагов условий на основе результата SSE или AVX AND или ANDNOT.
Ответ 2
Чтобы обратиться к редактируемой версии:
Если вы собираетесь напрямую подключиться к результату PTEST
, быстрее использовать его, чем MOVMSKPS
, в реестр GP, а затем сделать TEST
, чтобы установить флаги для ветки инструкция. На процессорах AMD перемещение данных между векторными и целыми доменами происходит очень медленно (от 5 до 10 циклов в зависимости от модели ЦП).
Если вам нужен дополнительный регистр для PTEST
, вы часто этого не делаете. Вы можете использовать то же значение, что и оба аргумента, например, с обычной не-векторной командой TEST
. (Тестирование foo & foo
совпадает с тестом foo
).
В вашем случае вам нужно проверить, что все векторные элементы установлены. Если вы измените сравнение, а затем ИЛИ результат вместе (так что вы тестируете !(x1 < a1[i]) || !(x2 < a2[i]) || ...
), у вас будут векторы, необходимые для тестирования для всех нулей, а не для всех. Но проблема с низким элементом все еще проблематична. Если вам нужно сохранить регистр, чтобы избежать необходимости использовать векторную маску для PTEST
/VTESTPS
, вы могли бы сдвинуть вектор на 4 байта перед тем, как сделать PTEST
, и разветвление на нем будет все-ноль.
AVX представил VTESTPS
, который, как я полагаю, позволяет избежать возможной задержки байпаса float → int. Если вы использовали какие-либо инструкции внутри домена для генерации входных данных для теста, вы могли бы также использовать (V)PTEST
. (Я знаю, что вы используете внутренности, но им больно печатать и смотреть по сравнению с мнемониками.)