【问题标题】:How can I write SSE instruction in C for two for loops?如何在 C 中为两个 for 循环编写 SSE 指令?
【发布时间】:2013-03-11 21:39:27
【问题描述】:

使用库“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];
}}

x 和 a 是使用 _mm_malloc() 初始化的 float*。内存访问模式可用作 __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' 随之变化。

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

【问题讨论】:

  • 您还没有告诉我们xa 是什么类型。您是否已经知道要如何运行循环?你想要什么样的内存访问模式。
  • x 和 a 是使用 _mm_malloc() 初始化的 float*。使用_m128?如果这有意义吗?抱歉,我是新手。
  • 谢谢。但请不要在评论中添加。对问题进行编辑。我们不想为了理解这个问题而阅读 cmets。而且任何关于矢量化策略的想法都会有所帮助。如果你没有,请说出来。如果您已经决定了策略,但无法实施,请说出来。对 Q 是什么产生巨大影响。
  • 好的,我将在主要问题中编辑上一个和这个。以向量大小 (N/4)*4 展开的策略?
  • “普通 C”是什么意思?您无法将 SSE 与普通 C 进行比较。这没有任何意义。 C是一种高级语言。 SSE 是一个 ISA。您的编译器可以将您的 C 编译为 SSE 指令。它可能已经这样做了,或者可以通过适当的编译器选项轻松地这样做。

标签: c for-loop sse vectorization


【解决方案1】:

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

我怀疑你的问题写得是否正确。如图所示,对于每个x[i],它添加a[i]n次,每个a[j]添加一次,对于0≤jn。所以相当于:

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

这将使用比其他可能的数组操作更简单的 SSE 代码来实现。当然,像上面那样简单地重写它会产生比原始公式更快的代码。

【讨论】:

  • 嗯.. 我可以为 2 个这样的非嵌套循环编写 SSE。可能有用,我试试谢谢
  • 恐怕不行。我正在研究需要不断更新 x 值的动态..
  • @NeilDa:那你没有正确和完整地说明问题。您应该显示一个 short, self-contained compilable example 来演示您要执行的计算。在您所说的问题中,不涉及x 的“不断更新”。
  • @NeilDA 您的首要任务是找出问题所在。目前你不明白你要解决什么问题。这个答案将您的算法从 O(N^2) 转换为 O(N)。没有多少 SSE 可以做到这一点。在尝试优化之前,您需要回到绘图板上并深入了解问题。在你能做到之前,你在浪费每个人的时间,尤其是你自己的时间。
【解决方案2】:
__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,以便累加正确。

【讨论】:

  • 我使用了稍微不同的代码,得到的结果与您的代码显示的结果相同。正在发生的事情是,如果某个 x 应该从 A 到 B,它只会上升到中间点。但是,上面的简单嵌套 for 循环一直采用 x 。不管怎么说,还是要谢谢你。我放弃了过夜。
【解决方案3】:
__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 任何改进它的评论或方法都会很棒!

【讨论】:

  • 与原始非矢量化版本的一个区别是a_v 在内部循环中保持不变。让我担心的另一件事是,这段代码依赖于编译器没有进行花哨的循环转换优化,因为a 正在同时更新这一事实并没有通过原子或易失性负载传达给编译器。为了完整起见,我将编辑我的答案以包括一个在内部循环中重新加载 a_v 的版本。
  • 根据您提出的问题,@Eric 的回答要好得多。所以看起来你回答了一个不同的问题。没有明确说明问题,这个问题有点白痴。
【解决方案4】:

变换:

    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);
    }
}

【讨论】:

  • 谢谢你,但它似乎不适用于我的程序。我不能在这里发布整件事,我知道你也很难理解。 :(
  • 但是,这会动态改变 x 吗?因为在我的程序中,“a”总是在变化,因此我希望“x”也随之变化。
  • @NeilDA,我认为您需要有意识地决定何时在您的算法中接受来自a 的新更新内容。例如。您可以在循环中的每一步重新计算sum_a。正确的答案取决于a 如何同时更新。
  • 你能看看我提供的答案是否正确吗?我已经回答了我自己的问题。
  • 非常感谢斯科特。我用过。我可以看到运行中没有明显的变化,但我想这是一个更有效/更合适的版本。干杯:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-24
  • 1970-01-01
  • 2010-09-08
  • 2022-01-27
  • 1970-01-01
相关资源
最近更新 更多