構造体とクラス


93

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

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

編集する

a。プロパティは値のタイプです(私が考える文字列を除く?)

b。彼らは(まだわかりませんが)検証メソッドを持っているかもしれません

編集2

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


2
彼らはパブリックフィールドのみを持つのでしょうか、それともメソッドも持つのでしょうか?タイプは整数などのプリミティブタイプですか?それらは配列に含まれますか、それともList <T>のようなものに含まれますか?
JeffFerguson、2010年

14
変更可能な構造体のリスト?ヴェロキラプトルに注意してください。
Anthony Pegram

1
@Anthony:ヴェロキラプトルのジョークを見逃しているようです:-s
Michel

5
velociraptorジョークはXKCDからのものです。しかし、「値型がスタックに割り当てられている」誤解/実装の詳細(該当する場合は削除)を使い回している場合は、エリックリッペルトに注意する必要があります...
Greg Beech

回答:


137

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

あなたはその質問への答えを決定できる唯一の人です。、両方の方法それを試して測定し、意味のある、ユーザーに焦点を当て、関連するパフォーマンスメトリックを、その後、あなたは変更が関連するシナリオで実際のユーザーに意味のある効果を持っているかどうかを知っていますよ。

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

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

多分クラス、多分構造。経験則として、オブジェクトが次の場合:
1.小さい
2.論理的に不変の値
3.それらがたくさんある次
に、構造体にすることを検討します。それ以外の場合は、参照型を使用します。

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

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

いいえスタック上のオブジェクトはコレクションのルートであるため、同じではありません。ガベージコレクターは、「スタックにあるものは生きているのか」と尋ねる必要はありません。その質問に対する答えは常に「はい、それはスタックにあります」だからです。(現在、スタックは実装の詳細であるため、オブジェクトを存続させるためにそれを信頼することはできません。ジッターは、通常はスタック値となるものを登録するなどの最適化を導入することを許可され、その後スタックに存在しません。そのため、GCはそれがまだ生きていることを認識していません。登録されたオブジェクトは、それを保持しているレジスタが再度読み取られなくなるとすぐに、その子孫を積極的に収集させることができます。

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

それは明らかですか?


エリック、コンパイラーまたはジッターのいずれかが不変性(おそらくで実施された場合readonly)を利用して最適化を可能にするかどうか知っていますか。私はそれが可変性の選択に影響を与えないようにします(私は理論的には効率の詳細については骨の折れていますが、実際には、効率への最初の取り組みは常に正確さの保証をできるだけ簡単にしようとするため、チェックとエッジケースでCPUサイクルと脳サイクルを浪費し、適切にミュータブルまたはイミュータブルであることはそれを助けます)が、不変性が遅くなる可能性があると言うことへのあらゆる嫌な反応に対抗します。
Jon Hanna

@ジョン:C#コンパイラはconstデータを最適化しますが、読み取り専用データは最適化しません。jitコンパイラーが読み取り専用フィールドでキャッシュの最適化を実行するかどうかはわかりません。
Eric Lippert、

残念ですが、不変性の知識はいくつかの最適化を可能にしますが、その時点で理論的な知識の限界に達しますが、それらは私が伸ばしたい限界です。それまでの間、「どちらの方法も高速になる可能性があります。ここで、なぜこのケースに当てはまるかをテストして見つけてください」と言うのに役立ちます:)
Jon Hanna

simple-talk.com/dotnet/.net-framework/…とあなた自身の記事(@Eric):blogs.msdn.com/b/ericlippert/archive/2010/09/30/…読んでダイビングを開始することをお勧めします詳細に。他にもたくさんの良い記事があります。ところで、100 000の小さなメモリ内オブジェクトの処理の違いは、クラスのメモリオーバーヘッド(約2.3 MB)があるためほとんど目立ちません。簡単なテストで簡単に確認できます。
Nick Martyshchenko

はい、それは明らかです。包括的な回答に非常に感謝します(広範囲な方がいいですか?Google翻訳は2つの翻訳を提供しました。私は、短い回答を書くのではなく、すべての詳細を書くのに時間をかけた)と答えました。
ミシェル

23

時にはstruct、new()コンストラクタを呼び出す必要がなく、フィールドを直接割り当てる必要があるため、通常よりもはるかに高速になります。

例:

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

約2〜3倍速いです

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

ここValuestruct二つのフィールド(とidisValid)。

struct Value
{
    int id;
    bool isValid;

    public Value(int i, bool isValid)
    {
        this.i = i;
        this.isValid = isValid;
    }
}

