3

通常、特定のシナリオが構造体またはクラスに適しているかどうかを自問する必要はありません。率直に言って、この場合、クラスの方法に進む前にその質問をしませんでした。最適化を行っている今、状況は少し混乱しています。

何百万もの Base10 桁を含む非常に大きな数を処理する数値処理アプリケーションを作成しています。数値は 2D 空間の (x,y) 座標です。Cellメイン アルゴリズムはかなりシーケンシャルであり、任意の時点でメモリ内にあるクラス (以下にリスト) のインスタンスは 200以下です。クラスの各インスタンスは約 5MB のメモリを占有するため、アプリケーションの合計ピーク メモリは 1GB を超えません。完成した製品は、20 GB の RAM を備えた 16 コアのマシンで実行され、他のアプリケーションがリソースを占有することはありません。

クラスは次のとおりです。

// Inheritance is convenient but not absolutely necessary here.
public sealed class Cell: CellBase
{
    // Will contain numbers with millions of digits (512KB on average).
    public System.Numerics.BigInteger X = 0;
    // Will contain numbers with millions of digits (512KB on average).
    public System.Numerics.BigInteger Y = 0;

    public double XLogD = 0D;
    // Size of the array is roughly Base2Log(this.X).
    public byte [] XBytes = null;

    public double YLogD = 0D;
    // Size of the array is roughly Base2Log(this.Y).
    public byte [] YBytes = null;

    // Tons of other properties for scientific calculations on X and Y.
    // NOTE: 90% of the other fields and properties are structs (similar to BigInteger).

    public Cell (System.Numerics.BigInteger x, System.Numerics.BigInteger y)
    {
        this.X = x;
        this.XLogD = System.Numerics.BigInteger.Log(x, 2);
        this.XBytes = x.ToByteArray();

        this.Y = y;
        this.YLogD = System.Numerics.BigInteger.Log(y, 2);
        this.YBytes = y.ToByteArray();
    }
}

構造体の代わりにクラスを使用することにしたのは、単純に、より自然に「感じた」からです。フィールド、メソッド、およびメモリの数はすべて、構造体ではなくクラスを本能的に指していました。さらに、基礎となるプライマリ オブジェクトはそれ自体が構造体である BigInteger のインスタンスであるため、一時的な代入呼び出しのオーバーヘッドがどの程度になるかを考慮して、それを正当化しました。

問題は、速度効率がこの場合の最終目標であることを考慮して、ここで賢明に選択したかどうかです。

役立つ場合に備えて、アルゴリズムについて少し説明します。各反復で:

  1. ソートは 200 個のインスタンスすべてに対して 1 回実行されました。実行時間の 20%。
  2. 対象の隣接 (x,y) 座標を計算します。実行時間の 60%。
  3. 上記のポイント 2 の並列/スレッド化のオーバーヘッド。実行時間の 10%。
  4. 分岐オーバーヘッド。実行時間の 10%。
  5. 最も高価な関数: BigInteger.ToByteArray() (実装)
4

5 に答える 5

4

これは、次のような多くの理由から、クラスとしてより適しています。

  • 単一の値を論理的に表すものではありません
  • 16バイトより大きい
  • 変更可能です

詳細については、クラスと構造の選択を参照してください。

さらに、私はそれが与えられたクラスにより適していることも提案します:

  • 参照型(配列)が含まれています。クラスを含む構造体が優れた設計アイデアになることはめったにありません。

ただし、これは、あなたがしていることを考えると、特に当てはまります。を使用する場合struct、並べ替えには、参照のコピーだけでなく、構造体全体のコピーが必要になります。すべてのデータをコピーすることになるため、メソッド呼び出し(refによって渡されない限り)も大きなオーバーヘッドが発生します。

List<Cell>リストへのすべてのアクセスがメモリにアクセスするため、構造体の任意の配列の境界チェック(つまり、構造体が類似の配列に保持されている場合)により、誤った共有が発生するため、コレクション内のアイテムの並列化にも大きなオーバーヘッドが発生する可能性があります。リストの先頭にあります。

