2012-12-14 3 views
9

フィールドへのアクセスは、複数のフィールド

class SimpleClass     struct SimpleStruct 
{         { 
    public int Value0;     public int Value0; 
}         } 

class ComplexClass     struct ComplexStruct 
{         { 
    public int Value0;     public int Value0; 
    public int Value1;     public int Value1; 
    public int Value2;     public int Value2; 
    public int Value3;     public int Value3; 
    public int Value4;     public int Value4; 
    public int Value5;     public int Value5; 
    public int Value6;     public int Value6; 
    public int Value7;     public int Value7; 
    public int Value8;     public int Value8; 
    public int Value9;     public int Value9; 
    public int Value10;     public int Value10; 
    public int Value11;     public int Value11; 
}         } 

は私のマシン上で、次の興味深い結果が得られ、次のタイプに置き換え、次の短いものの、完全なプログラム例T

const long iterations = 1000000000; 

T[] array = new T[1 << 20]; 
for (int i = 0; i < array.Length; i++) 
{ 
    array[i] = new T(); 
} 

Stopwatch sw = Stopwatch.StartNew(); 
for (int i = 0; i < iterations; i++) 
{ 
    array[i % array.Length].Value0 = i; 
} 

Console.WriteLine("{0,-15} {1} {2:n0} iterations/s", 
    typeof(T).Name, sw.Elapsed, iterations * 1000d/sw.ElapsedMilliseconds); 

とタイプのために遅いです(Windows 7 .NET 4.5 32ビット)

 
SimpleClass  00:00:10.4471717 95,721,260 iterations/s 
ComplexClass  00:00:37.8199150 26,441,736 iterations/s 
SimpleStruct  00:00:12.3075100 81,254,571 iterations/s 
ComplexStruct 00:00:32.6140182 30,661,679 iterations/s 

質問1:なぜComplexClassSimpleClassよりずっと遅いのですか?経過時間は、クラス内のフィールドの数に比例して増加するようです。たくさんのフィールドを持つクラスの最初のフィールドに書き込むのは、1つのフィールドしか持たないクラスの最初のフィールドに書き込むべきではないでしょうか?

質問2:なぜComplexStructSimpleStructより遅いのですか? ILコードを見ると、iはアレイに直接書き込まれ、ComplexStructのローカルインスタンスには書き込まれず、アレイにコピーされます。したがって、より多くのフィールドをコピーすることによってオーバーヘッドが発生しないはずです。

ボーナス質問:はなぜComplexClassよりComplexStruct速いのですか?


編集:小さなアレイと更新されたテスト結果、T[] array = new T[1 << 8];

 
SimpleClass  00:00:13.5091446 74,024,724 iterations/s 
ComplexClass  00:00:13.2505217 75,471,698 iterations/s 
SimpleStruct  00:00:14.8397693 67,389,986 iterations/s 
ComplexStruct 00:00:13.4821834 74,172,971 iterations/s 

だから実質SimpleClassComplexClass、及びSimpleStructComplexStructの間のわずかな差との差がありません。しかし、パフォーマンスは、SimpleClassSimpleStructで大幅に減少しました。


編集:T[] array = new T[1 << 16];そして今:

 
SimpleClass  00:00:09.7477715 102,595,670 iterations/s 
ComplexClass  00:00:10.1279081 98,745,927 iterations/s 
SimpleStruct  00:00:12.1539631 82,284,210 iterations/s 
ComplexStruct 00:00:10.5914174 94,419,790 iterations/s 

結果1<<15ためには1<<8のようなもので、1<<17のための結果は1<<20のようなものです。

あなたのCPUは、現時点ではそのキャッシュページにメモリを読み込む:1の質問へ

+0

私は決定的な知識の答えを持つ人を聞くことに興味があります。複雑なバージョンの速度低下に寄与すると思うことの1つは、メモリからCPUキャッシュに移動する必要のあるデータ量の増加です。 – hatchet

+0

私はCarson63000に、単純な構造と複雑な構造の違いは、複雑なタイプのキャッシュの利点が少ないことが原因であることにほとんど同意します。 struct vs classの場合、structは値型ですが、classは参照型なので、クラスとの間で余分な間接化が行われます。 –

+0

もう一つ興味深いのは、SimpleStructがSimpleClassより速くないのはなぜですか?私はそれが最速であると予想していたでしょう。 – hatchet

答えて

7

可能な答え。

データ型が大きいほど、各キャッシュページに必要なオブジェクトの数を減らすことができます。 1つの32ビット値しか書いていないにもかかわらず、CPUキャッシュにページが必要です。小さなオブジェクトでは、次に主メモリから読み込む前に、より多くのループを処理することができます。

2

私はそれを証明する文書はありませんが、それは地域性の問題かもしれないと思います。メモリの面でより複雑なクラスであるため、カーネルが遠くのメモリ領域、ヒープ上またはスタック上にアクセスするのに時間がかかります。しかし、客観的であるためには、あなたの措置の違いが、問題がシステムの欠陥であると本当に高いと言わなければならないと言わざるを得ない。

クラスと構造の違いについては、これも文書化することはできませんが、以前と同じ原則として、スタックがヒープ領域より頻繁にキャッシュされ、キャッシュミスが少なくなる可能性があります。

アクティブな最適化でプログラムを実行しましたか?

