标题缺乏一个更好的名字,我不确定我是否能够清楚地解释自己。我正在寻找一种通过索引访问“数据类型”的方法,但不强制编译器将其保存在数组中。在编写基于SSE / AVX内在函数的低级代码时会出现问题。
为了便于编程,我想编写如下代码,固定长度循环“寄存器”(数据类型__m512
):
inline void load(__m512 *vector, const float *in)
{
for(int i=0; i<24; i++)
vector[i] = _mm512_load_ps((in + i*SIMD_WIDTH));
}
// similarely: inline add(...) and inline store(...)
void add(float *in_out, const float *in)
{
__m512 vector1[24];
__m512 vector2[24];
load(vector1, in_out);
load(vector2, in);
add(vector1, vector2);
store(in_out, vector1);
}
将vector1
和vector2
定义为数组这一事实对于编译器(在我的情况下为icc
)来说似乎很麻烦:它似乎被迫使其“可寻址” ,将它保留在堆栈上,从而生成许多我不需要的load
和store
指令。据我所知,这是允许使用vector1
或vector2
的指针算术。
我希望编译器将所有仅保留在寄存器中。我知道 是可能的:如果我编写的代码没有__m512
数组但是很多单个 __m512
变量,编译器会生成更好的代码。< / p>
解决方案:
我(不成功)尝试使用类似下面的类(我曾希望switch语句和其他所有内容都会被优化掉):
class Vector
{
inline __m512 & operator[](int i)
{
switch(i)
case 0: return component0;
// ...
case 23: return component23;
}
__m512 component0;
// ...
__m512 component23;
};
我也考虑了宏,但找不到一个好的解决方案。
有什么建议吗?
谢谢,
西蒙
在下面的答案中发表评论后,这里有一个更详细的例子,我想做什么(这仍然是一个简化):
inline void project(__m512 *projected_vector, __m512 *vector)
{
for(int i=0; i<3; i++)
projected_vector[i] = _mm512_add_ps(vector[i], vector[i+3]);
}
inline void matrix_multiply(__m512 *out, const float *matrix, __m512 *in)
{
for(int i=0; i<3; i++)
{
out[i] = _mm512_mul_ps( matrix[3*i+0], in[0]);
out[i] = _mm512_fmadd_ps(matrix[3*i+1], in[1], out[i]);
out[i] = _mm512_fmadd_ps(matrix[3*i+2], in[2], out[i]);
}
}
inline void reconstruct(__m512 *vector, __m512 *projected_vector)
{
for(int i=0; i<3; i++)
vector[i] = _mm512_add_ps(vector[i], projected_vector[i]);
for(int i=0; i<3; i++)
vector[i+3] = _mm512_sub_ps(vector[i], projected_vector[i]);
}
inline void hopping_term(float *in_out, const float *matrix_3x3, const float *in)
{
__m512 vector_in[6];
__m512 vector_out[6];
__m512 half_vector1[3];
__m512 half_vector2[3];
load(vector_in, in);
project(half_vector1, vector_in);
matrix_multiply(half_vector2, matrix_3x3, half_vector1);
load(vector_out, in_out);
reconstruct(vector_out, half_vector2);
store(in_out, vector_out);
}
答案 0 :(得分:1)
我最近使用模板元编程做了一些非常类似的事情。在以下代码中,我认为您只需将_mm256
替换为_mm512
,并将SIMD_WIDTH
更改为16
。这应该循环展开你的循环。
#include <x86intrin.h>
#define SIMD_WIDTH 8
#define LEN 24*SIMD_WIDTH
template<int START, int N>
struct Repeat {
static void add (float * x, float * y, float * z) {
_mm256_store_ps(&z[START],_mm256_add_ps(_mm256_load_ps(&x[START]) ,_mm256_load_ps(&y[START])));
Repeat<START+SIMD_WIDTH, N>::add(x,y,z);
}
};
template<int N>
struct Repeat<LEN, N> {
static void add (float * x, float * y, float * z) {}
};
void sum_unroll(float *x, float *y, float *z, const int n) {
Repeat<0,LEN>::add(x,y,z);
}
当我使用g++ -mavx -O3 -S -masm=intel
进行编译时,程序集看起来像
vmovaps ymm0, YMMWORD PTR [rdi]
vaddps ymm0, ymm0, YMMWORD PTR [rsi]
vmovaps YMMWORD PTR [rdx], ymm0
vmovaps ymm0, YMMWORD PTR [rdi+32]
vaddps ymm0, ymm0, YMMWORD PTR [rsi+32]
vmovaps YMMWORD PTR [rdx+32], ymm0
vmovaps ymm0, YMMWORD PTR [rdi+64]
vaddps ymm0, ymm0, YMMWORD PTR [rsi+64]
vmovaps YMMWORD PTR [rdx+64], ymm0
vmovaps ymm0, YMMWORD PTR [rdi+96]
vaddps ymm0, ymm0, YMMWORD PTR [rsi+96]
vmovaps YMMWORD PTR [rdx+96], ymm0
vmovaps ymm0, YMMWORD PTR [rdi+128]
vaddps ymm0, ymm0, YMMWORD PTR [rsi+128]
vmovaps YMMWORD PTR [rdx+128], ymm0
...
vmovaps ymm0, YMMWORD PTR [rdi+736]
vaddps ymm0, ymm0, YMMWORD PTR [rsi+736]
vmovaps YMMWORD PTR [rdx+736], ymm0
我最近成功地使用了这种方法,我试图一次推动4个微操作(更多使用融合)。例如,在一个时钟周期内完成两个负载,一个存储和一个两个FMA。我检查了结果,以确保没有错误。在我的一些测试中,我能够提高效率。
编辑:这是基于OP更新问题的解决方案。我在传递中使用数组用于SIMD变量时遇到了问题,因此我通常不会使用数组。此外,由于寄存器重命名,我很少使用许多SIMD寄存器(我认为我使用的最多是11)。在这个例子中,只需要五个。
#include <x86intrin.h>
#define SIMD_WIDTH 8
static inline __m256 load(const float *in) {
return _mm256_loadu_ps(in);
}
inline void store(float *out, __m256 const &vector) {
_mm256_storeu_ps(out, vector);
}
inline __m256 project(__m256 const &a, __m256 const &b) {
return _mm256_add_ps(a, b);
}
inline void reconstruct(__m256 &vector1, __m256 &vector2, __m256 &projected_vector) {
vector1 = _mm256_add_ps(vector1, projected_vector);
vector2 = _mm256_sub_ps(vector1, projected_vector);
}
class So {
public:
__m256 half_vector[3];
So(const float *in) {
for(int i=0; i<3; i++)
half_vector[i] = project(load(&in[i*SIMD_WIDTH]), load(&in[(3+i)*SIMD_WIDTH]));
}
__m256 matrix_multiply(const float *matrix) {
__m256 out;
out = _mm256_mul_ps(_mm256_loadu_ps(&matrix[0]), half_vector[0]);
out = _mm256_fmadd_ps(_mm256_loadu_ps(&matrix[1]), half_vector[1], out);
out = _mm256_fmadd_ps(_mm256_loadu_ps(&matrix[2]), half_vector[2], out);
return out;
}
};
void hopping_term(float *in_out, const float *matrix_3x3, const float *in)
{
So so(in);
for(int i=0; i<3; i++) {
__m256 vector_out1, vector_out2;
__m256 half_vector2 = so.matrix_multiply(&matrix_3x3[3*i]);
vector_out1 = load(&in_out[i*SIMD_WIDTH]);
reconstruct(vector_out1, vector_out2, half_vector2);
store(&in_out[(0+i)*SIMD_WIDTH], vector_out1);
store(&in_out[(3+i)*SIMD_WIDTH], vector_out2);
}
}
这只使用五个AVX寄存器。这是程序集
vmovups ymm3, YMMWORD PTR [rdx]
vmovups ymm2, YMMWORD PTR [rdx+32]
vaddps ymm3, ymm3, YMMWORD PTR [rdx+96]
vmovups ymm0, YMMWORD PTR [rdx+64]
vaddps ymm2, ymm2, YMMWORD PTR [rdx+128]
vaddps ymm0, ymm0, YMMWORD PTR [rdx+160]
vmulps ymm1, ymm3, YMMWORD PTR [rsi]
vfmadd231ps ymm1, ymm2, YMMWORD PTR [rsi+4]
vfmadd231ps ymm1, ymm0, YMMWORD PTR [rsi+8]
vaddps ymm4, ymm1, YMMWORD PTR [rdi]
vsubps ymm1, ymm4, ymm1
vmovups YMMWORD PTR [rdi], ymm4
vmovups YMMWORD PTR [rdi+96], ymm1
vmulps ymm1, ymm3, YMMWORD PTR [rsi+12]
vfmadd231ps ymm1, ymm2, YMMWORD PTR [rsi+16]
vfmadd231ps ymm1, ymm0, YMMWORD PTR [rsi+20]
vaddps ymm4, ymm1, YMMWORD PTR [rdi+32]
vsubps ymm1, ymm4, ymm1
vmovups YMMWORD PTR [rdi+32], ymm4
vmovups YMMWORD PTR [rdi+128], ymm1
vmulps ymm3, ymm3, YMMWORD PTR [rsi+24]
vfmadd132ps ymm2, ymm3, YMMWORD PTR [rsi+28]
vfmadd132ps ymm0, ymm2, YMMWORD PTR [rsi+32]
vaddps ymm1, ymm0, YMMWORD PTR [rdi+64]
vsubps ymm0, ymm1, ymm0
vmovups YMMWORD PTR [rdi+64], ymm1
vmovups YMMWORD PTR [rdi+160], ymm0
答案 1 :(得分:1)
回答我自己的问题:我提出了一个基于模板和宏组合的解决方案。
struct
包含__m512
变量struct
示例:
struct RegisterVector
{
__m512 c0;
__m512 c1;
__m512 c2;
__m512 c3;
__m512 c4;
__m512 c5;
};
template <int vector_index> __m512 &element(RegisterVector &vector)
{
switch(vector_index)
{
case 0: return vector.c0;
case 1: return vector.c1;
case 2: return vector.c2;
case 3: return vector.c3;
case 4: return vector.c4;
case 5: return vector.c5;
}
}
#define LOOP3(loop_variable, start, ...) \
do { \
{ const int loop_variable = start + 0; __VA_ARGS__; } \
{ const int loop_variable = start + 1; __VA_ARGS__; } \
{ const int loop_variable = start + 2; __VA_ARGS__; } \
} while(0)
// simple usage example
LOOP3(c, 0, _mm512_add_ps(element<2*c+0>(vector1), element<2*c+1>(vector2)));
// more complex example: more than one instruction in the loop, cmul and cfmadd itself are inline functions for a complex mul or fmadd
LOOP3(r, 0,
cmul (su3[2*(3*0+r)+0], su3[2*(3*0+r)+1], element<2*0+0>(in), element<2*0+1>(in), element<2*r+0>(out), element<2*r+1>(out));
cfmadd(su3[2*(3*1+r)+0], su3[2*(3*1+r)+1], element<2*1+0>(in), element<2*1+1>(in), element<2*r+0>(out), element<2*r+1>(out));
cfmadd(su3[2*(3*2+r)+0], su3[2*(3*2+r)+1], element<2*2+0>(in), element<2*2+1>(in), element<2*r+0>(out), element<2*r+1>(out)));
正如您所看到的,您可以像往常一样在模板参数中执行整数运算,并且循环索引也可以用于访问其他数组。
LOOP
宏可能仍有待改进,但就我的目的来说已经足够了。
西蒙