Я пытаюсь определить, является ли встроенная последовательность _mm512_mullox_epi64 (основа AVX-512) значительно более медленной, чем аппаратно-реализованная встроенная последовательность _mm512_mullo_epi64 (AVX-512 Double-Word и Quad-Word ISA).
_mm512_mullo_epi64 вызовет исключение «Недопустимая инструкция» на оборудовании с AVX-512, но без расширений набора инструкций DWQW.
У меня нет процессора с поддержкой AVX-512, и попытка провести бенчмаркинг с помощью godbolt дает очень противоречивые результаты. Мой код также не компилируется с использованием быстрой скамьи, поскольку в настоящее время вы не можете передавать параметры компилятора, такие как -mavx512dq
.Мне также интересно узнать, есть ли хороший вариант для использования AVX2, поскольку нет встроенной функции умножения 64-битных целых чисел на AVX2.
Использование _mm256_mul_pd с приведением часто приводит к неправильным результатам, когда произведение находится в пределах границ int64_t, но за пределами 64-битного двойного числа.
Вот мой тестовый код, если вам интересно:
#include "immintrin.h"
#include <cstdint>
#include <array>
#include <algorithm>
#include <numeric>
#include <iostream>
#include <chrono>
std::array<int64_t, 1000000> arr1;
std::array<int64_t, 1000000> arr2;
std::array<int64_t, 1000000> arr3;
class Timer
{
public:
Timer()
{
start = std::chrono::high_resolution_clock::now();
}//End of constructor
Timer(Timer const&) = delete;
Timer& operator=(Timer const&) = delete;
Timer(Timer&&) = delete;
Timer& operator=(Timer&&) = delete;
~Timer()
{
end = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::duration d = end - start;
std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(d).count() << "ns\n";
}//End of destructor
private:
std::chrono::high_resolution_clock::time_point start;
std::chrono::high_resolution_clock::time_point end;
};//End of class Timer
template<uint64_t SIZE1, uint64_t SIZE2, uint64_t SIZE3>
void mul_f(const std::array<int64_t, SIZE1>& src, const std::array<int64_t, SIZE2>& src2, std::array<int64_t, SIZE3>& dest)
{
__m512i _src1;
__m512i _src2;
__m512i _dest;
for(uint64_t i = 0; i < SIZE3; i+=8)
{
if((i + 8) > SIZE3)
{
break;
}
_src1 = _mm512_load_epi64(&src[i]);
_src2 = _mm512_load_epi64(&src2[i]);
_dest = _mm512_mullox_epi64(_src1, _src2);
_mm512_store_epi64(&dest[i], _dest);
}
}
template<uint64_t SIZE1, uint64_t SIZE2, uint64_t SIZE3>
void mul_dq(const std::array<int64_t, SIZE1>& src, const std::array<int64_t, SIZE2>& src2, std::array<int64_t, SIZE3>& dest)
{
__m512i _src1;
__m512i _src2;
__m512i _dest;
for(uint64_t i = 0; i < SIZE3; i+=8)
{
if((i + 8) > SIZE3)
{
break;
}
_src1 = _mm512_load_epi64(&src[i]);
_src2 = _mm512_load_epi64(&src2[i]);
_dest = _mm512_mullo_epi64(_src1, _src2);
_mm512_store_epi64(&dest[i], _dest);
}
}
template<uint64_t SIZE1, uint64_t SIZE2, uint64_t SIZE3>
void mul_avx2(const std::array<int64_t, SIZE1>& src, const std::array<int64_t, SIZE2>& src2, std::array<int64_t, SIZE3>& dest)
{
__m256i _src1;
__m256i _src2;
__m256i _dest;
for(uint64_t i = 0; i < SIZE3; i+=4)
{
if((i + 4) > SIZE3)
{
break;
}
_src1 = _mm256_load_si256((__m256i*)&src[i]);
_src2 = _mm256_load_si256((__m256i*)&src2[i]);
int64_t d[4] = {};
for (size_t x = 0; x != 4; ++x)
{
#ifdef _WIN32
d[x] = _src1.m256i_i64[x] * _src2.m256i_i64[x];
#else
d[x] = _src1[x] * _src2[x];
#endif
}//End for
_dest = _mm256_load_si256((__m256i*) &d);
_mm256_store_si256((__m256i*)&dest[i], _dest);
}
}
int main()
{
std::iota(arr1.begin(), arr1.end(), 5);
std::iota(arr2.begin(), arr2.end(), 2);
{
Timer();
mul_f(arr1, arr2, arr3);
}
{
Timer();
mul_dq(arr1, arr2, arr3);
}
{
Timer();
mul_avx2(arr1, arr2, arr3);
}
return static_cast<int>(arr3[0]);
}
Заранее благодарим за помощь.
аппаратное обеспечение с AVX-512, но без DWQW — для справки, единственными процессорами AVX-512 (пока что) без AVX-512DQ являются снятые с производства карты Xeon Phi. (en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512). Я предполагаю, что если AMD когда-либо реализует AVX-512, они будут включать AVX-512DQ, как это делает Intel в своих основных процессорах. Вы можете прогнозировать производительность с помощью инструментов статического анализа, таких как LLVM-MCA, или с помощью uops.info/table.html. (Размер 64-битного элемента
vpmullq
занимает 3 мкп на инструкцию в текущей версии Intel, по сравнению с 2 дляvpmulld
или 1 дляvpmuludq
.)mullox
реализован с использованием 32-битного умножения и длинного умножения "школьного типа", поэтому кажется, что это будет значительно медленнее: godbolt.org/z/We88796KnТакже связано: вы можете делать вещи с повышенной точностью, используя множители мантиссы с точностью
double
с умной битовой манипуляцией битовых шаблонов FP и FMA. Но если 64-битной точности достаточно, а одногоdouble
(52/53-бит) недостаточно, то да, 64-битное целое может быть подходящим вариантом, даже если это неудобно и медленно умножается.Самый быстрый способ умножения массива int64_t? имеет векторизованную вручную версию AVX2, которая должна быть значительно лучше, чем извлечение в скаляр, особенно между другими векторными операциями.
Хорошо, спасибо, ребята! Я не уверен, буду ли я обновлять свой код для использования _mm512_mullo_epi64, но Xeon Phi все еще довольно новый, и если бы это был я, я бы хотел, чтобы мой процессор стоимостью более 2000 долларов не устарел через 5 лет.