Существует ли быстрый способ проверить, является ли вектор SIMD нулевым вектором (все компоненты равны +-нулю). В настоящее время я использую алгоритм с использованием сдвигов, который выполняется за время log2 (N), где N - размерность вектора. Существует ли что-нибудь быстрее? Обратите внимание, что мой вопрос шире (теги), чем предлагаемый ответ, и относится к векторам всех типов (целые, с плавающей запятой, двойные,...).
Тест нулевого вектора SIMD
Ответы (2)
Как насчет этого простого кода avx? Я думаю, что это O (N), и не знаю, как вы могли бы добиться большего успеха, не делая предположений о входных данных - вам нужно фактически прочитать каждое значение, чтобы узнать, равно ли оно 0, поэтому речь идет о том, чтобы сделать как можно больше за цикл .
Вы должны иметь возможность массировать код в соответствии с вашими потребностями. Следует рассматривать как +0, так и -0 как ноль. Будет работать для невыровненных адресов памяти, но выравнивание по 32-байтовым адресам ускорит загрузку. Возможно, вам придется добавить что-то для обработки оставшихся байтов, если размер не кратен 8.
uint64_t num_non_zero_floats(float *mem_address, int size) {
uint64_t num_non_zero = 0;
__m256 zeros _mm256_setzero_ps ();
for(i = 0; i != size; i+=8) {
__m256 vec _mm256_loadu_ps (mem_addr + i);
__m256 comparison_out _mm256_cmp_ps (zeros, vec, _CMP_EQ_OQ); //3 cycles latency, throughput 1
uint64_t bits_non_zero = _mm256_movemask_ps(comparison_out); //2-3 cycles latency
num_non_zero += __builtin_popcountll(bits_non_zero);
}
return num_non_zero;
}
__m256i
, выполнить ИЛИ в два отдельных аккумулятора, а затем, в конце, ИЛИ объединить аккумуляторы вместе, привести к __m256
, выполнить _mm256_cmp_ps()
и выполнить _mm256_movemask_ps()
или vptest
. При этом есть изящный трюк, который можно использовать, чтобы избежать перемещения маски и подсчета всплывающих окон во внутреннем цикле, — привести результат сравнения к целому числу и вычесть его из аккумулятора. Если результатом сравнения являются все единицы (== -1
), вычитание его из аккумулятора добавляет к нему +1
. Если это 0, вычитание не будет иметь никакого эффекта.
- person Iwillnotexist Idonotexist; 14.05.2015
__m256i acc = _mm256_setzero_si256();/* In the loop... */ acc = _mm256_sub_epi32(acc, _mm256_castps_si256(comparison_out));
. И тогда в конце цикла вы суммируете 8 аккумуляторов, и вам никогда не придется проходить через popcount. И это работает как в системах с SSE, так и в системах без popcount.
- person Iwillnotexist Idonotexist; 14.05.2015
Если вы хотите проверить поплавки на +/- 0,0, вы можете проверить, что все биты равны нулю, кроме бита знака. Любые установленные биты в любом месте, кроме бита знака, означают, что число с плавающей запятой не равно нулю. (http://www.h-schmidt.net/FloatConverter/IEEE754.html)
руководство по оптимизации ассемблера от Agner Fog указывает, что вы можете проверить число с плавающей запятой или двойное число на ноль, используя целочисленные инструкции:
; Example 17.4b
mov eax, [rsi]
add eax, eax ; shift out the sign bit
jz IsZero
Однако для векторов использование ptest
с маской знакового бита лучше, чем использование paddd
для избавления от знакового бита. На самом деле, test [rsi], $0x7fffffff
может быть более эффективным, чем последовательность загрузки/добавления Агнера Фога, но 32-битная немедленная, вероятно, останавливает загрузку от микрослияния на Intel и, возможно, имеет больший размер кода.
x86 PTEST
(SSE4.1) выполняет побитовое И и устанавливает флаги на основе результата.
movdqa xmm0, [mask]
.loop:
ptest xmm0, [rsi+rcx]
jnz nonzero
add rcx, 16 # count up towards zero
jl .loop # with rsi pointing to past the end of the array
...
nonzero:
Или cmov
может быть полезно использовать флаги, установленные ptest
.
IDK, если бы можно было использовать инструкцию счетчика циклов, которая не устанавливала нулевой флаг, чтобы вы могли выполнить оба теста с одной инструкцией перехода или чем-то еще. Возможно нет. И дополнительная uop для слияния флагов (или зависание частичных флагов на более ранних процессорах) сведет на нет преимущество.
@Iwillnotexist Idonotexist: повторите один из ваших комментариев к ОП: вы не можете просто переместить маску, не сделав сначала pcmpeq
или cmpps
. Ненулевой бит может не находиться в старшем бите! Вы, наверное, знали об этом, но один из ваших комментариев, похоже, упустил это из виду.
Мне нравится идея объединения нескольких значений с помощью операции ИЛИ перед фактическим тестированием. Вы правы в том, что знаковые биты будут выполнять операцию ИЛИ с другими знаковыми битами, а затем вы игнорируете их так же, как если бы вы тестировали по одному. Цикл, который POR
s 4 или 8 векторов перед каждым PTEST
, вероятно, будет быстрее. (PTEST
составляет 2 мкп и не может макросплавиться с jcc
.)
N
велико, то очевидным решением будет обработка элементов группами поW
, гдеW
— самый большой блок, который вы можете обработать, объединив их вместе с помощью операции ИЛИ. Когда вы дойдете до конца, у вас будут варианты: 1)ptest
в SSE4.1+ 2) сравнить с нулем, затемpmovmskb
в SSE2+ и для целочисленных типов илиmovmskps
/movmskpd
для одинарной/двойной точности. Если N особенно велико, окончательное сокращение не будет вашим узким местом; Это будет потоковая передача данных, которые должны быть объединены ИЛИ в ваши регистры. Для NEON на ARMv7 окончательное сокращение можно выполнить с помощью попарных беззнаковыхmax
es, пока не будет достигнут 32-битный размер слова. - person Iwillnotexist Idonotexist   schedule 15.03.2015movmskps
, вы правильно игнорируете знак нуля. - person Iwillnotexist Idonotexist   schedule 15.03.2015