.NET GC & Memory (1)

初心者向けにはしばしば「適当に回収してくれます」と説明される .NET の GC ですが,実際の GC は無作為に実行されるのではなく,あるアルゴリズムに従って動いており,「適当」という言葉でブラックボックス感を演出するのはあまり好きではありません.まあ適切で当然な「適当」だとしても,一意に定まるわけじゃないのでそこをぼかすのはまずいと思うわけですよ.
以下はいつぞやの GDNJ IDisposable 論争で挙げたもの*1を少しだけ書き直したものですが,手元の環境を含め Visual C# .NET 2003 + .NET Framework 1.1 SP1 という条件下でちらほら完走しない現象が再現できたようです.

using System;
using System.Diagnostics;

class Program
{
    static void Main(string args)
    {
        Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));

        int i = 0;
        try
        {
            for (i = 0; i < 1024; ++i)
            {
                byte buffer = new Byte[1024 * 1024 * 128];

                // 一度アクセスしておかないと C# 2.0 の最適化コンパイラによって
                // 配列の作成そのものが除去される
                buffer[0] = 1;
            }
            Trace.WriteLine("完走!");
        }
        catch
        {
            Trace.WriteLine(i + " 回目でストップ");
        }
    }
}

生成された 128MB のバッファは,すぐにルート参照から外れますので,GC が効率的に動いている,あるいは新規メモリ確保時にオンデマンドで GC を行っていれば,最後まで走ってくれそうなものです.が,予想に反して動いてくれないというのが斬新というか話のネタだったわけですね.
GDNJ の『.NET Framework 1.1 で数十MByte のデータを扱うとメモリ不足になる。』も同じような話題が取り上げられています.
C++ のように確定論的にメモリの確保/解放を行うコードを C# に移植する際にこのような問題が表面化する可能性があり,あまりうれしくない振る舞いです.
一方,Visual C# 2005 + .NET 2.0 では上記コードは問題なく完走するようになりました,と書くと単純にそれまでの話のように見えますが,実際には .NET 1.1 SP1 でも Server GC を使用すると完走する可能性が高いんじゃないかと思います.少なくとも手元の環境では完走してくれます.App.config にこんな感じ.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <gcServer enabled="true" />
  </runtime>
</configuration>

というわけで,何が言いたいかというと,いざというときのためにブラックボックスの中をあけて動作を調べてみる方法も知っておいた方がいいんじゃないですかね? と.まあ全員が知っている必要はないと思いますが,あるコミュニティ内で特定技術のブラックボックス率が一定値を超えるとやっぱり困った状態に陥りやすいのではないかと思います.閾値はまあケースバイケースでしょうけどね.
CLR の場合は Hosting API で色々中に潜ることができるので,その辺についてもう少し書いておいた方が後々楽になるかなと思った次第です.

*1:http://www.gdncom.jp/general/bbs/ShowPost.aspx?PostID=4718&PageIndex=10#5934