これをクラスとして残すことをお勧めします。さらに、フィールドをプロパティに移動し、クラスをできるだけ不変にすることをお勧めします。これにより、デザインをクリーンに保つことができ、マルチスレッド時に問題が発生する可能性が低くなります。

于 2012-08-24T22:34:30.140 に答える
2

あなたが書いたものに基づいて判断するのは難しいですが(Cellたとえば、typeの値をコピーする頻度はわかりません)、ここでは正しいアプローチであることが強く期待されます。class

クラス内のメソッドの数は関係ありませんが、フィールドがたくさんある場合は、別のメソッド(など)に値を渡すたびに、それらすべてのフィールドをコピーすることの影響を考慮する必要があります。

基本的に、そもそも値型のようには感じられませんが、パフォーマンスが特に重要である場合、哲学的側面はあなたにとってそれほど興味深いものではないかもしれないことを私は理解しています。

そうです、あなたは正しい決定を下したと思います。現時点で他に何も信じる理由はありませんが、もちろん、決定を簡単に変更して構造体としてテストできれば、推測よりも優れています。パフォーマンスを正確に予測することは非常に困難です。

于 2012-08-24T22:34:49.313 に答える
1

クラスにはメモリの大部分を消費する配列が含まれており、クラス自体のメモリ消費の周りにセルインスタンスが200個しかないため、問題にはなりません。クラスがより自然に感じたのは正しかったです。それは確かに正しい選択です。私の推測では、XByte[]とXYBytes[]の比較は、ソート時間を制限します。それはすべて、アレイの大きさと比較の実行方法によって異なります。

于 2012-08-24T22:34:07.807 に答える
1

パフォーマンスの問題を無視して、それらに取り組みましょう。

構造体は ValueTypes であり、ValueTypes は値の型です。整数とDateTimeは値型であり、比較に適しています。がどのように と同じであるか同じではないか、またはあるものが別の とどのように同じであるか1同じではないかについて話すことは意味がありません。用途によって意味が異なる場合がありますが、 1 == 1 および 1 != 2 であり、 2010-02-03T12:45:23.321Z == 2010-02-03T12:45:23.321Z および 2010-02-03T12 です。 :45:23.321Z != 2931-03-05T09:21:29.43Z は整数と日時の性質に固有のものであり、それがそれらを値型にするものです。12010-02-03T12:45:23.321Z2010-02-03T12:45:23.321Z

これが最も純粋な考え方です。上記と一致する場合は値型、一致しない場合は参照型です。他には何も入りません。

拡張 1: X が X を持つことができる場合、それは参照型でなければなりません。これが上で述べたことから論理的に続くかどうかは議論の余地がありますが、実際には、それ自体の別のインスタンスをメンバーとして (直接的または間接的に) 持つ構造体を持つことはできません。

拡張 2: ミュータブルな構造体に起因する問題は上記に起因すると言う人もいれば、そうでない人もいます。繰り返しになりますが、この問題についてどう考えても、実際的な困難があります。変更可能な構造体は、いくつかの場合に役立ちますが、当然のことながら、最適化として public のケースではなく private のケースに制限する必要があるほどの混乱を引き起こします。

ここにパフォーマンスビットが来ます...

値型と参照型は、速度、メモリの使用、およびメモリの使用がガベージ コレクションに影響を与える方法に影響を与えるさまざまなケースで異なる特性を持ち、パフォーマンスに関する限り、それぞれに異なる長所と短所を示します。それにどれだけ注意を払うかは、そのレベルにどれだけ降りる必要があるかにかかっています。構造体とクラスのどちらかを決定する際に上記のルールに従えば、両者の相違点のバランスが取れて勝つ傾向があると言う価値があるので、それを超えてこれについて考え始めると、少なくとも最適化の領域に接しています.

最適化レベル 1。

値型インスタンスがインスタンスごとに 16 バイトを超える場合は、おそらく参照する必要があります。これは、最適化によるものではなく、「自然な」違いであるとさえ言われることがあります。「16バイト以下」を伴う「値の型」には厳密には何もありませんが、そのようにバランスをとる傾向があります。

