web-dev-qa-db-ja.com

構造体とクラス

コードで100,000個のオブジェクトを作成しようとしています。それらは小さなもので、プロパティは2つまたは3つしかありません。それらを一般的なリストに入れ、それらがループしたら、値aをチェックし、値bを更新します。

これらのオブジェクトをクラスまたは構造体として作成する方が速い/良いですか?

[〜#〜] edit [〜#〜]

a。プロパティは値型です(と思う文字列を除く)

b。検証メソッドがあるかもしれません(まだわかりません)

編集2

私は疑問に思っていました:ヒープ上のオブジェクトとスタックはガベージコレクターによって同等に処理されますか、それとも異なる動作をしますか?

87
Michel

これらのオブジェクトをクラスまたは構造体として作成するのはfasterですか?

あなたはその質問に対する答えを決定できる唯一の人です。 measure意味のある、ユーザーに焦点を当てた関連するパフォーマンスメトリックを両方試してみてください。そうすれば、変更が関連するシナリオで実際のユーザーに意味のある効果をもたらすかどうかがわかります。

構造体は、(スタック上にあるためではなくより小さいであり、より簡単に圧縮されるため)ヒープメモリを消費しません。ただし、参照コピーよりもコピーに時間がかかります。メモリ使用量または速度に関するパフォーマンスメトリックが何であるかわかりません。ここにはトレードオフがあり、あなたはそれが何であるかを知っている人です。

これらのオブジェクトをクラスまたは構造体として作成するのはbetterですか?

多分クラス、多分構造体。経験則として:オブジェクトが次の場合:
1。小さい
2。論理的に不変の値
3。たくさんあります
次に、構造体にすることを検討します。そうでなければ、参照型に固執します。

構造体の一部のフィールドを変更する必要がある場合、通常、フィールドが正しく設定された新しい構造体全体を返すコンストラクタを構築することをお勧めします。それはおそらくわずかに遅いですが(測定してください!)、論理的には推論するのがはるかに簡単です。

ヒープ上のオブジェクトとスタックは、ガベージコレクターによって同等に処理されますか?

No、スタック上のオブジェクトはコレクションのルートであるため、これらは同じではありません。ガベージコレクタは、「スタック上のこれは生きているのか?」と尋ねる必要はありません。その質問への答えは常に「はい、スタック上にあります」からです。 (現在、スタックは実装の詳細であるため、オブジェクトの生存keepに依存することはできません。ジッターは最適化を導入するために許可されています。たとえば、通常はスタック値となるものを登録すると、スタック上にないため、GCはそれがまだ生きていることを認識しません。再び読まれるつもりはありません。)

ただし、ガベージコレクターdoesは、スタック上のオブジェクトをアライブとして扱う必要があります。これは、アライブとして知られているオブジェクトをアライブとして扱うのと同じ方法です。スタック上のオブジェクトは、存続する必要のあるヒープ割り当てオブジェクトを参照できるため、GCは、ライブセットを決定するために、スタックオブジェクトを生きているヒープ割り当てオブジェクトのように扱う必要があります。しかし、明らかに、それらは、ヒープを圧縮する目的で「ライブオブジェクト」として扱われるnotです。なぜなら、そもそもヒープ上にないからです。

それは明らかですか?

127
Eric Lippert

時々structを使用すると、new()コンストラクターを呼び出す必要がなく、フィールドを直接割り当てて、通常よりずっと速くすることができます。

例:

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i].id = i;
    list[i].is_valid = true;
}

約2〜3倍高速

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i] = new Value(i, true);
}

ここで、Valueは、2つのフィールド(idおよびis_valid)を持つstructです。

一方、アイテムを移動するか、値の種類を選択する必要があるので、コピーを行うと速度が低下します。正確な答えを得るには、コードのプロファイルを作成してテストする必要があると思います。

22
ja72

構造体はクラスに似ているように見えるかもしれませんが、注意すべき重要な違いがあります。まず、クラスは参照型であり、構造体は値型です。構造体を使用することにより、組み込み型のように動作するオブジェクトを作成し、その利点も享受できます。

クラスでNew演算子を呼び出すと、ヒープに割り当てられます。ただし、構造体をインスタンス化すると、スタック上に作成されます。これにより、パフォーマンスが向上します。また、クラスのように構造体のインスタンスへの参照を処理しません。 structインスタンスを直接操作します。このため、メソッドに構造体を渡すとき、参照としてではなく値によって渡されます。

詳細はこちら:

http://msdn.Microsoft.com/en-us/library/aa288471(VS.71).aspx

7
kyndigs

構造体の配列は、メモリ上の連続したブロックのヒープ上で表されますが、オブジェクトの配列は、ヒープ上の他の場所にある実際のオブジェクトとの参照の連続したブロックとして表されるため、オブジェクトと配列参照の両方にメモリが必要です。

この場合、それらをList<>(およびList<>が配列に戻される)に配置すると、構造体を使用する方がメモリ効率が向上します。

(ただし、大きな配列は、その寿命が長い場合、プロセスのメモリ管理に悪影響を与える可能性があるラージオブジェクトヒープで使用されることに注意してください。また、メモリが唯一の考慮事項ではありません。)

6
Paul Ruane

値のセマンティクスがある場合は、おそらく構造体を使用する必要があります。参照セマンティクスがある場合は、おそらくクラスを使用する必要があります。例外があります。ほとんどの場合、値のセマンティクスが存在する場合でもクラスの作成に傾いていますが、そこから開始します。

