2015-12-27 3 views
5

Мне нужно сдвинуть переменную __m128i (скажем, v) на m бит таким образом, чтобы биты перемещались по всей переменной (So, результирующая переменная представляет v * 2^m). Каков наилучший способ сделать это ?!Лучший способ сдвинуть __m128i?

Обратите внимание, что _mm_slli_epi64 сдвиги v0 и v1 отдельно:

r0 := v0 << count 
r1 := v1 << count 

так последние биты v0 пропустил, но я хочу, чтобы переместить эти биты в Г1.

Edit: Я ищу кода, быстрее, чем это (м < 64):

r0 = v0 << m; 
r1 = v0 >> (64-m); 
r1 ^= v1 << m; 
r2 = v1 >> (64-m); 
+1

Если 'M' случается быть кратным 8 битам, и у вас есть SSSE3, вам повезло:' palignr'. Если нет, он становится уродливым, и вам действительно нужно делать сдвиги, AND, тасования и OR. –

+1

См. Http://stackoverflow.com/questions/9980801/looking-for-sse-128-bit-shift-operation-for-non-immediate-shift-value –

+0

Выполняете ли вы обработку битовых потоков или арифметических переменных (ints, поплавки и т. д.)? – bazza

ответ

1

В SSE4.A инструкции insrq и extrq могут быть использованы для сдвига (и поворот) через __mm128i 1 -64 бит за раз. В отличие от аналогов 8/16/32/64 бит pextrN/pinsrX, эти инструкции выбирают или вставляют m бит (между 1 и 64) при любом смещении бита от 0 до 127. Следует отметить, что сумма длины и смещения не должна превышать 128.

+0

См. Пересмотренный ответ. В правильной инструкции нет p. –

+2

Большой оговоркой, по-видимому, является только ее AMD. –

3

Для подсчета постоянных сдвигов во время компиляции вы можете получить неплохие результаты. В противном случае это не так.

Это всего лишь реализация SSE кода r0/r1 с вашего вопроса, так как нет другого очевидного способа сделать это. Сдвиги с переменным числом доступны только для бит-сдвигов в векторных элементах, а не для смещений байтов всего регистра. Таким образом, мы просто переносим низкие 64 бит до максимума 64 и используем сдвиг с переменным числом, чтобы поместить их в нужное место.

// untested 
#include <immintrin.h> 

/* some compilers might choke on slli/srli with non-compile-time-constant args 
* gcc generates the xmm, imm8 form with constants, 
* and generates the xmm, xmm form with otherwise. (With movd to get the count in an xmm) 
*/ 

// doesn't optimize for the special-case where count%8 = 0 
// could maybe do that in gcc with if(__builtin_constant_p(count)) { if (!count%8) return ...; } 
__m128i mm_bitshift_left(__m128i x, unsigned count) 
{ 
    __m128i carry = _mm_bslli_si128(x, 8); // old compilers only have the confusingly named _mm_slli_si128 synonym 
    if (count >= 64) 
     return _mm_slli_epi64(carry, count-64); // the non-carry part is all zero, so return early 
    // else 
    carry = _mm_srli_epi64(carry, 64-count); // After bslli shifted left by 64b 

    x = _mm_slli_epi64(x, count); 
    return _mm_or_si128(x, carry); 
} 

__m128i mm_bitshift_left_3(__m128i x) { // by a specific constant, to see inlined constant version 
    return mm_bitshift_left(x, 3); 
} 
// by a specific constant, to see inlined constant version 
__m128i mm_bitshift_left_100(__m128i x) { return mm_bitshift_left(x, 100); } 

Я думал, что это будет менее удобно, чем оказалось. _mm_slli_epi64 работает с gcc/clang/icc, даже если счетчик не является константой времени компиляции (генерирует movd из целочисленной reg в xmm reg). Существует _mm_sll_epi64 (__m128i a, __m128i count) (обратите внимание на недостаток i), но, по крайней мере, в наши дни, внутренний код i может генерировать любую форму psllq.


во время компиляции постоянной версии счета являются довольно эффективным, compiling to 4 instructions (или 5 без AVX):

mm_bitshift_left_3(long long __vector(2)): 
     vpslldq xmm1, xmm0, 8 
     vpsrlq xmm1, xmm1, 61 
     vpsllq xmm0, xmm0, 3 
     vpor xmm0, xmm0, xmm1 
     ret 

Performance:

Это имеет 3 цикла задержки (vpslldq (1) - > vpsrlq (1) -> vpor (1)) на Intel SnB/IvB/Haswell, пропускная способность которого ограничена одним на 2 цикла (насыщение блока сдвига вектора на порте 0). Байт-сдвиг работает на блоке тасования на другом порту. Сдвиг векторов немедленного счета - это все одноуровневые инструкции, так что это всего лишь 4 слитых домена, которые занимают пространство конвейера, когда смешиваются с другим кодом. (Variable-счета векторные сдвиги 2 моп, 2 цикла задержки, так что переменная-счетчик версии этой функции хуже, чем она выглядит из подсчета инструкций.)

Или для подсчетов> = 64:

mm_bitshift_left_100(long long __vector(2)): 
     vpslldq xmm0, xmm0, 8 
     vpsllq xmm0, xmm0, 36 
     ret 

Если ваш счетчик смены не константа времени компиляции, вам нужно разветвить счетчик> 64, чтобы выяснить, следует ли сдвигать перенос влево или вправо.Я считаю, что значение сдвига интерпретируется как целое число без знака, поэтому отрицательный счет невозможно.

Также требуются дополнительные инструкции для подсчета int и 64-счета в векторные регистры. Выполнение этого безрассудным способом с векторным сравнением и инструкцией по смешиванию может быть возможно, но ветка, вероятно, хорошая идея.


версия переменного счетчик для __uint128_t в регистрах GP выглядит довольно хорошо; лучше, чем версия SSE. Clang does a slightly better job than gcc, emitting fewer mov instructions, но он по-прежнему использует две команды cmov для счета count = = 64. (. Поскольку команды x86 целочисленный сдвиг маскировать счет, а не насыщением)

__uint128_t leftshift_int128(__uint128_t x, unsigned count) { 
    return x << count; // undefined if count >= 128 
} 
+0

Большое спасибо. К сожалению, 'count' не является константой времени компиляции. Однако я проверю оба предложения. – user0

+0

Согласно моим тестам, мой старый код, написанный 4 'int64_t' vars, быстрее (> 2 раза) для произвольно сгенерированного' count'; но для константы compile-time 'count',' mm_bitshift_left' - по меньшей мере в 1,5 раза быстрее. – user0

+0

@ user0: Я не удивлен. В реальном приложении, я ожидаю, что будет немного предсказуемости при сдвиге. Кроме того, был ли ваш тест на микробиблиотеку * просто * сдвигом или он проверил сдвиг в качестве операции между двумя другими векторными свойствами? В этом случае сдвиг 'int64_t' должен получить значения от вектора до GP regs и обратно. (Я думаю, что в своем ответе я сказал, что если ваши данные еще не в векторных регах, сдвиг '__uint128' (или его рукописный эквивалент с' int64_t') должен преуспеть.) –

 Смежные вопросы

  • Нет связанных вопросов^_^