c – 临时/“不可寻址”的固定大小数组?

标题缺乏一个更好的名称,我不确定我是否能够清楚地解释自己.我正在寻找一种通过索引访问“数据类型”的方法,但不强制编译器将其保存在数组中.在编写基于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):它似乎被迫使其“可寻址”,将其保留在堆栈中,从而产生大量负载,存储我不需要的说明.据我所知,这是允许带有vector1或vector2的指针算术.

我希望编译器只将所有内容保存在寄存器中.我知道这是可能的:如果我编写没有__m512数组的代码但是编写了许多单独的__m512变量,编译器会生成更好的代码.

解决方案:

我(不成功)尝试使用类似下面的类(我曾希望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);
}

最佳答案 我最近使用模板元编程做了一些非常类似的事情.在下面的代码中,我认为您只需要将_mm256替换为_mm512并将SIMD_WIDTH更改为16.这应该将循环展开24次.

#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.我检查了结果,以确保没有错误.在我的一些测试中,我能够提高效率.

编辑:这是一个基于OPs更新问题的解决方案.我在传递中使用数组用于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
点赞