SSE оптимизированная эмуляция 64-битных целых
Для хобби-проекта, над которым я работаю, мне нужно эмулировать определенные 64-разрядные целочисленные операции на процессоре x86, и это должно быть быстрым.
В настоящее время я делаю это с помощью инструкций MMX, но с этим действительно сложно работать, потому что мне приходится все время сбрасывать состояние регистра fp (и потому что большинство инструкций MMX имеют дело со знаковыми целыми числами, и мне нужно поведение без знака).
Поэтому мне интересно, могут ли гуру SSE/ оптимизации здесь, в SO, предложить лучшую реализацию с использованием SSE.
Мне нужны следующие операции (вполне конкретные):
uint64_t X, Y;
X = 0;
X = 1;
X << 1;
X != Y;
X + 1;
X & 0x1 // get lsb
X | 0x1 // set lsb
X > Y;
В частности, мне не нужно сложение или смещение общего назначения, например, просто добавить единицу и сдвиг влево. На самом деле, только точные операции, показанные здесь.
За исключением, конечно, на x86, uint64_t
эмулируется с помощью двух 32-битных скаляров, что является медленным (и, в моем случае, просто не работает, потому что мне нужно, чтобы загрузки / хранилища были атомарными, чего не будет при загрузке / хранении двух отдельных регистров),
Следовательно, мне нужно SIMD решение. Некоторые из этих операций тривиальны, уже поддерживаются SSE2. Другие (!=
а также <
) требуется немного больше работы.
Предложения? SSE и SSE2 в порядке. Чтобы разрешить SSE3, потребовалось бы какое-то убеждение, и о SSE4, вероятно, не может быть и речи (CPU, поддерживающий SSE4, в любом случае, вероятно, будет работать 64-битным, и поэтому мне не нужны эти обходные пути)
1 ответ
SSE2 имеет прямую поддержку для некоторых 64-битных целочисленных операций:
Установите оба элемента на 0:
__m128i z = _mm_setzero_si128();
Установите оба элемента на 1:
__m128i z = _mm_set_epi32(0,1,0,1);
Вертикально складывать / вычитать каждое 64-битное целое число:
__m128i z = _mm_add_epi64(x,y)
__m128i z = _mm_sub_epi64(x,y)
Сдвиг влево:
__m128i z = _mm_slli_epi64(x,i) // i must be an immediate
Битовые операторы:
__m128i z = _mm_and_si128(x,y)
__m128i z = _mm_or_si128(x,y)
SSE не имеет приращений, поэтому вам придется использовать константу с 1
,
Сравнения сложнее, поскольку 64-битная поддержка отсутствует.
Вот один для равенства:
__m128i t = _mm_cmpeq_epi32(a,b);
__m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));
Это установит для каждого 64-битного элемента значение 0xffffffffffff
если они равны Если вы хотите это как 0
или же 1
в int
Вы можете вытащить его, используя _mm_cvtsi32_si128()
и добавить 1
,
И меньше, чем: (не полностью проверено)
a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
__m128i t = _mm_cmplt_epi32(a,b);
__m128i u = _mm_cmpgt_epi32(a,b);
__m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);
Это установит для каждого 64-битного элемента значение 0xffffffffffff
если соответствующий элемент в a
меньше чем b
,
Вот версии "равно" и "меньше чем", которые возвращают логическое значение. Они возвращают результат сравнения для нижнего 64-разрядного целого числа.
inline bool equals(__m128i a,__m128i b){
__m128i t = _mm_cmpeq_epi32(a,b);
__m128i z = _mm_and_si128(t,_mm_shuffle_epi32(t,177));
return _mm_cvtsi128_si32(z) & 1;
}
inline bool lessthan(__m128i a,__m128i b){
a = _mm_xor_si128(a,_mm_set1_epi32(0x80000000));
b = _mm_xor_si128(b,_mm_set1_epi32(0x80000000));
__m128i t = _mm_cmplt_epi32(a,b);
__m128i u = _mm_cmpgt_epi32(a,b);
__m128i z = _mm_or_si128(t,_mm_shuffle_epi32(t,177));
z = _mm_andnot_si128(_mm_shuffle_epi32(u,245),z);
return _mm_cvtsi128_si32(z) & 1;
}