Как эффективно комбинировать сравнения в 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. Если(...) оператор оценивает 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
в этом слоте), как могут результаты всех сравнений быть (например) и-ЕД или-ЕД вместе, в целом? Is 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.
интересно, что на 32bit версия _mm_test работает примерно на 10% медленнее. Оказывается, компилятор проливает маски после развертывания цикла и должен перечитывать их обратно; это, вероятно, не нужно и можно избежать в сборке с ручным кодом.
какой метод выбрать? Кажется, что нет никаких веских причин предпочесть VPTEST
над VMOVMSKPS
. На самом деле, есть небольшая причина предпочесть VMOVMSKPS
, а именно он освобождает регистр xmm, который в противном случае был бы занят маской.
2 ответов
если вы работаете с поплавками, вы обычно хотите использовать MOVMSKPS
(и соответствующая инструкция AVX VMOVMSKPS
) вместо PMOVMSKB
.
это в стороне, да, это один стандартный способ сделать это; вы также можете использовать PTEST
(VPTEST
) для непосредственного обновления флагов условий на основе результата SSE или AVX и или ANDNOT.
чтобы обратиться к вашей отредактированной версии:
если вы собираетесь напрямую ветвиться на результат PTEST
, это быстрее использовать его, чем MOVMSKPS
в GP reg, а затем сделайте TEST
на этом, чтобы установить флаги для инструкции ветви. На процессорах AMD перемещение данных между векторными и целочисленными доменами происходит очень медленно (задержка от 5 до 10 циклов, в зависимости от модели процессора).
что касается необходимости дополнительного регистра для PTEST
, вы часто этого не делаете. Можно использовать то же значение, что и оба args, как с регулярным не-вектором TEST
инструкция. (Тестирование foo & foo
это то же самое, что тестирование foo
).
в вашем случае вам нужно проверить, что все векторные элементы установлены. Если вы отменили сравнение, а затем или результат вместе (так что вы тестируете !(x1 < a1[i]) || !(x2 < a2[i]) || ...
) у вас будут векторы, которые вам нужно проверить для всех нулей, а не для всех. Но иметь дело с низким элементом все еще проблематично. Если вам нужно сохранить регистр, чтобы избежать необходимости в векторе маска для PTEST
/ VTESTPS
, вы можете сдвинуть вектор вправо на 4 байта, прежде чем делать PTEST
и ветвление на нем все-ноль.
С AVX представила VTESTPS
, что, я думаю, позволяет избежать возможной задержки обхода float -> int. Если вы использовали какие-либо инструкции int-domain Для генерации входных данных для теста, вы также можете использовать (V)PTEST
. (Я знаю, что вы использовали внутренние компоненты, но их больно печатать и смотреть по сравнению с мнемоникой.)