如何将 SSE XMM 或 AVX YMM 和 ZMM 寄存器中的所有 32 位或 64 位子寄存器相加?
How to sum all 32-bit or 64-bit sub-registers in an SSE XMM, or AVX YMM, and ZMM register?
假设您的任务在每个浮点子寄存器中产生小计。我没有看到将小计汇总为一个浮点总数的指令。我是否需要将 MM 寄存器存储在普通的旧内存中,然后使用简单的指令进行求和?
(这些是双精度还是单精度尚未解决,如果我能找到操作码,我计划对每个 CPU 变体进行编码,直到即将推出的(?)512 位 AVX 版本。 )
我已经为 AVX2 实现了以下内联函数。它将所有元素和 returns 结果相加。您可以将此视为建议答案,以为此目的开发您自己的功能。
注意:_mm256_extract_epi32
未针对 AVX 提供,您可以将自己的方法与 vmovss
一起使用,例如 float _mm256_cvtss_f32 (__m256 a)
并开发您的水平加法函数。
// my horizontal addition of epi32
inline int _mm256_hadd2_epi32(__m256i a)
{
__m256i a_hi;
a_hi = _mm256_permute2x128_si256(a, a, 1); //maybe it should be 4
a = _mm256_hadd_epi32(a, a_hi);
a = _mm256_hadd_epi32(a, a);
a = _mm256_hadd_epi32(a, a);
return _mm256_extract_epi32(a,0);
}
wget http://www.agner.org/optimize/vectorclass.zip
unzip vectorclass.zip -d vectorclass
cd vectorclass/
此代码为 GPLv3。
上交所
grep -A11 horizontal_add vectorf128.h
static inline float horizontal_add (Vec4f const & a) {
#if INSTRSET >= 3 // SSE3
__m128 t1 = _mm_hadd_ps(a,a);
__m128 t2 = _mm_hadd_ps(t1,t1);
return _mm_cvtss_f32(t2);
#else
__m128 t1 = _mm_movehl_ps(a,a);
__m128 t2 = _mm_add_ps(a,t1);
__m128 t3 = _mm_shuffle_ps(t2,t2,1);
__m128 t4 = _mm_add_ss(t2,t3);
return _mm_cvtss_f32(t4);
#endif
--
static inline double horizontal_add (Vec2d const & a) {
#if INSTRSET >= 3 // SSE3
__m128d t1 = _mm_hadd_pd(a,a);
return _mm_cvtsd_f64(t1);
#else
__m128 t0 = _mm_castpd_ps(a);
__m128d t1 = _mm_castps_pd(_mm_movehl_ps(t0,t0));
__m128d t2 = _mm_add_sd(a,t1);
return _mm_cvtsd_f64(t2);
#endif
}
AVX
grep -A6 horizontal_add vectorf256.h
static inline float horizontal_add (Vec8f const & a) {
__m256 t1 = _mm256_hadd_ps(a,a);
__m256 t2 = _mm256_hadd_ps(t1,t1);
__m128 t3 = _mm256_extractf128_ps(t2,1);
__m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
return _mm_cvtss_f32(t4);
}
--
static inline double horizontal_add (Vec4d const & a) {
__m256d t1 = _mm256_hadd_pd(a,a);
__m128d t2 = _mm256_extractf128_pd(t1,1);
__m128d t3 = _mm_add_sd(_mm256_castpd256_pd128(t1),t2);
return _mm_cvtsd_f64(t3);
}
AVX512
grep -A3 horizontal_add vectorf512.h
static inline float horizontal_add (Vec16f const & a) {
#if defined(__INTEL_COMPILER)
return _mm512_reduce_add_ps(a);
#else
return horizontal_add(a.get_low() + a.get_high());
#endif
}
--
static inline double horizontal_add (Vec8d const & a) {
#if defined(__INTEL_COMPILER)
return _mm512_reduce_add_pd(a);
#else
return horizontal_add(a.get_low() + a.get_high());
#endif
}
get_high()
和 get_low()
Vec8f get_high() const {
return _mm256_castpd_ps(_mm512_extractf64x4_pd(_mm512_castps_pd(zmm),1));
}
Vec8f get_low() const {
return _mm512_castps512_ps256(zmm);
}
Vec4d get_low() const {
return _mm512_castpd512_pd256(zmm);
}
Vec4d get_high() const {
return _mm512_extractf64x4_pd(zmm,1);
}
对于整数,请在 vectori128.h、vectori256.h 和 vectori512.h 中查找 horizontal_add
。
您也可以直接使用 Vector Class 库 (VCL)
#include <stdio.h>
#define MAX_VECTOR_SIZE 512
#include "vectorclass.h"
int main(void) {
float x[16]; for(int i=0;i<16;i++) x[i]=i+1;
Vec4f v4 = Vec4f().load(x);
Vec8f v8 = Vec8f().load(x);
Vec16f v16 = Vec16f().load(x);
printf("%f %d\n", horizontal_add(v4), 4*5/2);
printf("%f %d\n", horizontal_add(v8), 8*9/2);
printf("%f %d\n", horizontal_add(v16), 16*17/2);
}
像这样编译(GCC 只有我的 KNL 对于 AVX512 来说太旧了)
SSE2: g++ -O3 test.cpp
AVX: g++ -O3 -mavx test.cpp
AVX512ER: icpc -O3 -xMIC-AVX512 test.cpp
输出
10.000000 10
36.000000 36
136.000000 136
VCL 库的一个好处是,如果您使用例如Vec8f 对于只有 SSE2 的系统,它将使用 SSE 模拟 AVX 两次。
有关如何使用 MSVC、ICC、Clang 和 GCC 编译不同指令集的信息,请参阅 vectorclass.pdf 手册中的 "Instruction sets and CPU dispatching" 部分。
假设您的任务在每个浮点子寄存器中产生小计。我没有看到将小计汇总为一个浮点总数的指令。我是否需要将 MM 寄存器存储在普通的旧内存中,然后使用简单的指令进行求和?
(这些是双精度还是单精度尚未解决,如果我能找到操作码,我计划对每个 CPU 变体进行编码,直到即将推出的(?)512 位 AVX 版本。 )
我已经为 AVX2 实现了以下内联函数。它将所有元素和 returns 结果相加。您可以将此视为建议答案,以为此目的开发您自己的功能。
注意:_mm256_extract_epi32
未针对 AVX 提供,您可以将自己的方法与 vmovss
一起使用,例如 float _mm256_cvtss_f32 (__m256 a)
并开发您的水平加法函数。
// my horizontal addition of epi32
inline int _mm256_hadd2_epi32(__m256i a)
{
__m256i a_hi;
a_hi = _mm256_permute2x128_si256(a, a, 1); //maybe it should be 4
a = _mm256_hadd_epi32(a, a_hi);
a = _mm256_hadd_epi32(a, a);
a = _mm256_hadd_epi32(a, a);
return _mm256_extract_epi32(a,0);
}
wget http://www.agner.org/optimize/vectorclass.zip
unzip vectorclass.zip -d vectorclass
cd vectorclass/
此代码为 GPLv3。
上交所
grep -A11 horizontal_add vectorf128.h
static inline float horizontal_add (Vec4f const & a) {
#if INSTRSET >= 3 // SSE3
__m128 t1 = _mm_hadd_ps(a,a);
__m128 t2 = _mm_hadd_ps(t1,t1);
return _mm_cvtss_f32(t2);
#else
__m128 t1 = _mm_movehl_ps(a,a);
__m128 t2 = _mm_add_ps(a,t1);
__m128 t3 = _mm_shuffle_ps(t2,t2,1);
__m128 t4 = _mm_add_ss(t2,t3);
return _mm_cvtss_f32(t4);
#endif
--
static inline double horizontal_add (Vec2d const & a) {
#if INSTRSET >= 3 // SSE3
__m128d t1 = _mm_hadd_pd(a,a);
return _mm_cvtsd_f64(t1);
#else
__m128 t0 = _mm_castpd_ps(a);
__m128d t1 = _mm_castps_pd(_mm_movehl_ps(t0,t0));
__m128d t2 = _mm_add_sd(a,t1);
return _mm_cvtsd_f64(t2);
#endif
}
AVX
grep -A6 horizontal_add vectorf256.h
static inline float horizontal_add (Vec8f const & a) {
__m256 t1 = _mm256_hadd_ps(a,a);
__m256 t2 = _mm256_hadd_ps(t1,t1);
__m128 t3 = _mm256_extractf128_ps(t2,1);
__m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
return _mm_cvtss_f32(t4);
}
--
static inline double horizontal_add (Vec4d const & a) {
__m256d t1 = _mm256_hadd_pd(a,a);
__m128d t2 = _mm256_extractf128_pd(t1,1);
__m128d t3 = _mm_add_sd(_mm256_castpd256_pd128(t1),t2);
return _mm_cvtsd_f64(t3);
}
AVX512
grep -A3 horizontal_add vectorf512.h
static inline float horizontal_add (Vec16f const & a) {
#if defined(__INTEL_COMPILER)
return _mm512_reduce_add_ps(a);
#else
return horizontal_add(a.get_low() + a.get_high());
#endif
}
--
static inline double horizontal_add (Vec8d const & a) {
#if defined(__INTEL_COMPILER)
return _mm512_reduce_add_pd(a);
#else
return horizontal_add(a.get_low() + a.get_high());
#endif
}
get_high()
和 get_low()
Vec8f get_high() const {
return _mm256_castpd_ps(_mm512_extractf64x4_pd(_mm512_castps_pd(zmm),1));
}
Vec8f get_low() const {
return _mm512_castps512_ps256(zmm);
}
Vec4d get_low() const {
return _mm512_castpd512_pd256(zmm);
}
Vec4d get_high() const {
return _mm512_extractf64x4_pd(zmm,1);
}
对于整数,请在 vectori128.h、vectori256.h 和 vectori512.h 中查找 horizontal_add
。
您也可以直接使用 Vector Class 库 (VCL)
#include <stdio.h>
#define MAX_VECTOR_SIZE 512
#include "vectorclass.h"
int main(void) {
float x[16]; for(int i=0;i<16;i++) x[i]=i+1;
Vec4f v4 = Vec4f().load(x);
Vec8f v8 = Vec8f().load(x);
Vec16f v16 = Vec16f().load(x);
printf("%f %d\n", horizontal_add(v4), 4*5/2);
printf("%f %d\n", horizontal_add(v8), 8*9/2);
printf("%f %d\n", horizontal_add(v16), 16*17/2);
}
像这样编译(GCC 只有我的 KNL 对于 AVX512 来说太旧了)
SSE2: g++ -O3 test.cpp
AVX: g++ -O3 -mavx test.cpp
AVX512ER: icpc -O3 -xMIC-AVX512 test.cpp
输出
10.000000 10
36.000000 36
136.000000 136
VCL 库的一个好处是,如果您使用例如Vec8f 对于只有 SSE2 的系统,它将使用 SSE 模拟 AVX 两次。
有关如何使用 MSVC、ICC、Clang 和 GCC 编译不同指令集的信息,请参阅 vectorclass.pdf 手册中的 "Instruction sets and CPU dispatching" 部分。