如何在C中为两个for循环编写SSE指令?

时间:2013-03-11 21:39:28

标签: c for-loop sse vectorization

使用库'immintrin.h',我能够为简单的for循环和操作编写SSE指令。但是,如何为显示的语句编写SSE指令?

for (int i =0; i<n; i++){
   for (int j=0; j<n; j++) {
     x[i] += a[i] + a[j];
}}

使用_mm_malloc()初始化x和a。内存访问模式可以用作__m128,也可以用作4个字节的展开策略。

我很抱歉,如果我不太清楚,但就像

一样
for (int i = 0; i < vecsize; i+=4) {
        __m128 a  = _mm_load_ps(a+i);
        __m128 x  = _mm_add_ps(x,a);
        _mm_store_ps(x+i, x);
    }

(仅适用于1个循环),我想要类似于上面显示的循环。

编辑:我(EricPostpischil)正在从评论中注入此文本,因为它对问题陈述是重要。作者NeilDA应该对此进行扩展:

  

...在我的程序中'a'总是在变化,因此我希望'x'随之改变。

我已经做到了!我提交了答案..

4 个答案:

答案 0 :(得分:3)

这只是部分答案,但评论过长/详细。

我怀疑你的问题是否写得正确。如图所示,对于每个x[i],它会添加a[i] n 次并将每个a[j]添加一次,因为0≤ j &lt; 名词的。所以它相当于:

sum = 0;
for (j = 0; j < n; ++j)
    sum += a[j];
for (i = 0; i < n; ++i)
    x[i] += n*a[i] + sum;

这将使用比其他可能的阵列操作更简单的SSE代码来实现。当然,如上所述简单地重写它将产生比原始公式更快的代码。

答案 1 :(得分:0)

__m128 *mx = (__m128*)x;
__m128 *ma = (__m128*)a;
__m128 temp_a;

for (int i = 0; i < (n>>2); ++i) {
    for (int j = 0; j < (n>>2); ++j) {
        temp_a = _mm_add_ps(*(ma+i), *(ma+j));
        *mx = _mm_add_ps(*mx, temp_a);
    }
    mx++;
}

当然,您必须确保n是4的倍数,并确保将x初始化为0,以便积累正确。

答案 2 :(得分:0)

变换:

    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            x[i] += a[i] + a[j];

成:

    for (i = 0; i < n; i++)
        x[i] += n*a[i] + sum(a));

请参阅以下代码中的f_sse()

#include <stdio.h>
#include <string.h>
#include <immintrin.h>

enum {
    N = 4,
};

float x[N], a[N] = { .1, .2, .3, .4 }, y[N];

void f(float *x, float *a, int n)
{
    int i, j;
    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            x[i] += a[i] + a[j];
}

float array_sum(float *a, int n)
{
    /* could be vectorized as well */
    int i;
    float s;
    for (s = 0, i = 0; i < n; i++)
        s += a[i];
    return s;
}

void f_sse(float *x, float *a, int n)
{
    int i, l;
    float t;
    __m128 sum_a, n_vec;
    t = array_sum(a, n);
    sum_a = _mm_set1_ps(t);
    n_vec = _mm_set1_ps(n);
    l = n / 4;
    for (i = 0; i < l; i += 4) {
        __m128 ai, xi;
        ai = _mm_load_ps(a + i);
        xi = _mm_load_ps(x + i);
        ai = _mm_mul_ps(ai, n_vec);
        ai = _mm_add_ps(ai, sum_a);
        xi = _mm_add_ps(xi, ai);
        _mm_store_ps(x + i, xi);
    }
}

int main()
{
    int i, r;
    f(x, a, N);
    f_sse(y, a, N);
    r = memcmp(x, y, N);
    if (r == 0)
        return 0;
    printf("x: { ");
    for (i = 0; i < N; i++)
        printf("%f, ", x[i]);
    printf("}\n");
    printf("y: { ");
    for (i = 0; i < N; i++)
        printf("%f, ", y[i]);
    printf("}\n");
    return 3;
}

由于您声明a正在同时更新,因此您无法进行上述循环转换。您需要有意识地决定何时从a获取更新。它永远不会匹配原始的非矢量化版本,因为我们一次只能加载4个浮点数。

在内循环中重新加载a[j]

void f_sse(float *x, float *a, int n)
{
    int i, j, l;
    l = n / 4;
    for (i = 0; i < l; i += 4) {
        __m128 ai, xi;
        ai = _mm_load_ps(a + i);
        xi = _mm_load_ps(x + i);
        for (j = 0; j < n; j++) {
            /* re-load a[j] to get updates */
            xi = _mm_add_ps(xi, _mm_add_ps(ai, _mm_set1_ps(((volatile float *)a)[j])));
        }
        _mm_store_ps(x + i, xi);
    }

}

在内循环中重新加载a[j](a + i)

void f_sse(float *x, float *a, int n)
{
    int i, j, l;
    l = n / 4;
    for (i = 0; i < l; i += 4) {
        __m128 ai, xi;
        xi = _mm_load_ps(x + i);
        for (j = 0; j < n; j++) {
            /* re-load (a + i) to get updates */
            ai = _mm_load_ps(a + i);
            /* re-load a[j] to get updates */
            xi = _mm_add_ps(xi, _mm_add_ps(ai, _mm_set1_ps(((volatile float *)a)[j])));
        }
        _mm_store_ps(x + i, xi);
    }
}

答案 3 :(得分:0)

__m128 x_v, a_v, aj_v;
for (int i = 0; i < (vec_size); i+=4) {
            x_v = _mm_load_ps(x + i);
            a_v = _mm_load_ps(a+i);
        for (int j = 0; j < N; j++) {
            aj_v = _mm_set1_ps(a[j]);
            x_v = _mm_add_ps(x_v, _mm_add_ps(aj_v, a_v));
        }
            _mm_store_ps(x + i, x_v);
    }

我不知道它是否可以进一步改善,但这会将我的时间从0.17秒减少到0.04秒:D 任何言论或改进方法都会很棒!