Тест нулевого вектора SIMD

Существует ли быстрый способ проверить, является ли вектор SIMD нулевым вектором (все компоненты равны +-нулю). В настоящее время я использую алгоритм с использованием сдвигов, который выполняется за время log2 (N), где N - размерность вектора. Существует ли что-нибудь быстрее? Обратите внимание, что мой вопрос шире (теги), чем предлагаемый ответ, и относится к векторам всех типов (целые, с плавающей запятой, двойные,...).


person user1095108    schedule 14.03.2015    source источник
comment
Зависит от доступных инструкций, помечая неон и avx вместе. Я не знаю, что вы задумали.   -  person harold    schedule 14.03.2015
comment
@harold список / таблица встроенных функций или идей для выполнения этой очень распространенной операции. Если вопрос слишком широкий, я удалю.   -  person user1095108    schedule 14.03.2015
comment
Ну, в самом общем случае вы, вероятно, застряли с log (n), это сводится к тому, если вы реализуете нулевую инструкцию в аппаратном обеспечении, где ваше дерево ИЛИ (или пары вместе, пока у вас не будет 1 бит, который равен 0, если и только если бы все входные биты были равны 0) было бы глубиной log(n) уровней. Так что, если мы не сможем немного сдвинуть стойки ворот, это будет фактически невозможно.   -  person harold    schedule 15.03.2015
comment
Если N велико, то очевидным решением будет обработка элементов группами по W, где W — самый большой блок, который вы можете обработать, объединив их вместе с помощью операции ИЛИ. Когда вы дойдете до конца, у вас будут варианты: 1) ptest в SSE4.1+ 2) сравнить с нулем, затем pmovmskb в SSE2+ и для целочисленных типов или movmskps/movmskpd для одинарной/двойной точности. Если N особенно велико, окончательное сокращение не будет вашим узким местом; Это будет потоковая передача данных, которые должны быть объединены ИЛИ в ваши регистры. Для NEON на ARMv7 окончательное сокращение можно выполнить с помощью попарных беззнаковых maxes, пока не будет достигнут 32-битный размер слова.   -  person Iwillnotexist Idonotexist    schedule 15.03.2015
comment
@IwillnotexistIdonotexist Вы приняли во внимание подписанный ноль? Я думаю, что вы этого не сделали.   -  person user1095108    schedule 15.03.2015
comment
@harold Я не думаю, что дерево ИЛИ работает с нулями со знаком.   -  person user1095108    schedule 15.03.2015
comment
@user1095108 user1095108 На самом деле, я и @harold тоже. Если в векторе только значения +0, то результат редукции будет тождественно +0, а если есть только +0 и -0, то ИЛИ-редукция будет генерировать -0. И, конечно же, вы должны знать, что IEEE предписывает сравнение -0 равным 0, поэтому, когда вы выполняете это сравнение для == 0 перед movmskps, вы правильно игнорируете знак нуля.   -  person Iwillnotexist Idonotexist    schedule 15.03.2015
comment
@ user1095108 это тривиальное расширение того же принципа   -  person harold    schedule 15.03.2015


Ответы (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;
}
person Hal    schedule 22.04.2015
comment
Я думаю, что __builtin_popcountll не имеет значения для определения нуля. - person user1095108; 22.04.2015
comment
да, просто проверьте bits_non_zero и, кроме того, конечно, выйдите из цикла, остановив ненужную обработку, если она не равна нулю, если вы хотите эту оптимизацию. Код фактически подсчитывает количество поплавков в векторе, которые не равны нулю, как следует из его названия, поэтому вы должны иметь возможность массировать код в соответствии со своими потребностями. - person Hal; 23.04.2015
comment
@Hal Это вряд ли будет быстрее, чем просто загрузить данные как __m256i, выполнить ИЛИ в два отдельных аккумулятора, а затем, в конце, ИЛИ объединить аккумуляторы вместе, привести к __m256, выполнить _mm256_cmp_ps() и выполнить _mm256_movemask_ps() или vptest. При этом есть изящный трюк, который можно использовать, чтобы избежать перемещения маски и подсчета всплывающих окон во внутреннем цикле, — привести результат сравнения к целому числу и вычесть его из аккумулятора. Если результатом сравнения являются все единицы (== -1), вычитание его из аккумулятора добавляет к нему +1. Если это 0, вычитание не будет иметь никакого эффекта. - person Iwillnotexist Idonotexist; 14.05.2015
comment
@Hal Кратко, __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. Ненулевой бит может не находиться в старшем бите! Вы, наверное, знали об этом, но один из ваших комментариев, похоже, упустил это из виду.

Мне нравится идея объединения нескольких значений с помощью операции ИЛИ перед фактическим тестированием. Вы правы в том, что знаковые биты будут выполнять операцию ИЛИ с другими знаковыми битами, а затем вы игнорируете их так же, как если бы вы тестировали по одному. Цикл, который PORs 4 или 8 векторов перед каждым PTEST, вероятно, будет быстрее. (PTEST составляет 2 мкп и не может макросплавиться с jcc.)

person Peter Cordes    schedule 08.07.2015