c++ - SIMD 程序运行缓慢

标签 c++ c performance sse simd

我正开始使用 SIMD 编程,但我现在不知道该做什么。我试图减少运行时间,但它是以另一种方式进行的。

这是我的基本代码: https://codepaste.net/a8ut89

void blurr2(double * u, double * r) {

    int i;
    double dos[2] = { 2.0, 2.0 };

    for (i = 0; i < SIZE - 1; i++) {
        r[i] = u[i] + u[i + 1];
    }
}

模糊2:0.43s

int contarNegativos(double * u) {

    int i;
    int contador = 0;

    for (i = 0; i < SIZE; i++) {
        if (u[i] < 0) {
            contador++;
        }
    }
    return contador;
}

负计数:1.38s

void ord(double * v, double * u, double * r) {

    int i;

    for (i = 0; i < SIZE; i += 2) {
        r[i] = *(__int64*)&(v[i]) | *(__int64*)&(u[i]);
    }
}

顺序:0.33


这是我的 SIMD 代码:

https://codepaste.net/fbg1g5

void blurr2(double * u, double * r) {

    __m128d rp2;
    __m128d rdos;
    __m128d rr;
    int i;
    int sizeAux = SIZE % 2 == 1 ? SIZE : SIZE - 1;
    double dos[2] = { 2.0, 2.0 };

    rdos = *(__m128d*)dos;

    for (i = 0; i < sizeAux; i += 2) {
        rp2 = *(__m128d*)&u[i + 1];
        rr = _mm_add_pd(*(__m128d*)&u[i], rp2);
        *((__m128d*)&r[i]) = _mm_div_pd(rr, rdos);
    }
}

模糊2:0.42s


int contarNegativos(double * u) {

    __m128d rcero;
    __m128d rr;
    int i;
    double cero[2] = { 0.0, 0.0 };
    int contador = 0;

    rcero = *(__m128d*)cero;

    for (i = 0; i < SIZE; i += 2) {
        rr = _mm_cmplt_pd(*(__m128d*)&u[i], rcero);
        if (((__int64 *)&rr)[0]) {
            contador++;
        };
        if (((__int64 *)&rr)[1]) {
            contador++;
        };
    }
    return contador;
}

负计数:1.42s


void ord(double * v, double * u, double * r) {

    __m128d rr;
    int i;

    for (i = 0; i < SIZE; i += 2) {
        *((__m128d*)&r[i]) = _mm_or_pd(*(__m128d*)&v[i], *(__m128d*)&u[i]);
    }
}

阶数:0.35s

**不同的解决方案。


你能解释一下我做错了什么吗?我有点迷路了……

最佳答案

使用 _mm_loadu_pd 而不是指针转换和取消引用 __m128d。您的代码保证在 gcc/clang 上出现段错误,其中 __m128d 被假定为对齐。


blurr2:乘以 0.5 而不是除以 2。它会更快很多。 (我在前一两天用完全相同的代码对一个问题发表了同样的评论,你也是吗?)


negativeCount: _mm_castpd_si128 比较结果为整数,用_mm_sub_epi64累加。 (位模式是全零或全一,即 2 的补码 0/-1)。

#include <immintrin.h>
#include <stdint.h>

static const size_t SIZE = 1024;

uint64_t countNegative(double * u) {
    __m128i counts = _mm_setzero_si128();
    for (size_t i = 0; i < SIZE; i += 2) {
        __m128d cmp = _mm_cmplt_pd(_mm_loadu_pd(&u[i]), _mm_setzero_pd());
        counts = _mm_sub_epi64(counts, _mm_castpd_si128(cmp));
    }

    //return counts[0] + counts[1];  // GNU C only, and less efficient
    // horizontal sum
    __m128i hi64  = _mm_shuffle_epi32(counts, _MM_SHUFFLE(1, 0, 3, 2));
    counts = _mm_add_epi64(counts, hi64);

    uint64_t scalarcount = _mm_cvtsi128_si64(counts);
    return scalarcount;
}

要了解有关高效 vector 水平和的更多信息,请参阅 Fastest way to do horizontal float vector sum on x86 .但第一个规则是在循环之外进行。

( source + asm on the Godbolt compiler explorer )

从 MSVC(我猜你正在使用它,否则你会从 *(__m128d*)foo 得到段错误),内部循环是:

$LL4@countNegat:
    movups   xmm0, XMMWORD PTR [rcx]
    lea      rcx, QWORD PTR [rcx+16]
    cmpltpd xmm0, xmm2
    psubq    xmm1, xmm0
    sub      rax, 1
    jne      SHORT $LL4@countNegat

展开(可能还有两个 vector 累加器)可能会更快,但这相当不错,在 Sandybridge/Haswell 上可能接近每 16 字节 1.25 个时钟。 (5 个融合域 uops 的瓶颈)。

您的版本实际上是在内部循环内部解包为整数!如果您使用的是 MSVC -Ox,它实际上是分支而不是使用无分支比较 + 条件添加。我很惊讶它并不比标量版本慢。

此外,(int64_t *)&rr 违反了严格的别名。 char* 可以为任何东西起别名,但将其他指针转换到 SIMD vector 上并期望它起作用是不安全的。如果是这样,你很幸运。编译器通常会为它或内在函数生成类似的代码,而且对于适当的内在函数通常不会更糟。

关于c++ - SIMD 程序运行缓慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47641724/

相关文章:

c++ - 二分无向图测试: Converting to check nodes with strings instead of integers

c++ - 可移植地识别非标准 C++?

C字符串包含模数

c - 如何在 C 中编写 pragma

c# - 将数组拆分成 block - 有更快的方法吗?

c# - 避免 VBCSCompiler 性能命中 Roslyn 支持的 ASP.NET Razor MVC View ?

Android 性能问题 : Many small apps or one big app?

c++ - fatal error mysql.h :No such file or directory during compilation

c++ - 主动模式和被动模式下的 FTP 服务器端口

c - 使用 const int 定义数组的大小