Сдвиг 4 целых чисел вправо на разные значения SIMD



SSE не предоставляет способа сдвига упакованных целых чисел на переменную величину (я могу использовать любые инструкции AVX и старше). Вы можете делать только однообразные смены. Результат, которого я пытаюсь достичь для каждого целого числа в векторе, таков.



i[0] = i[0] & 0b111111;
i[1] = (i[1]>>6) & 0b111111;
i[2] = (i[2]>>12) & 0b111111;
i[3] = (i[3]>>18) & 0b111111;


По существу пытается изолировать другую группу из 6 битов в каждом целом числе.



Так каково же оптимальное решение?



Вещи, о которых я думал:
Вы можете имитировать переменный сдвиг вправо, с переменным сдвигом влево и равномерным сдвигом вправо сдвиг. Я подумал о том, чтобы умножить упакованные целые числа на различную величину каждое (таким образом, имитируя сдвиг влево). Тогда с полученным результатом вы можете сделать равномерный сдвиг вправо и получить ответ. Проблема с этой конкретной операцией, которую я бы использовал для умножения, была бы _mm_mullo_epi32, которая имеет разочаровывающую задержку (10 циклов для haswell), и, учитывая мою программу, ей пришлось бы ждать результата, потому что этот конкретный результат является зависимостью для следующих инструкций. В целом этот метод я думаю было бы только немного быстрее, чем метод грубой силы, который распаковывает, сдвигает с помощью скалярных инструкций, а затем переупаковывает вектор, что займет около 20 циклов, я думаю.

604   1  

1 ответ:

Если AVX2 доступен , для этого требуется только одна эффективная инструкция. например:__m128i _mm_srlv_epi32 (__m128i a, __m128i count) (vpsrlvd), и 256-битные версии. Переменные сдвиги 32-битных и 64-битных элементов по соответствующим элементам счета доступны в левой, правой арифметике и правой логике. (Арифметический сдвиг вправо недоступен при 64-битном размере элемента.)

AVX512BW добавляет 16-битные переменные сдвиги .


Эмулируя его без AVX2:

Что это за цепочка зависимостей? Можете ли вы развернуть и перемежать так, чтобы два вектора находились в полете одновременно? Две длинные цепочки dep параллельно гораздо лучше, чем одна длинная цепочка dep, если она настолько длинна, что окно out-of-order не может видеть следующую цепочку dep в следующей итерации цикла.


Возможно, стоит сделать отдельную версию AVX2 вашей функции для использования на процессорах Haswell и более поздних процессорах (где вы можете использоватьпеременную shift ). Если вы это сделаете, ваша функция будет использовать толькоpmulld (mullo_epi32) на процессорах, где это наиболее эффективно. (то есть вы избегаете SSE4.1 mullo_epi32 на процессорах AVX2, потому что оказывается, что эти процессоры сделали эту инструкцию медленнее.)

pmulld похоже, лучшее, что мы можем сделать для пропускной способности и плавленого домена uop рассчитывать даже на Haswell.

На SnB / IvB, где это один uop для векторно-целочисленной единицы умножения, вся функция составляет только 2 uops / 6 Cycle latency / one на пропускную способность 1c. (Что еще хуже, чем мне удалось с shift / blend, так что вы хотели бы использовать только pmulld, если пропускная способность / размер кода имеет значение вообще,и вы не узкое место чисто по задержке. например, после разворачивания.)

Если ваши числа сдвигов являются постоянными, и у вас есть запасные биты в верхней части вашего регистра, вы можете умножить на степени 2, а затем использовать фиксированный сдвиг вправо. сдвиньте вправо каждый DW в __m128i на другую величину . Выбивание высоких битов не является проблемой для вашего извлечения битового поля, потому что вы собираетесь в любом случае держите только самые низкие биты.

// See the godbolt link below for a version of this with more comments
// SnB/IvB: 6c latency, 2 fused-domain uops.
__m128i isolate_successive_6bits_mul (__m128i input)
{
  // We can avoid the AND if we shift the elements all the way to the left to knock off the high garbage bits.
  // 32 - 6 - 18 = 8 extra bits to left shift
    __m128i mul_constant = _mm_set_epi32(1<<(0+8), 1<<(6+8), 1<<(12+8), 1<<(18+8));
    __m128i left_vshift = _mm_mullo_epi32(input, mul_constant);
    __m128i rightshifted = _mm_srli_epi32(left_vshift, (18+8));
    return rightshifted;
}

Умный способ с блендами:

(К сожалению, у нас нет AVX2 vpblendd для эффективных смесей dword, которые могут работать на любом порту. pblendw ограничивается портом 5 на процессорах Intel. blendps может быть хорошим для пропускной способности (работает на любом порту), но может вводить задержки обхода между целочисленными инструкциями.)

Сдвигайте и смешивайте так, чтобы каждый элемент заканчивался правильным общим количеством сдвигов.