2番目の編集に関しては、GCはヒープのみを処理しますが、スタックスペースよりもヒープスペースがはるかに多いため、スタックに物を置くことは必ずしも成功するとは限りません。それに加えて、構造体型のリストとクラス型のリストはいずれにしてもヒープ上にあるので、この場合は関係ありません。

編集:

用語evilは有害であると考え始めています。結局のところ、クラスを変更可能にすることは、それが積極的に必要でない場合には悪い考えであり、変更可能な構造体を使用することを決して排除しません。しかし、ほとんどの場合悪い考えであるほどよくない考えですが、ほとんどの場合値のセマンティクスと一致しないため、特定のケースで構造体を使用することは意味がありません。

ネストされたプライベート構造体では、合理的な例外が存在する可能性があります。そのため、その構造体のすべての使用は非常に限られたスコープに制限されます。ただし、これはここでは当てはまりません。

本当に、私は「それが悪い構造であるように変異する」ことは、ヒープとスタックについて行うことよりもはるかに良いとは思いません(少なくとも頻繁に誤って伝えられたとしても、少なくともパフォーマンスに影響します)。 「それは変化するので、かなりありそうは値のセマンティクスを持っていると考えるのは理にかなっていないので、それは悪い構造です」はわずかに異なるだけですが、重要なことだと思います。

4
Jon Hanna

構造体は、本質的に、フィールドの集合にすぎません。 .NETでは、構造体がオブジェクトのように「ふりをする」ことができ、各構造体タイプに対して、.NETは同じフィールドとメソッドを持つヒープオブジェクトタイプを暗黙的に定義します-ヒープオブジェクトである-オブジェクトのように動作します。このようなヒープオブジェクトへの参照を保持する変数(「ボックス化」構造)は参照セマンティクスを示しますが、構造体を直接保持する変数は、単純に変数の集合です。

構造体対クラスの混乱の多くは、構造体が非常に異なる設計ガイドラインを持つはずの2つの非常に異なる使用例があるという事実に起因すると思いますが、MSガイドラインはそれらを区別しません。オブジェクトのように振る舞うものが必要な場合があります。その場合、MSガイドラインはかなり合理的ですが、「16バイト制限」はおそらく24〜32に近いはずです。ただし、必要なのは変数の集合です。その目的のために使用される構造体は、単純に多数のパブリックフィールド、および場合によってはEqualsオーバーライド、ToStringオーバーライド、およびIEquatable(itsType).Equals実装で構成される必要があります。フィールドの集約として使用される構造体はオブジェクトではないため、ふりをするべきではありません。構造の観点から見ると、フィールドの意味は「このフィールドに最後に書き込まれたもの」以外の何ものでもないはずです。追加の意味は、クライアントコードによって決定される必要があります。

たとえば、変数集約構造体にメンバーMinimumおよびMaximumがある場合、構造体自体はMinimum <= Maximumを約束しません。そのような構造体をパラメーターとして受け取るコードは、個別のMinimum値とMaximum値が渡されたかのように動作する必要があります。 MinimumMaximumを超えないという要件は、Minimumパラメーターが個別に渡されるMaximumを超えないという要件と見なす必要があります。

時々考慮すべき有用なパターンは、次のようなExposedHolder<T>クラスを定義することです。

class ExposedHolder<T>
{
  public T Value;
  ExposedHolder() { }
  ExposedHolder(T val) { Value = T; }
}

List<ExposedHolder<someStruct>>があり、someStructが変数を集約する構造体である場合、myList[3].Value.someField += 7;のようなことを行うことができますが、他のコードにmyList[3].Valueを与えると内容が与えられますValueを変更する手段ではなく、対照的に、List<someStruct>を使用した場合、var temp=myList[3]; temp.someField += 7; myList[3] = temp;を使用する必要があります。可変クラス型を使用した場合、myList[3]の内容を外部コードに公開するには、すべてのフィールドを他のオブジェクトにコピーする必要があります。不変のクラス型または「オブジェクトスタイル」構造体を使用した場合、someFieldが異なるmyList[3]のような新しいインスタンスを作成し、それを保存する必要があります。リストに新しいインスタンス。

追加の注意事項:多数の類似のものを保存する場合は、ネストされた構造の配列に保存するのが良いかもしれません。できれば各配列のサイズを1Kから64K程度に保つようにしてください。構造の配列は特別です。インデックスを作成すると、内部の構造への直接参照が生成されるため、「a [12] .x = 5;」と言えます。配列のようなオブジェクトを定義できますが、C#では、そのような構文を配列と共有することはできません。

3
supercat

最善の解決策は、測定し、再度測定し、さらに測定することです。 「構造体を使用する」や「クラスを使用する」などの単純化された簡単な答えを難しくする可能性のある作業の詳細があるかもしれません。

3
FMM

クラスを使用します。

一般的な注意事項について。作成した値bを更新してみませんか?

1
Preet Sangha

C++の観点からは、クラスと比較してstructsプロパティの変更が遅くなることに同意します。しかし、ヒープではなくスタックに構造体が割り当てられているため、読み込みが速くなると思います。ヒープからデータを読み取るには、スタックからより多くのチェックが必要です。

1
Robert

結局、structを使用する場合は、文字列を取り除き、固定サイズのcharまたはbyteバッファーを使用してください。

それはre:パフォーマンスです。

1