一方、アイテムを移動する必要があるか、値のタイプを選択すると、コピーが遅くなります。正確な答えを得るには、コードをプロファイルしてテストする必要があると思います。


当然のことながら、ネイティブの境界を越えて値をマーシャリングすると、物事がはるかに速くなります。
leppie

list示されたコードがで機能しないことを考えると、以外の名前を使用することをお勧めしList<Value>ます。
スーパーキャット2015年

7

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

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

詳細はこちら:

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


4
私はそれがMSDNでそれを言うのを知っています、しかしMSDNは全体の話をしていません。スタックとヒープは実装の詳細であり、構造体は常にスタックに置かれるわけではありません。これに関する最近の1つのブログについては、blogs.msdn.com
Anthony Pegram

"...値で渡されます..."参照と構造体の両方が値で渡されます(「ref」を使用する場合を除く)—値または参照が渡されるかどうかが異なります。つまり、構造体は値ごとに渡されます、クラスオブジェクトは値渡しの参照で渡され、refマークの付いたパラメータは参照渡しで渡されます。
ポールルアン

10
その記事はいくつかの重要な点で誤解を招くものであり、MSDNチームに修正または削除するように依頼しました。
Eric Lippert、

2
@supercat:最初のポイントに対処する:より大きなポイントは、値または値への参照が格納されているマネージコードでは、ほとんど無関係です。私たちは、ほとんどの場合、開発者がランタイムに代わってスマートストレージの決定を行えるようにするメモリモデルを作成するために懸命に取り組んできました。これらの違いは、Cの場合のように、それらを理解できないとクラッシュする結果になる場合に非常に重要です。C#ではそれほどではありません。
Eric Lippert、

1
@supercat:2番目の点に対処するために、変更可能な構造体はほとんど悪ではありません。たとえば、void M(){S s = new S(); s.Blah(); N(s); }。リファクタリング:void DoBlah(S s){s.Blah(); } void M(S s = new S(); DoBlah(s); N(s);}。Sは変更可能な構造体であるため、バグが発生しただけです。すぐにバグを見ましたか?またはSが変更可能な構造体はバグを隠しますか?
Eric Lippert

6

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

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

(ただし、その大きな配列はラージオブジェクトヒープ上に配置されるため、存続期間が長いと、プロセスのメモリ管理に悪影響を与える可能性があります。また、メモリだけが考慮事項ではないことに注意してください。)


refこれに対処するためにキーワードを使用することができます。
レッピー

「ただし、その大きな配列はラージオブジェクトヒープ上に配置されるため、存続時間が長い場合、プロセスのメモリ管理に悪影響を与える可能性があります。」-なぜそう思うのかよくわかりません。LOHに割り当てられていても、(おそらく)存続期間の短いオブジェクトであり、Gen 2のコレクションを待たずにすばやくメモリを解放したい場合を除き、メモリ管理に悪影響はありません。
Jon Artus

@Jon Artus:LOHは圧縮されません。長期間有効なオブジェクトは、LOHを前と後の空きメモリの領域に分割します。割り当てには連続メモリが必要であり、これらの領域が割り当てに十分な大きさでない場合は、より多くのメモリがLOHに割り当てられます(つまり、LOHフラグメンテーションが発生します)。
Paul Ruane、2011

4

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

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

編集:

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

ネストされたプライベート構造体には、例外があり、その構造体のすべての使用は非常に限定されたスコープに制限されます。これはここでは適用されません。

本当に、「変異するので悪い構造体だ」というのは、ヒープとスタック(少なくとも、頻繁に誤解されているとしても、パフォーマンスに影響を与える)に比べてずっとましだとは思いません。「それは変化するので、それを値のセマンティクスを持つと考えるのはおそらく意味がないので、それは悪い構造です」とは少しだけ違いますが、重要なのは私が思うからです。


3

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


メジャーの部分に同意しますが、私の意見では、それは単純明快な例であり、おそらくいくつかの一般的なことが言えると思いました。そして結局のところ、一部の人々はそうしました。
ミシェル

3

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

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

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

時々考慮すべき有用なパターンは、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]を外部コードに公開するには、すべてのフィールドを他のオブジェクトにコピーする必要があります。不変のクラス型または「オブジェクトスタイル」の構造体を使用した場合は、異なるものmyList[3]を除いて同様のsomeField新しいインスタンスを作成し、その新しいインスタンスをリストに格納する必要があります。

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



1

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


1

まあ、やっぱりstructで行くなら、文字列を取り除き、固定サイズのcharまたはbyteバッファを使用してください。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.