Ответ 1
Я думаю, что возможно реализовать BigNum с SIMD эффективно, но не так, как вы предлагаете.
Вместо того, чтобы внедрять один BigNum с использованием SIMD-регистра (или с массивом SIMD-регистров), вы должны обрабатывать сразу несколько BigNums.
Рассмотрим 128-битное дополнение. Пусть 128-битные целые числа определяются парой высоких и низких 64-битных значений, и предположим, что мы хотим добавить 128-битное целое число (y_low, y_high)
в 128-битное целое число (x_low, x_high)
. С помощью скалярных 64-битных регистров требуется только две команды
add rax, rdi // x_low += y_low;
adc rdx, rsi // x_high += y_high + (x_low < y_low);
С SSE/AVX проблема, как объясняют другие, заключается в том, что нет флажков переноса SIMD. Флаг переноса должен быть рассчитан, а затем добавлен. Для этого требуется 64-разрядное сравнение без знака. Единственным реалистичным вариантом для этого с SSE является команда AMD XOP vpcomgtuq
vpaddq xmm2, xmm0, xmm2 // x_low += y_low;
vpcomgtuq xmm0, xmm0, xmm2 // x_low < y_low
vpaddq xmm1, xmm1, xmm3 // x_high += y_high
vpsubq xmm0, xmm1, xmm0 // x_high += xmm0
Для добавления двух пар 128-разрядных чисел используется четыре команды. Для скалярных 64-битных регистров требуется также четыре команды (два add
и два adc
).
С AVX2 мы можем добавить сразу четыре пары 128-битных чисел. Но в XOP нет 64-битной 64-разрядной команды без знака. Вместо этого мы можем сделать следующее для a<b
:
__m256i sign64 = _mm256_set1_epi64x(0x8000000000000000L);
__m256i aflip = _mm256_xor_si256(a, sign64);
__m256i bflip = _mm256_xor_si256(b, sign64);
__m256i cmp = _mm256_cmpgt_epi64(aflip,bflip);
Регистр sign64
может быть предварительно вычислен, поэтому необходимы только три инструкции. Поэтому добавление четырех пар 128-битных номеров с помощью AVX2 может быть выполнено с помощью шести инструкций
vpaddq
vpaddq
vpxor
vpxor
vpcmpgtq
vpsubq
тогда как скалярные регистры нуждаются в восьми инструкциях.
AVX512 имеет одну инструкцию для выполнения 64-битного сравнения без знака vpcmpuq
. Поэтому должно быть возможно добавить восемь пар 128-битных чисел, используя только четыре команды
vpaddq
vpaddq
vpcmpuq
vpsubq
В скалярном регистре потребуется 16 команд для добавления восьми пар 128-битных чисел.
Вот таблица с кратким изложением количества SIMD-инструкций (называемая nSIMD) и количеством скалярных инструкций (называемых nscalar), необходимых для добавления числа пар (называемых npairs) из 128-битных чисел
nSIMD nscalar npairs
SSE2 + XOP 4 4 2
AVX2 6 8 4
AVX2 + XOP2 4 8 4
AVX-512 4 16 8
Обратите внимание, что XOP2 еще не существует, и я только предполагаю, что он может существовать в какой-то момент.
Обратите также внимание на то, что для эффективного использования массивы BigNum необходимо хранить в массиве структуры массива (AoSoA). Например, используя l
, чтобы означать, что младшие 64-битные и h
означают высокие 64-бит, массив из 128-битных целых чисел хранится как массив структур, подобных этому
lhlhlhlhlhlhlhlh
вместо этого следует сохранить с помощью AoSoA, подобного этому
SSE2: llhhllhhllhhllhh
AVX2: llllhhhhllllhhhh
AVX512: llllllllhhhhhhhh