繁体   English   中英

C++ - 将 std::exp 应用于 std::vector

[英]C++ - Apply std::exp to an std::vector

有没有比简单地更快(从性能角度)的方法

std::vector<double> y;
y.reserve(x.size());
for(size_t i = 0; i < x.size(); ++i)
    y.push_back(std::exp(x[i]));

如果您需要最接近的 ULP 的最大精度,这可能与您将要获得的一样快。

如果您可以接受一些近似误差,那么使用 SIMD 的方法要快得多

令人惊讶的是, push_back有一点开销,因为它实际上并不知道您预留了足够的空间,因此它总是需要检查。 由于此检查可以更改循环迭代之间的控制流,因此push_back阻止编译器进行自动矢量化。

考虑这两个函数,第一个函数使用push_back ,而第二个函数就地修改副本(或移入值):

auto exp1(std::vector<double> const& xs) -> std::vector<double> {
    auto ys = std::vector<double>{};
    ys.reserve(xs.size());
    for(auto x : xs){ ys.push_back(std::exp(x)); }
}

auto exp2(std::vector<double> xs) -> std::vector<double> {
    for(auto & x : xs){ x = std::exp(x); }
    return xs;
}

如果在 GCC 9.1 中编译,我们将查看程序集输出

gcc -std=c++17 -O3 -march=skylake-avx512

这是exp1的内部循环(嵌入了相当多的额外代码,这些代码永远不会被执行,因为您已经reserve d):

.L45:
        add     rbx, 8
        vmovsd  QWORD PTR [r14], xmm0
        add     r14, 8
        cmp     r12, rbx
        je      .L44
.L18:
        vmovsd  xmm0, QWORD PTR [rbx]
        call    exp
        vmovsd  QWORD PTR [rsp], xmm0
        cmp     rbp, r14
        jne     .L45

这是exp2的:

.L53:
        vmovsd  xmm0, QWORD PTR [rbx]
        add     rbx, 8
        call    exp
        vmovsd  QWORD PTR [rbx-8], xmm0
        cmp     rbp, rbx
        jne     .L53

在实践中,它们基本相同,因为exp很复杂,而且 GCC 不知道如何自动对其进行矢量化。 但是,请考虑在内循环中发生更简单的事情的情况:

auto sq1(std::vector<double> const& xs) -> std::vector<double> {
    auto ys = std::vector<double>{};
    ys.reserve(xs.size());
    for(auto x : xs){ ys.push_back(x*x); }
}

auto sq2(std::vector<double> xs) -> std::vector<double> {
    for(auto & x : xs){ x *= x; }
    return xs;
}

这是sq1的内部循环:

.L89:
        vmovsd  QWORD PTR [rsi], xmm0
        add     rbx, 8
        add     rsi, 8
        mov     QWORD PTR [rsp+24], rsi
        cmp     rbp, rbx
        je      .L72
.L75:
        vmovsd  xmm0, QWORD PTR [rbx]
        mov     rsi, QWORD PTR [rsp+24]
        vmulsd  xmm0, xmm0, xmm0
        vmovsd  QWORD PTR [rsp+8], xmm0
        cmp     rsi, QWORD PTR [rsp+32]
        jne     .L89

这是sq2的。 请注意,它使用vmulpdymm寄存器,并且一次跳转 32 个字节而不是一次 8 个字节。

.L11:
        vmovupd ymm0, YMMWORD PTR [rdx]
        add     rdx, 32
        vmulpd  ymm0, ymm0, ymm0
        vmovupd YMMWORD PTR [rdx-32], ymm0
        cmp     rdx, rcx
        jne     .L11

当然,这个内循环片段有点误导:如果std::vector的大小没有被 4 整除,它隐藏了大量用于处理剩余部分的代码。 不过,我的主要观点是是的,你实际上可以做得比reserve + push_back好一点(当我第一次发现时,这让我很惊讶),如果我们不特别处理exp会好得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM