1

私はベクトル化について学ぼうとしており、車輪を再投資するのではなく、Agner Fog のベクトル ライブラリを使用しています。

ここに私のオリジナルの C++/STL コードがあります

#include <vector>
#include <vectorclass.h>   
template<typename T>
double mean_v1(T begin,T end) {
        float mean = 0;
        std::for_each(begin,end,[&mean](const double& d) { mean+=d; });

    return mean / std::distance(begin,end);
}

double mean_v2(T begin,T end) {
    float mean = 0;
    const int distance = std::distance(begin,end); // This is expensive
    const int loop = ( distance >> 2)+1; // divide by 4
    const int partial = distance & 2; // remainder 4
    Vec4d vec;
    for(int i = 0; i < loop;++i) {
        if(i == (loop-1)) {
            vec.load_partial(partial,&*begin);
            mean = horizontal_add(vec);
        }
        else  {
            vec.load(&*begin);
            mean = horizontal_add(vec);
            begin+=4; // This is expensive
        }
    }
    return mean / distance;
}

int main(int argc,char**argv) {
    using namespace boost::assign;
    std::vector<float> numbers;
    // Note 13 numbers, which won't fit into a sse register perfectly
    numbers+=39.57,39.57,39.604,39.58,39.61,31.669,31.669,31.669,31.65,32.09,33.54,32.46,33.45;

    const float mean1 = mean_v1(numbers.begin(),numbers.end());
    const float mean2 = mean_v2(numbers.begin(),numbers.end());


    return 0;
}

v1 と v2 の両方が正しく機能し、どちらもほぼ同じ時間がかかります。ただし、プロファイリングすると std::distance() が表示され、反復子を移動するのに合計時間のほぼ 45% がかかります。ベクトルの追加はわずか 0.8% で、v1 よりも大幅に高速です。

Web を検索すると、すべての例が、SSE レジスターに正確に適合する完全な数の値を扱っているようです。たとえば、この例では、ループの設定に計算よりもはるかに長い時間がかかっています。

このシナリオに対処する方法については、ベスト プラクティスやアイデアが必要だと思います。

mean() のインターフェースを変更して float[] を使用することはできないが、反復子を使用する必要があると仮定します

4

1 に答える 1

3

float と double を不必要に混在させています。特に、アキュムレータを 2 倍にしないと、精度が完全に破壊され、より大きなシリーズでは満足のいくものにはなりません。

演算は非常に軽量であるため、ここでパフォーマンスを破壊しているのはメモリ アクセスである可能性が高く、メモリ キャッシュ ラインとその動作を読み取ってください。基本的に、ここで行う必要があるのは事前に調査することです。一部のプロセッサには、キャッシュにデータを取り込むための明示的な指示があります。それ以外の場合は、メモリの場所で事前にロードを実行できます。ループ内に別のレベルのネストを作成し、数回の反復で取得できることがわかっているデータを定期的にキャッシュに準備します。

パフォーマンスを最大化するために人々が行うことは、実際のデータ レイアウトの設計に多くの時間を費やすことです。データに対して中間変換を行う必要はありません。したがって、人々が行うことは、アライメントされたメモリを割り当て (ほとんどの SIMD 命令セットは、アライメントされていないメモリへの読み取り/書き込みに対して重大なペナルティを要求または課す)、命令セットに適合するようにデータを集約しようとします。実際、多くの場合、命令セットがサポートするレジスタ サイズに合わせてデータをパディングすると効果的です。したがって、3 次元のベクトルを処理する場合、使用されていない余分な要素をパディングすると、ほとんどの場合、大きな効果が得られます。

于 2013-07-02T11:41:11.417 に答える