代码之家  ›  专栏  ›  技术社区  ›  enanone

求反std::矢量的最快方法

  •  27
  • enanone  · 技术社区  · 7 年前

    假设我有一个双精度的std::向量,即

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

    哪里 N 如此之大以至于性能至关重要。现在假设 MyVec 否定 向量的版本:我需要 -MyVec .

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

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

    但是,真的,我不知道这是合理的还是我这边的超级天真。

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

    5 回复  |  直到 7 年前
        1
  •  28
  •   ColdCat Noor    7 年前
    #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生成nice组件

    .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++代码,它几乎每次都能工作。

        2
  •  16
  •   keith    7 年前

    幸运的是 std::vector 是连续的,因此可以使用向量内部函数(使用未对齐的加载/存储和可能溢出的特殊处理)乘以-1。或使用 ippsMulC_64f / ippsMulC_64f_I 从intel的IPP库(您很难写得更快),它将使用平台上可用的最大矢量寄存器: https://software.intel.com/en-us/ipp-dev-reference-mulc

    更新:为了澄清评论中的一些困惑,“英特尔IPP”的完整版本是免费的(尽管您可以支付支持费用),并在Linux、Windows和macOS上提供。

        3
  •  3
  •   porges    7 年前

     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 如前所述,这取决于您的用例(实际上我怀疑是否存在这样的用例会带来任何可测量的好处)。

        4
  •  3
  •   Brett Hale    7 年前

    首先,泛型 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; 如果这是你的目标,那么使用 作用不要破坏预期的操作员评估!


    在启用avx512的情况下,clang生成了这个循环,在每次迭代前/后长度处理之间抵消了令人印象深刻的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生成了类似的循环,但似乎坚持索引寻址。

        5
  •  0
  •   Surt    7 年前

    用于每个

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