否定std :: vector的最快方法

时间:2017-11-15 14:45:07

标签: c++ optimization stdvector lapack blas

假设我有一个double的std :: vector,即

std::vector<double> MyVec(N);

N如此之大,以至于表现很重要。现在假设MyVec是一个非平凡的向量(即它不是零向量,但已被某些例程修改)。现在,我需要向量的否定版本:我需要-MyVec

到目前为止,我一直在通过

实现它
std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());

但是,实际上,我不知道这是否合情合理,或者只是我身边的超级天真。

我做得对吗?或者std :: transform在这种情况下只是一个超慢的例程?

PS:我一直在使用BLAS和LAPACK库,但我没有发现任何符合此特殊需求的内容。但是,如果在BLAS / LAPACK中存在比std :: transform更快的函数,我很高兴知道。

5 个答案:

答案 0 :(得分:28)

#include <vector>
#include <algorithm>
#include <functional> 
void check()
{
    std::vector<double> MyVec(255);
    std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());
}

https://godbolt.org/上带有copile选项-O3的代码生成了精美的程序集

.L3:
[...]
  cmp r8, 254
  je .L4
  movsd xmm0, QWORD PTR [rdi+2032]
  xorpd xmm0, XMMWORD PTR .LC0[rip]
  movsd QWORD PTR [rdi+2032], xmm0
.L4:

很难想象更快。你的代码已经很完美了,不要试图超越编译器,并且几乎每次都使用干净的C ++代码。

答案 1 :(得分:17)

幸运的是,std::vector中的数据是连续的,因此您可以使用向量内在函数(使用未对齐的加载/存储和特殊处理可能的溢出)乘以-1。或者使用来自英特尔IPP库的ippsMulC_64f / ippsMulC_64f_I(您将努力更快地编写内容),这将使用您平台可用的最大向量寄存器:https://software.intel.com/en-us/ipp-dev-reference-mulc

更新:为了澄清评论中的一些混淆,英特尔IPP的完整版本是免费的(虽然您可以支付费用),并且可以在Linux,Windows和macOS上使用。

答案 2 :(得分:3)

正如其他人所说,它完全取决于您的使用案例。可能最简单的方法是这样的:

 struct MyNegatingVect {
     MyVect data;
     bool negated = false;
     void negate() { negated = !negated; }
     // ... setter and getter need indirection ...
     // ..for example
     MyVect::data_type at(size_t index) { return negated ? - data.at(index) : data.at(index);
 };

对于每个单一访问的额外间接是否值得将否定转换为设置单个bool取决于已经提到过的用例(实际上我怀疑有一个用例会带来任何可衡量的利益)。

答案 3 :(得分:3)

首先,算术类型向量的通用negate函数作为示例:

#include <type_traits>
#include <vector>

...

template <typename arithmetic_type> std::vector<arithmetic_type> &
negate (std::vector<arithmetic_type> & v)
{
    static_assert(std::is_arithmetic<arithmetic_type>::value,
        "negate: not an arithmetic type vector");

    for (auto & vi : v) vi = - vi;

    // note: anticipate that a range-based for may be more amenable
    // to loop-unrolling, vectorization, etc., due to fewer compiler
    // template transforms, and contiguous memory / stride.

    // in theory, std::transform may generate the same code, despite
    // being less concise. very large vectors *may* possibly benefit
    // from C++17's 'std::execution::par_unseq' policy?

    return v;
}

您对规范一元operator -函数的期望需要以下列形式创建临时函数:

std::vector<double> operator - (const std::vector<double> & v)
{
    auto ret (v); return negate(ret);
}

或者一般地说:

template <typename arithmetic_type> std::vector<arithmetic_type>
operator - (const std::vector<arithmetic_type> & v)
{
    auto ret (v); return negate(ret);
}

是否想要将运算符实现为:

template <typename arithmetic_type> std::vector<arithmetic_type> &
operator - (std::vector<arithmetic_type> & v)
{
    return negate(v);
}

虽然(- v)将否定元素并返回修改后的向量而不需要临时性,但它会通过有效设置来打破数学约定:v = - v;如果这是您的目标,那么请使用negate功能。不要打破预期的运营商评估!

clang,启用avx512,生成此循环,在每次迭代中取消令人印象深刻的64次双重 - 在前/后长度处理之间:

        vpbroadcastq    LCPI0_0(%rip), %zmm0
        .p2align        4, 0x90
LBB0_21:
        vpxorq  -448(%rsi), %zmm0, %zmm1
        vpxorq  -384(%rsi), %zmm0, %zmm2
        vpxorq  -320(%rsi), %zmm0, %zmm3
        vpxorq  -256(%rsi), %zmm0, %zmm4
        vmovdqu64       %zmm1, -448(%rsi)
        vmovdqu64       %zmm2, -384(%rsi)
        vmovdqu64       %zmm3, -320(%rsi)
        vmovdqu64       %zmm4, -256(%rsi)
        vpxorq  -192(%rsi), %zmm0, %zmm1
        vpxorq  -128(%rsi), %zmm0, %zmm2
        vpxorq  -64(%rsi), %zmm0, %zmm3
        vpxorq  (%rsi), %zmm0, %zmm4
        vmovdqu64       %zmm1, -192(%rsi)
        vmovdqu64       %zmm2, -128(%rsi)
        vmovdqu64       %zmm3, -64(%rsi)
        vmovdqu64       %zmm4, (%rsi)
        addq    $512, %rsi              ## imm = 0x200
        addq    $-64, %rdx
        jne     LBB0_21

gcc-7.2.0生成一个类似的循环,但似乎坚持索引寻址。

答案 4 :(得分:0)

使用for_each

std::for_each(MyVec.begin(), MyVec.end(), [](double& val) { val = -val });

或C ++ 17并行

std::for_each(std::execution::par_unseq, MyVec.begin(), MyVec.end(), [](double& val) { val = -val });