読者です 読者をやめる 読者になる 読者になる

構造体とクラスの使い分け

.NET

@IT 会議室ネタ.内容は GC 絡み.

回答に妙に気になる内容がたくさんあったので少しだけ書いてみます.アカウント無いので.
あと,同じサイトで以前こんな記事を書いているので,よろしければどうぞ的な.



ひとくちに GC 対策といっても色々あって,たとえば以下の 2 つは別物です.

  • GC の発生頻度そのものを抑えるために, GC ヒープからのアロケーションを避けること
  • GC 発生時の停止時間を短くするために,「生きているオブジェクト」の個数を少なく保つこと

ボクシングを減らすのは前者,値型だけで構成された構造体の配列を活用するのは後者に有用です.
んで各論.

ひろしさんの書き込み (2008-09-23 15:02) より:

Q1.配列の場合、(b)(c)に関係なくクラスより構造体のほうが常に有利ですか?

Q2.CLR標準のジェネリックコレクション(例 List<T> Queue<T> Dictionary<T>等)についてガイドライン等、有用な情報源はありますか?

Q1

次のようなサンプルを実行してみればわかります。

public struct A
{
    public string name;
}

public class AList : List<A>
{
}

main()
{
    AList l = new AList();
    for (int i = 0; i < 10; i++) {
        l.Add(new A());
        // 「l[i].name = i.ToString();」ではない理由は?
        A r = l[i];
        r.name = i.ToString();
    }
    foreach (A a in l) {
        // この出力が、どうなるかに注目
        System.Diagnostics.Debug.WriteLine(a.name);
    }
    // これとの違いを考えよう
    A[] arr = new A[10];
    for (int i = 0; i < arr.Length; i++) {
        arr[i] = new A();
        arr[i].name = i.ToString();
    }
    foreach (A a in arr) {
        System.Diagnostics.Debug.WriteLine(a.name);
    }
}

これの謎は、ボックス化とボックス化解除 (C# プログラミング ガイド)<microsoft.com>あたりで。

ここに示されたコードで,本当にボクシングは発生するのでしょうか? (ToString や文字列出力の内部実装をのぞく)
おそらく List<T> のインデクサが byref-return*1 を使わずに値のコピーを返していることに気をつけろと言いたいのだと思うのですが,そこでなぜボクシングの話になるのかが分かりません.配列の場合もやはりボクシングは関係なくて,単にマネージポインタが使われているだけですし.
まあそれはさておき,質問者の Q1 に対する私なりの回答ですが,確かに値型だけで構成された構造体の配列は,「生きているオブジェクト」の個数を減らすのに有効です.ただし,その他の条件下では構造体が不利なケースもあるかもしれません.一般論として有利不利を言い切るのは難しい問題に,万能の判断基準を求めているように見えます.難しく考えずに,「困らない方」を選べば良いんじゃないでしょうかね.
次.

ひろしさんの書き込み (2008-09-24 20:46) より:

例えば配列の場合は、構造体で要素を構成すればメモリ上ではひと固まりになります。一方、クラスで要素を構成すればはメモリ上で要素の数だけ分散してしまいます。後者のほうが、GCに大きな負荷をかけてしまうような気がします。

関係ありません。

自分でメモリ管理をしてみれば、わかります。C言語の教科書的参考書なら、基本的なメモリ管理の方法が書いてあると思います。

ここも質問者の方が正しくて,「生きているオブジェクト」の個数を少なくすることは GC のマークフェイズの負荷を減らす上で有効です.まあこの辺は @IT に書いた記事読んでくださいということで.

さらに記事には、10万個のオブジェクトがまだ参照されていることをXbox 360 CLRが調べて回るだけで、14ミリ秒の時間が必要だったという記述が続く(コンパクト化の作業時間を含まないでこの値だそうだ)。この停止時間を短くするには、結局のところオブジェクト数を減らすしかほかにない。純粋な値型の配列は要素数によらず1オブジェクト扱いなので有利というわけだ。

さらに次.

あるいは各ジェネリックコレクション特有の実装を反映した個別のルールが必要になるのでしょうか?

構造体かクラスか/値型か参照型か を格納するコレクションで決めるなんてナンセンスだと思いますけど!? .NETクラスライブラリのコレクションの実装は、いたってフツーな実装ですよ。

コレクションの特徴(追加挿入の得意不得意、ランダムアクセスの得意不得意・・・)を知らずに使うほうが よっぽど問題だと思います。

私は回答者のようには思わなくて,GC を気にせざるをえない状況下では当然コレクションの内部実装も気にします.もちろんコレクションの特徴自体も重要なのですが,そういった特徴が同じでも,GC への影響がまるで異なる複数の実装がありうるわけです.質問者の方が気にしているのは,そういった別の評価軸のことでしょう.
次.

Tdnr_Symさんの書き込み (2008-09-28 03:21) より:

ジェネリックを使っていればボックス化が発生するような場面はそうないですよね!?

ジェネリック コレクション クラスが参照型なので、発生していると思います→2008-09-24 22:02

先ほども書いたように,あのコードでボクシングが発生しているとは思えません.