これは OutOfMemoryException であるため、ここで問題になっているのはコレクションのサイズや容量ではなく、アプリケーションでのメモリ使用量です。秘訣は、この例外を取得するために、マシンまたはプロセスでさえもメモリを使い果たす必要がないことです。
私が考えているのは、大きなオブジェクト ヒープがいっぱいになっているということです。コレクションが大きくなると、新しいアイテムに対応するためにバックグラウンドでストレージを追加する必要があります。新しいストレージが割り当てられ、アイテムがコピーされると、古いストレージが解放され、ガベージ コレクションの対象となります。
問題は、特定のサイズ (以前は 85000 バイトでしたが、現在は異なる可能性があります) を超えると、ガベージ コレクター (GC) がラージ オブジェクト ヒープ (LOH) と呼ばれるものを使用してメモリを追跡することです。GC が LOH からメモリを解放すると (これは最初はめったに発生しません)、メモリはオペレーティング システムに戻り、他のプロセスで使用できるようになりますが、そのメモリの仮想アドレス空間は自分のプロセス内で引き続き使用されます。 . プログラムのアドレス テーブルに大きな穴ができます。この穴はラージ オブジェクト ヒープ上にあるため、圧縮または再利用されることはありません。
この例外が厳密に 2 のべき乗で表示される理由は、ほとんどの .Net コレクションがコレクションにストレージを追加するために 2 倍のアルゴリズムを使用しているためです。その時点までRAMがすでに割り当てられていたため、再度2倍にする必要がある時点で常にスローされます。
したがって、迅速な解決策は、ほとんどの .Net コレクションのほとんど使用されていない機能を利用することです。コンストラクターのオーバーロードを見ると、ほとんどのコレクション型には、最初の構築時に容量を設定できるものがあります。この容量は厳密な制限ではありません — これは出発点にすぎません — しかし、非常に大きくなるコレクションがある場合など、いくつかの場合に役立ちます。初期容量をわいせつなものに設定できます...できれば、すべてのアイテムを保持するのに十分な大きさにするか、少なくとも1回か2回だけ「2倍」にする必要があります。
コンソール アプリケーションで次のコードを実行すると、この効果を確認できます。
var x = new List<int>();
for (long y = 0; y < long.MaxValue; y++)
x.Add(0);
私のシステムでは、134217728 アイテムの後に OutOfMemory 例外がスローされます。134217728 * int あたり 4 バイトは (正確には) 512MB の RAM にすぎません。プロセス内の実際のサイズはこれだけであるため、まだスローすべきではありませんが、古いバージョンのコレクションにアドレス空間が失われるため、とにかくスローされます。
次のように容量を設定するようにコードを変更しましょう。
var x = new List<int>(134217728 * 2);
for (long y = 0; y < long.MaxValue; y++)
x.Add(0);
現在、私のシステムは、スロー時に 268435456 アイテム (1GB の RAM) まで到達します。これは、2GB の仮想アドレス テーブル制限の一部を消費するプロセスによって使用される他の RAM のおかげで、その 1GB を 2 倍にすることができないためです (つまり、 : コレクション オブジェクトとプロセス自体からのループ カウンターとオーバーヘッド)。
私が説明できないのは、3 を乗数として使用できないということです。さまざまな乗数を使用して、どのくらい大きくなるかを調べようとした小さな実験では、数値が一貫していないことが示されました. ある時点で 2.6 を超えることができましたが、その後 2.4 未満に戻らなければなりませんでした。何か新しい発見があると思います。
このソリューションで十分なスペースが得られる場合は、 3 GBの仮想アドレス空間を取得するために使用できるトリックもあります。または、アプリを x86 または AnyCPU ではなく x64 用にコンパイルするように強制することもできます。2.0 ランタイム (.Net 3.5 までのすべて) に基づくフレームワークのバージョンを使用している場合は、.Net 4.0 以降に更新してみてください。それらに失敗すると、データをディスクに保持し、一度に 1 つのアイテムまたはアイテムの小さなサンプル (キャッシュ) のみをメモリに保持するなど、データの処理方法を完全に書き直す必要があります。私はこの最後のオプションを本当にお勧めします。なぜなら、他のものは最終的に予期せず再び壊れる可能性があるからです (また、データセットが最初からこれほど大きい場合は、データセットも同様に成長する可能性があります)。