単純な「16 バイト」ルールから離れて、小さいほどコピーが高速になり、逆に、20 バイトのインスタンスに曲げることは、200 バイトのインスタンスに曲げるよりも影響が少なくなります。

たくさんの箱と箱を開ける必要がありますか? ジェネリックスの導入以来、1.0 と 1.1 でボックス化およびアンボックス化する多くのケースを回避することができたので、これは以前ほど大きな問題ではありませんが、そうするとパフォーマンスが低下します。

最適化レベル 2。

値型をスタックに配置したり、(それらへの参照ではなく) 配列に直接配置したり、構造体またはクラスの直接フィールド (これらへの参照ではなく) にしたりできるという事実により、値型およびそれらの値にアクセスできます。フィールドを高速化します。

それらの配列を作成しようとしていてすべてゼロの値が有用な出発点である場合は、参照型と同様に null の配列を取得します。これにより、構造体を高速化できます。

編集:上記から拡張されたもの。配列をすばやく反復処理する場合、参照に従うことを促進する直接アクセスと同様に、いくつかのインスタンスを一度に CPU キャッシュにロードします。 time (現在の x86-32 または x86-64/amd では 64 バイト相当、ia-64 では 128 バイト相当)。問題になるにはかなりタイトなループでなければなりませんが、そうなる場合もあります。

ほとんどの場合、「パフォーマンスのためにクラスではなく構造体を選びました」というのは、最初のポイント、または最初のポイントと 2 番目のポイントの組み合わせのいずれかに帰着します。

最適化レベル 3。

関心のある値の一部が互いに重複しており、それらのサイズが大きい場合、不変インスタンス (または、次の操作を開始すると決して変更されない可変インスタンス) を使用すると、意図的にたとえば、サイズが 2kiB の 20 個の複製オブジェクトは実際には同じオブジェクトであり、その場合は 26kiB を節約できるため、多くのメモリを節約できるように異なる参照にエイリアスを設定します。また、身元をショートカットできるケースが増えるため、比較を高速化することもできます。これは、参照型でのみ実行できます。

最適化レベル 4。

配列を持つ構造体は、含まれている配列のエイリアスを作成し、上記の手法を内部的に使用して、その点をバランスさせることができますが、多少複雑になります。

最適化レベル X。

これらの長所と短所についてどれだけ考えて特定の答えにたどり着いたとしても、実際に結果を測定すると別の結果が得られるのであれば、問題ではありません。長所と短所の両方があるため、これを誤解する可能性は常にあります。

1から4まで、値型と参照型の違いなどを考えると、最適化は別として、クラスを選ぶべきだと思います。

レベル XI について考えてみると、実際にテストした結果、私が間違っていることが証明されたとしても驚かないでしょう。最善の策は、クラスから構造体への変更が困難な場合 (エイリアシングや null 値の可能性を多用する場合) は、そうすることは負けであると確信できることです。難しくない場合は、測定するだけです。何かを 10,000 回実行する実際の実行を含むテストを測定することを強くお勧めします。実際に別の操作を 20 倍頻繁に実行した場合、特定の操作を数秒以内に 10,000 回実行できるかどうかは誰が気にしますか?

于 2012-08-24T23:29:01.617 に答える
0

構造体は、(1) 構造体の状態がその内容ではなく配列の ID に依存する場合 ( の場合のようにArraySegment)、または (2) 配列への参照がない場合にのみ、配列型フィールドを安全に含むことができます。変更しようとする可能性のあるものによって保持されることはありません (通常、これは、配列フィールドがプライベートになり、構造体自体が配列を作成し、参照を格納する前に、配列に対して行われるすべての変更を実行することを意味します分野)。

私はここにいる他の人々よりもはるかに一般的に構造体を使用することを提唱していますが、データ ストレージに 2 つの配列型フィールドがあるという事実は、構造体の使用に対する強力な議論のように思えます。

于 2012-08-25T22:34:46.513 に答える