2

このクラスを受けて、

アプローチ 1:

typedef float v4sf __attribute__ (vector_size(16))

class Unit
{
    public:
    Unit(int num)
    {
        u = new float[num];
        v = new float[num];
    }
    void update()
    {
        for(int i =0 ; i < num; i+=4)
        {
            *(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
            //many other equations
        }
    }
    float*u,*v,*t; //and many other variables
}

アプローチ 2:

アプローチ 1 と同じです。ただし、アプローチ 2 ではv、 、u、およびその他すべての変数が、配置を使用して、ヒープに事前に割り当てられた大きなチャンクに割り当てられますnew

typedef float v4sf __attribute__ (vector_size(16))

class Unit
{
    public:
    Unit(int num)
    {
        buffer = new char[num*sizeof(*u) + sizeof(*v)  /*..and so on for other variables..*/]
        u = new(buffer) float[num];
        v = new(buffer+sizeof(float)*num) float[num];
        //And so on for other variables
    }
    void update()
    {
        for(int i =0 ; i < num; i+=4)
        {
            *(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
            //many other equations
        }
    }
    char* buffer;
    float*u,*v,*t; //and many other variables
}

ただし、アプローチ 2 は 2 倍高速です。何故ですか?

約 12 個の float 変数があり、num は 500K です。update() が呼び出され1kます。速度は、メモリ割り当てには影響しません。私はこのように速度を測定します:

double start = getTime();
for( int i = 0; i < 1000; i++)
{
   unit->update();
}
double end = getTime();
cout<<end - start;

そして、これはアプローチ 2 で約 2 倍速くなります。

コンパイラ オプション:gcc -msse4 -o3 -ftree-vectorize.

L1 キャッシュは 256K、RAM は 8GB、ページサイズは 4K です。

編集: アプローチ 2 で変数を割り当てる際の間違いを修正しました。すべての変数は、異なるセクションに正しく割り当てられます。プロセッサは Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz

編集: ここにソースを追加しました - Source。アプローチ 1) は 69.58 秒、アプローチ 2) は 46.74 秒です。2 倍高速ではありませんが、それでも高速です。

4

4 に答える 4

4

おそらく、「アプローチ 2」にはバグがあるためです。すべての変数uvtがメモリ内のまったく同じ場所に配置されています (配置 new に同じアドレスを渡しています)。

編集:そして今はそうではありません... ;)

プロファイリングせずに推測するのは難しいですが、デフォルトのアロケーターに関連している可能性があります。最初のアプローチで、変数ごとに new を個別に呼び出す場合、それらの変数に互いに近いアドレスが割り当てられるという保証はありません。一方、2 番目のアプローチでは、それらが互いにできるだけ近くにあることを確認します。これにより、キャッシュの使用率が最大になり、キャッシュ ミスが制限されます。

于 2012-07-24T18:27:16.927 に答える
1

タイミングを分解して、コンストラクターのどの部分と のどの部分があるかを確認すると便利ですupdate

変更されていないためupdate、そのタイミングに影響を与える唯一のものは、データに対するキャッシュの影響です。これは、2 倍の違いを説明できる以上のものです。

于 2012-07-24T18:40:09.003 に答える
0

通常の new は実際には割り当て + 構築ですが、placement new は単なる構築です。
したがって、当然、割り当て + 2 の構築は、割り当て + 構築 + 割り当て + 構築よりも高速です。
さらに、整数型の構築は nop なので、あなたの場合は 2 つの割り当てと 1 つの割り当てです。

于 2012-07-24T18:25:47.890 に答える
0

アプローチ 2 では、u と v のアドレスが呼び出し間で変化しないことをコンパイラーが認識できたため、 for ループの方程式で使用されるポインターの一部をレジスターに保持したと仮定します。

于 2012-07-24T18:26:43.207 に答える