Как умножить два 64-битных целых числа на два других 64-битных целых числа? Я не нашел ни одной инструкции, которая может это сделать.
Умножение SSE 2 64-битных целых чисел
Ответы (3)
Поздний ответ, но это лучшая версия того, что опубликовал Барабас.
Если вы когда-либо использовали векторные расширения GCC или Clang, они используют эту процедуру.
Здесь используется тот же метод, что и в умножении на длину и умножение на сетку.
65
* 73
----
15 // (5 * 3)
180 // (6 * 3) * 10
350 // (5 * 7) * 10
+ 4200 // + (6 * 7) * 100
------
4745
Однако вместо того, чтобы делать каждую единицу из 10, он использует каждую единицу из 32 бит и пропускает последнее умножение, потому что оно всегда будет сдвигаться за 64-й бит, точно так же, как вы не умножали бы 6 * 7, если бы вы усечение значений больше 99.
#include <emmintrin.h>
/*
* Grid/long multiply two 64-bit SSE lanes.
* Works for both signed and unsigned.
* ----------------.--------------.----------------.
* | | b >> 32 | a & 0xFFFFFFFF |
* |----------------|--------------|----------------|
* | d >> 32 | b*d << 64 | a*d << 32 |
* |----------------|--------------|----------------|
* | c & 0xFFFFFFFF | b*c << 32 | a*c |
* '----------------'--------------'----------------'
* Add all of them together to get the product.
*
* Because we truncate the value to 64 bits, b*d << 64 will be zero,
* so we can leave it out.
*
* We also can add a*d and b*c first and then shift because of the
* distributive property: (a << 32) + (b << 32) == (a + b) << 32.
*/
__m128i Multiply64Bit(__m128i ab, __m128i cd)
{
/* ac = (ab & 0xFFFFFFFF) * (cd & 0xFFFFFFFF); */
__m128i ac = _mm_mul_epu32(ab, cd);
/* b = ab >> 32; */
__m128i b = _mm_srli_epi64(ab, 32);
/* bc = b * (cd & 0xFFFFFFFF); */
__m128i bc = _mm_mul_epu32(b, cd);
/* d = cd >> 32; */
__m128i d = _mm_srli_epi64(cd, 32);
/* ad = (ab & 0xFFFFFFFF) * d; */
__m128i ad = _mm_mul_epu32(ab, d);
/* high = bc + ad; */
__m128i high = _mm_add_epi64(bc, ad);
/* high <<= 32; */
high = _mm_slli_epi64(high, 32);
/* return ac + high; */
return _mm_add_epi64(high, ac);
}
Проводник компилятора Примечание. Версия векторного расширения GCC также приведена ниже для сравнения.
-march=skylake-avx512
мы получаем AVX512DQ vpmulqq
:) AVX512, наконец, представил 64-битное целочисленное умножение размера элемента.
- person Peter Cordes; 15.01.2019
imul r64, r/m64
uop на 64-битное целое число — это очень хорошо. Самый быстрый способ умножить массив int64_t?). В моем ответе используется mullo_epi32
(SSE4.1 или AVX2) для одновременного получения обоих продуктов с низким и высоким уровнем, хотя pmulld
действительно занимает 2 мкп на процессорах Intel.
- person Peter Cordes; 15.01.2019
phaddd
, но он декодирует 2 перетасовки, которые передают paddd
uop по вертикали; его быстрее не использовать. Я не просматривал детали моего связанного ответа, но в нем упоминается использование psrlq / paddq / pand (всего 3 операции) вместо phadd + pshufd (3 операции в случайном порядке + ADD). Больше инструкций, но меньше мопов и гораздо меньше узких мест при случайном переносе. О, vpaddl
расширяет элементы. PHADDD имеет 2 входа и 1 выход, так что это не полная замена.
- person Peter Cordes; 16.01.2019
Я знаю, что это старый вопрос, но я действительно искал именно это. Поскольку для этого до сих пор нет инструкции, я сам реализовал 64-битное умножение с помощью pmuldq, как упомянул Пол Р. Вот что я придумал:
// requires g++ -msse4.1 ...
#include <emmintrin.h>
#include <smmintrin.h>
__m128i Multiply64Bit(__m128i a, __m128i b)
{
auto ax0_ax1_ay0_ay1 = a;
auto bx0_bx1_by0_by1 = b;
// i means ignored
auto ax1_i_ay1_i = _mm_shuffle_epi32(ax0_ax1_ay0_ay1, _MM_SHUFFLE(3, 3, 1, 1));
auto bx1_i_by1_i = _mm_shuffle_epi32(bx0_bx1_by0_by1, _MM_SHUFFLE(3, 3, 1, 1));
auto ax0bx0_ay0by0 = _mm_mul_epi32(ax0_ax1_ay0_ay1, bx0_bx1_by0_by1);
auto ax0bx1_ay0by1 = _mm_mul_epi32(ax0_ax1_ay0_ay1, bx1_i_by1_i);
auto ax1bx0_ay1by0 = _mm_mul_epi32(ax1_i_ay1_i, bx0_bx1_by0_by1);
auto ax0bx1_ay0by1_32 = _mm_slli_epi64(ax0bx1_ay0by1, 32);
auto ax1bx0_ay1by0_32 = _mm_slli_epi64(ax1bx0_ay1by0, 32);
return _mm_add_epi64(ax0bx0_ay0by0, _mm_add_epi64(ax0bx1_ay0by1_32, ax1bx0_ay1by0_32));
}
Godbolt на SSE Multiply64Bit.
_mm_mul_epi32
— это инструкция SSE4.1. _mm_mul_epu32
— это инструкция SSE2. _mm_mul_epu32
производит намного лучший код, но требует беззнаковых типов.
- person jww; 09.01.2019
Вам нужно будет реализовать собственную 64-битную процедуру умножения, используя 32-битные операции умножения. Это, вероятно, не будет более эффективным, чем просто делать это со скалярным кодом, особенно потому, что будет много перетасовки векторов, чтобы получить все необходимые операции.
pmuldqq
или что-то еще в SSE4?
- person Gunther Piez; 26.07.2013
pmuldq
, который представляет собой 32x32 => 64-битное умножение, поэтому вы можете использовать его в качестве строительного блока для 64x64-битного умножения.
- person Paul R; 26.07.2013