И-маскировать низкий уровень 6 бит после объединения всего в один вектор.

Та же задержка, что и у метода грубой силы (см. ниже) на процессорах Intel, и лучшая пропускная способность (из-за меньшего количества UOP). Только две немедленные смеси, связывающие port5, хороши. (AVX2 vpblendd может работать на любом порту, но тогда мы просто используем vpsrlvd.)

// seems to be optimal for Intel CPUs.
__m128i isolate_successive_6bits (__m128i input)
{ // input =   [ D      C      B     A ]
  // output =  [ D>>18  C>>12  B>>6  A ] & set1(0b111111)
    __m128i right12 = _mm_srli_epi32(input, 12);
    __m128i merged = _mm_blend_epi16(input, right12, 0xF0);  // copy upper half, like `movhps` (but don't use that because of extra bypass delay)
    // merged = [ D>>12  C>>12  B>>0  A>>0 ]
    __m128i right6 = _mm_srli_epi32(merged, 6);
    merged = _mm_blend_epi16(merged, right6, 0b11001100);    // blend in the odd elements
    // merged = [ D>>(12+6)  C>>12  B>>(0+6)  A>>0 ]        
    return _mm_and_si128(merged, _mm_set1_epi32(0b111111));  // keep only the low 6 bits
}

Я поместил обе версии в проводник компилятора Godbolt.

Эта версия содержит только 5 uops, скомпилированных с gcc 5.3 -O3 -march=ivybridge:

    # input in xmm0, result in xmm0
isolate_successive_6bits:
    vpsrld          xmm1, xmm0, 12                # starts on cycle 0, result ready for the start of cycle 1
    vpblendw        xmm0, xmm0, xmm1, 240         # cycle 1
    vpsrld          xmm1, xmm0, 6                 # cycle 2
    vpblendw        xmm0, xmm0, xmm1, 204         # cycle 3
    vpand           xmm0, xmm0, XMMWORD PTR .LC0[rip] # cycle 4, result ready on cycle 5
    ret

Каждая инструкция является зависит от предыдущего, поэтому имеет задержку 5С. Процессоры SnB/IvB/HSW/BDW имеют только один порт сдвига, поэтому они не могут воспользоваться преимуществами параллелизма, доступного в более грубой версии (которая делает три смены с различным количеством сдвигов). Скайлэйк может, но затем два цикла смешивания впоследствии съедают улучшение.


Способ "грубой силы" :

Сделайте три сдвига три разных счета сдвигов и используйте три немедленных смешивания (pblendw) для объединения четыре вектора в один, который имеет каждый желаемый элемент.

// same latency as the previous version on Skylake
// slower on previous Intel SnB-family CPUs.
isolate_successive_6bits_parallel:
    vpsrld          xmm1, xmm0, 6            # cycle 0.   SKL: c0
    vpsrld          xmm2, xmm0, 12           # cycle 1 (resource conflict on pre-Skylake).  SKL: c0
    vpblendw        xmm1, xmm0, xmm1, 12     # cycle 2 (input dep).  SKL: c1
    vpsrld          xmm3, xmm0, 18           # cycle 2.  SKL: c1
    vpblendw        xmm0, xmm2, xmm3, 192    # cycle 3 (input dep). SKL: c2
    vpblendw        xmm0, xmm1, xmm0, 240    # cycle 4 (input dep). SKL: c3
    vpand           xmm0, xmm0, XMMWORD PTR .LC0[rip]  # cycle 5 (input dep). SKL: c4.
    ret

Выполнение слияния с цепочкой линейных зависимостей вместо дерева означает, что слияние может завершиться раньше, чем будет готов результат последнего сдвига:

isolate_successive_6bits_parallel2:
    vpsrld          xmm1, xmm0, 6          # c0.  SKL:c0
    vpsrld          xmm2, xmm0, 12         # c1.  SKL:c0
    vpblendw        xmm1, xmm0, xmm1, 12   # c2.  SKL:c1
    vpblendw        xmm1, xmm1, xmm2, 48   # c3.  SKL:c2
    vpsrld          xmm0, xmm0, 18         # c2.  SKL:c1
    vpblendw        xmm0, xmm1, xmm0, 192  # c4.  SKL:c3 (dep on xmm1)
    vpand           xmm0, xmm0, XMMWORD PTR .LC0[rip] # c5.  SKL:c4
    ret

Хм, нет, не помогает. Никакого выигрыша в задержку для ШНБ регистрироваться, или для СКЛ. Первое слияние может произойти только после одного сдвига, потому что несмещенный вход-это то, что нам нужно для одного элемента. Если бы элемент 0 нуждался в ненулевом количестве сдвигов, этот способ имел бы преимущество для пре-СКЛ, и, возможно, недостаток для СКЛ.

Comments

    Ничего не найдено.