私はこのようなクラスを持っています:
//Array of Structures
class Unit
{
public:
float v;
float u;
//And similarly many other variables of float type, upto 10-12 of them.
void update()
{
v+=u;
v=v*i*t;
//And many other equations
}
};
Unit 型のオブジェクトの配列を作成します。そして、それらの update を呼び出します。
int NUM_UNITS = 10000;
void ProcessUpdate()
{
Unit *units = new Unit[NUM_UNITS];
for(int i = 0; i < NUM_UNITS; i++)
{
units[i].update();
}
}
処理を高速化し、おそらくループを自動ベクトル化するために、AoS を配列の構造に変換しました。
//Structure of Arrays:
class Unit
{
public:
Unit(int NUM_UNITS)
{
v = new float[NUM_UNITS];
}
float *v;
float *u;
//Mnay other variables
void update()
{
for(int i = 0; i < NUM_UNITS; i++)
{
v[i]+=u[i];
//Many other equations
}
}
};
ループの自動ベクトル化に失敗すると、配列の構造のパフォーマンスが非常に低下します。50 ユニットの場合、SoA の更新は AoS よりわずかに高速ですが、100 ユニット以降では、SoA は AoS よりも遅くなります。300 ユニットでは、SoA はほぼ 2 倍悪くなります。100K ユニットでは、SoA は AoS よりも 4 倍遅くなります。キャッシュは SoA の問題かもしれませんが、パフォーマンスの違いがこれほど大きくなるとは思っていませんでした。cachegrind でプロファイリングすると、両方のアプローチで同様の数のミスが示されます。Unit オブジェクトのサイズは 48 バイトです。L1 キャッシュは 256K、L2 は 1MB、L3 は 8MB です。ここで何が欠けていますか?これは本当にキャッシュの問題ですか?
編集: gcc 4.5.2 を使用しています。コンパイラ オプションは -o3 -msse4 -ftree-vectorize です。
SoA で別の実験を行いました。配列を動的に割り当てる代わりに、コンパイル時に「v」と「u」を割り当てました。100K ユニットの場合、動的に割り当てられた配列を使用する SoA よりも 10 倍高速なパフォーマンスが得られます。ここで何が起こっているのですか?静的メモリと動的に割り当てられたメモリにこのようなパフォーマンスの違いがあるのはなぜですか?