web-dev-qa-db-ja.com

std :: vectorを無効にする最も速い方法

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よりも高速なこのような関数が存在する場合は、喜んでお知らせします。

26
enanone
#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 Assemblyを生成します

.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++コードを使用しないでください。

28
ColdCat

幸いなことにstd::vectorのデータは連続しているため、ベクトル組み込み関数を使用して-1を乗算できます(アライメントされていないロード/ストアとオーバーフローの特別な処理を使用)。または、インテルのIPPライブラリのippsMulC_64f/ippsMulC_64f_Iを使用します(より速く何かを書くのに苦労します)。プラットフォームで利用可能な最大のベクトルレジスタを使用します。 https://software.intel .com/en-us/ipp-dev-reference-mulc

更新:コメントの混乱を解消するために、Intel IPPのフルバージョンは無料で(サポートに料金を支払うことができます)、Linux、Windows、およびmacOSに搭載されています。

17
keith

他の人が述べたように、それはあなたのユースケースに完全に依存します。おそらく最も簡単な方法は次のようなものでしょう:

 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の設定に変換する価値があるかどうかは、既に述べたように、ユースケースに依存します(実際、これが測定可能なメリットをもたらすユースケースがあるとは思いません)。

最初に、算術型ベクトル用の汎用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);
}

not演算子を次のように実装するように誘惑されます:

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

(- v)は、要素を無効にし、一時的なものを必要とせずに変更されたベクトルを返します。効果的に設定することにより、数学的な規則を破ります:v = - v;それが目標であれば、negate関数を使用します。期待されるオペレーター評価を壊さないでください!


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は同様のループを生成しますが、インデックス付きアドレス指定を要求しているようです。

3
Brett Hale

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