編集:私はComplexStructに小テストを行い、パラメーターとしてLayoutKind.ExplicitStructLayoutAttributeを使用した、その後、構造の各フィールドにパラメータとして0とFieldOffsetAttributeを添加しました。時間は大幅に短縮され、私は彼らがSimpleStructのものとほぼ同じだと思う。私はデバッグモードで、デバッガをオンにし、最適化を行わなかった。構造体はそのフィールドを保持していましたが、メモリ内のサイズは削減されていました。

+0

私はデバッガを付けずにリリースビルドをテストしました。 – dtb

+0

両方の構造体が大きすぎてスタックに移動できません。 – evanmcdonnal

+1

@Trispedもし私が彼のコードを正しく理解していれば、スタックは1MBです。アレイのサイズは2^20で、バイト数はMBです。 'SimpleClass'オブジェクトの配列は、スタックのサイズの4倍です。スタックに格納することはできません。構造体が大きすぎてスタックに入ることができない場合は、スタックオーバーフローが発生する可能性があります。 – evanmcdonnal

2

回答1ComplexClass CPUのキャッシュはComplexClassオブジェクトが一度キャッシュに収まるので、少ない固定サイズであるため、SimpleClassより遅いです。基本的には、メモリからフェッチするのに必要な時間のために増加が見られます。これは、キャッシュに入り、RAMの速度を落とすと、より明確になります(extream)。

回答2:回答1と同じです。

ボーナス:構造体の配列は構造体の連続ブロックであり、配列ポインタによってのみ参照されます。クラスの配列は、クラスインスタンスへの参照の連続ブロックであり、配列ポインタによって参照されます。クラスはヒープ上に作成されるので(基本的にどこにでも余裕があります)、連続した順序付けされたブロックにはありません。これはスペースを最適化するのに最適ですが、CPUキャッシングには悪いです。結果として、大規模なクラスへのポインタの大きな配列でより多くのCPUキャッシュミスが配列を順番に反復するとき、構造体の配列の順番に反復があります。

SimpleStructSimpleClassその後、遅い理由:私は(どこかに私が言われている76の咬傷の周りの)構造体にするオーバーヘッドの量があります理解して何から。私は何が何であるか、なぜそれがあるのか​​は分かりませんが、ネイティブコード(C++コンパイル済み)を使ってこの同じテストを実行すると、SimpleStruct配列の方が性能が向上することがわかります。それはちょうど推測です。


いずれにしても、これは興味深いものです。私は今夜​​それを試してみるつもりです。結果を掲載します。完全なコードを取得することは可能ですか?

+0

私はこれについてより多くの結果を見ることを楽しみにしています。質問のコードは私が持っているものすべてです。それぞれのタイプに対して4回重複しています。 – dtb

+0

私はテストを実行し、SimpleClassよりも少し遅いSimpleStructと同じ結果を見ました。ガベージコレクタからメモリにデータを割り当てました。 SimpleStructは要素あたり4バイトを消費したので、構造体の配列にオーバーヘッドはありません。SimpleClassは要素あたり16バイト(64ビットシステム上)を使用しました。これは、おそらく配列の参照のための8バイト+オブジェクトのint値+ 4バイト+オブジェクトヘッダーの4バイトです。 – hatchet

1

モジュラスを削除するためにベンチマークを少し変更しました。これはおそらく時間の大部分を占めるでしょう。あなたはモジュラス計算ではなくフィールドアクセス時間を比較しているようです。

const long iterations = 1000; 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    //long sMem = GC.GetTotalMemory(true); 
    ComplexStruct[] array = new ComplexStruct[1 << 20]; 
    for (int i = 0; i < array.Length; i++) { 
     array[i] = new ComplexStruct(); 
    } 
    //long eMem = GC.GetTotalMemory(true); 
    //Console.WriteLine("memDiff=" + (eMem - sMem)); 
    //Console.WriteLine("mem/elem=" + ((eMem - sMem)/array.Length)); 
    Stopwatch sw = Stopwatch.StartNew(); 
    for (int k = 0; k < iterations; k++) { 
     for (int i = 0; i < array.Length; i++) { 
      array[i].Value0 = i; 
     } 
    } 
    Console.WriteLine("{0,-15} {1} {2:n0} iterations/s", 
     typeof(ComplexStruct).Name, sw.Elapsed, (iterations * array.Length) * 1000d/sw.ElapsedMilliseconds); 

(各テストのタイプを置き換えます)。

SimpleClass 357.1 
SimpleStruct 411.5 
ComplexClass 132.9 
ComplexStruct 159.1 

これらの数が、私は限り構造体のバージョンVSクラスとして期待されるであろうものに近い:私は(内部ループの割り当て/秒の単位:百万ドル)、これらの結果を取得します。私は複雑なバージョンの遅い時間は、より大きなオブジェクト/構造体のCPUキャッシュ効果によって説明されると思います。コメントアウトされたメモリ測定コードを使用すると、構造バージョンが全体的なメモリ消費量が少ないことがわかります。メモリ測定コードが構造体バージョンとクラスバージョンの相対的な時間に影響を与えたことに気が付いた後、私はGC.Collectを追加しました。

+0

私のコードは、私が最適化しようとしている大きなプログラムのスニペットです。モジュラスはそこに不可欠な部分です。しかし、これを試していただきありがとうございます - それはもう一度地方が重要であることを示しています。 – dtb

関連する問題