web-dev-qa-db-ja.com

まだ十分な空きメモリがあるときに「System.OutOfMemoryException」がスローされました

これは私のコードです:

int size = 100000000;
double sizeInMegabytes = (size * 8.0) / 1024.0 / 1024.0; //762 mb
double[] randomNumbers = new double[size];

例外:タイプ 'System.OutOfMemoryException'の例外がスローされました。

このマシンには4GBのメモリがあり、2.5GBは無料ですこの実行を開始すると、明らかに100000000の乱数のうち762MBを処理するのに十分なPCのスペースがあります。使用可能なメモリがあれば、できるだけ多くの乱数を保存する必要があります。本番環境に行くと、ボックスに12GBがあり、それを利用したいと思います。

CLRは、最初にデフォルトの最大メモリに制限しますか?さらに要求するにはどうすればよいですか?

更新

これを小さなチャンクに分割し、メモリ要件に段階的に追加すると、問題がmemory fragmentationに起因する場合に役立つと考えましたが、そうではありませんArrayListの合計を超えることはできませんblockSizeの微調整に関係なく、256MBのサイズ

private static IRandomGenerator rnd = new MersenneTwister();
private static IDistribution dist = new DiscreteNormalDistribution(1048576);
private static List<double> ndRandomNumbers = new List<double>();

private static void AddNDRandomNumbers(int numberOfRandomNumbers) {
    for (int i = 0; i < numberOfRandomNumbers; i++) {
      ndRandomNumbers.Add(dist.ICDF(rnd.nextUniform()));                
  }
}

私のメインメソッドから:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
84
m3ntat

これを読みたいと思うかもしれません:「 「メモリ不足」は物理メモリを参照しません by Eric Lippert」.

要するに、非常に単純化された「メモリ不足」とは、実際に利用可能なメモリの量が少なすぎるということではありません。最も一般的な理由は、現在のアドレス空間内に、必要な割り当てを提供するのに十分な大きさの連続したメモリ部分がないことです。 100 MBのブロックがあり、それぞれ4 MBの大きさがある場合、5 MBのブロックが1つ必要なときに役立ちません。

キーポイント:

  • 私たちが「プロセスメモリ」と呼ぶデータストレージは、私の意見ではディスク上の大容量ファイルとして最もよく視覚化されます。
  • RAMは単なるパフォーマンスの最適化と見なすことができます
  • プログラムが消費する仮想メモリの総量は、実際にはそのパフォーマンスにあまり関係ありません
  • 「RAM不足」が「メモリ不足」エラーになることはほとんどありません。エラーの代わりに、ストレージが実際にディスク上にあるという事実の全コストが突然重要になるため、パフォーマンスが低下します。
129
Fredrik Mörk

762MBを割り当てるための連続したメモリブロックがないため、メモリが断片化され、アロケータが必要なメモリを割り当てるのに十分な大きさの穴を見つけることができません。

  1. / 3GBで作業を試みることができます(他の人が提案したように)
  2. または、64ビットOSに切り替えます。
  3. または、大量のメモリを必要としないようにアルゴリズムを変更します。たぶん、いくつかの小さな(比較的)チャンクのメモリを割り当てます。
25
Shay Erlichmen

Visual Studioのデフォルトのコンパイルモードである32ビットプロセスではなく、64ビットプロセスをビルドしていることを確認してください。これを行うには、プロジェクトを右クリックして、[プロパティ]-> [ビルド]-> [プラットフォームターゲット:x64]を選択します。 32ビットプロセスと同様に、32ビットでコンパイルされたVisual Studioアプリケーションには2GBの仮想メモリ制限があります。

64ビットプロセスは64ビットポインターを使用するため、この制限はありません。したがって、理論上の最大アドレススペース(仮想メモリのサイズ)は16エクサバイト(2 ^ 64)です。実際には、Windows x64はプロセスの仮想メモリを8TBに制限しています。メモリ制限の問題を解決するには、64ビットでコンパイルします。

ただし、Visual Studioのオブジェクトのサイズは、デフォルトで2GBに制限されています。合計サイズが2GBを超える複数のアレイを作成できますが、デフォルトで2GBを超えるアレイを作成することはできません。 2GBを超える配列を作成したい場合は、app.configファイルに次のコードを追加してください。

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
24

おそらく理解したように、問題は、メモリの断片化のために機能しない、1つの大きな連続したメモリブロックを割り当てようとしていることです。私があなたがしていることをする必要があるなら、私は以下をするでしょう:

int sizeA = 10000,
    sizeB = 10000;
double sizeInMegabytes = (sizeA * sizeB * 8.0) / 1024.0 / 1024.0; //762 mb
double[][] randomNumbers = new double[sizeA][];
for (int i = 0; i < randomNumbers.Length; i++)
{
    randomNumbers[i] = new double[sizeB];
}

次に、特定のインデックスを取得するには、randomNumbers[i / sizeB][i % sizeB]を使用します。

常に値に順番にアクセスする場合の別のオプションは、 オーバーロードされたコンストラクター を使用してシードを指定することです。この方法で、半乱数( DateTime.Now.Ticks など)を取得して変数に保存し、リストを開始するたびに、元のシードを使用して新しいRandomインスタンスを作成します。

private static int randSeed = (int)DateTime.Now.Ticks;  //Must stay the same unless you want to get different random numbers.
private static Random GetNewRandomIterator()
{
    return new Random(randSeed);
}

FredrikMörkの答えにリンクされているブログは、問題は通常アドレス空間の不足によるものであることを示していますが、 2GB CLRオブジェクトサイズの制限(同じブログのShuggyCoUkからのコメントで言及されている)のような他の問題の数は、メモリの断片化を超えており、ページファイルサイズの影響について言及していません CreateFileMapping function )の。

2GBの制限は、randomNumbersが2GB未満でなければならないことを意味します。配列はクラスであり、オーバーヘッドがあるため、doubleの配列は2 ^ 31より小さい必要があります。 Lengthを2 ^ 31より小さくする必要があるかどうかはわかりませんが、 。NET配列のオーバーヘッド? は12-16バイトを示します。

メモリの断片化は、HDDの断片化に非常に似ています。 2GBのアドレス空間がある場合がありますが、オブジェクトを作成および破棄すると、値間にギャップが生じます。これらのギャップが大きなオブジェクトに対して小さすぎて、追加のスペースを要求できない場合は、System.OutOfMemoryExceptionを取得します。たとえば、200万、1024バイトのオブジェクトを作成する場合、1.9GBを使用しています。アドレスが3の倍数ではないすべてのオブジェクトを削除すると、.6GBのメモリを使用しますが、2024バイトのオープンブロックを間に挟んでアドレススペース全体に広がります。 .2GBのオブジェクトを作成する必要がある場合、それを収めるのに十分な大きさのブロックがなく、追加のスペースを取得できないため(32ビット環境を想定)、それを行うことができません。この問題の解決策としては、より小さいオブジェクトの使用、メモリに保存するデータ量の削減、メモリ管理アルゴリズムの使用によるメモリの断片化の制限/防止などがあります。大量のメモリを使用する大規模なプログラムを開発しない限り、これは問題にならないことに注意してください。また、ウィンドウはページファイルサイズとシステム上のRAMの量によって主に制限されるため、この問題は64ビットシステムで発生する可能性があります。

ほとんどのプログラムはOSに作業メモリを要求し、ファイルマッピングを要求しないため、システムのRAMおよびページファイルサイズによって制限されます。ブログのNéstorSánchez(NéstorSánchez)のコメントに記載されているように、C#のようなマネージコードを使用すると、RAM /ページファイルの制限とオペレーティングシステムのアドレススペースに縛られます。


それは予想よりずっと長かった。うまくいけば誰かの助けになります。アレイが2GBのデータしか保持していないにもかかわらず、24GBのRAMを備えたシステムでx64プログラムを実行しているSystem.OutOfMemoryExceptionに遭遇したため、これを投稿しました。

7
Trisped

/ 3GB Windowsブートオプションに反対することをお勧めします。他のすべて(one動作が悪いアプリケーションでこれを行うのはやり過ぎであり、おそらくあなたの問題を解決できない可能性があります)は別として、多くの不安定性を引き起こす可能性があります。

多くのWindowsドライバーはこのオプションでテストされていないため、多くのドライバーはユーザーモードポインターが常にアドレススペースの下位2GBを指していると想定しています。つまり、/ 3GBでひどく壊れる可能性があります。

ただし、Windowsは通常、32ビットプロセスを2GBのアドレス空間に制限します。しかし、それは2GBを割り当てることができると期待すべきだという意味ではありません!

アドレス空間には、あらゆる種類の割り当てられたデータがすでに散らばっています。スタック、ロードされるすべてのアセンブリ、静的変数などがあります。どこにも800MBの連続した未割り当てメモリが存在するという保証はありません。

2つの400MBチャンクを割り当てると、おそらくより良い結果が得られます。または4つの200MBチャンク。割り当てが小さいと、断片化されたメモリスペースに空きを見つけるのがはるかに簡単になります。

とにかく、これを12GBマシンに展開する場合、すべての問題を解決する64ビットアプリケーションとして実行する必要があります。

5
jalf

32ビットから64ビットへの変更は私のために働いた-あなたが64ビットのPCを使用していて、移植する必要がない場合は試してみる価値があります。

3
chris

このような大きな構造が必要な場合は、おそらくメモリマップドファイルを利用できます。この記事は役に立つかもしれません: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP、デヤン

2
Dejan Stanič

大規模な配列を割り当てるのではなく、イテレーターを利用してみてください。これらは遅延実行されます。つまり、値はforeachステートメントで要求されたときにのみ生成されます。この方法でメモリを使い果たすべきではありません:

private static IEnumerable<double> MakeRandomNumbers(int numberOfRandomNumbers) 
{
    for (int i = 0; i < numberOfRandomNumbers; i++)
    {
        yield return randomGenerator.GetAnotherRandomNumber();
    }
}


...

// Hooray, we won't run out of memory!
foreach(var number in MakeRandomNumbers(int.MaxValue))
{
    Console.WriteLine(number);
}

上記は、必要な数の乱数を生成しますが、foreachステートメントで要求された場合にのみ生成します。そのようにメモリを使い果たすことはありません。

または、すべてを1か所にまとめる必要がある場合は、メモリではなくファイルに保存します。

32ビットウィンドウには2GBのプロセスメモリ制限があります。他の人が言及した/ 3GBブートオプションは、OSカーネルで使用するために残りわずか1GBでこの3GBを作成します。現実的に、手間をかけずに2GB以上を使用する場合は、64ビットOSが必要です。これにより、4GBの物理RAMがありますが、ビデオカードに必要なアドレススペースにより、そのメモリのかなりのチャックが使用できなくなる可能性があります(通常500MB程度)。

1
redcalx

setgcAllowVeryObjectsプロパティの値をTrueに、アプリケーションconfigurationファイルで試してください。

注意この機能を有効にする前に、すべての配列のサイズが2 GB未満であると想定する安全でないコードがアプリケーションに含まれていないことを確認してください。たとえば、配列をバッファーとして使用する安全でないコードは、配列が2 GBを超えないという前提で記述されている場合、バッファーオーバーランの影響を受けやすくなります。

<configuration>  
  <runtime>  
    <gcAllowVeryLargeObjects enabled="true" />  
  </runtime>  
</configuration>
0

ソリューションをx64に変換します。それでも問題が発生する場合は、次のような例外をスローするすべての要素に最大長を付与します。

 var jsSerializer = new JavaScriptSerializer();
 jsSerializer.MaxJsonLength = Int32.MaxValue;
0
Samidjo

同様の問題がありました。これはStringBuilder.ToString()が原因でした。

0
Ricardo Rix

Visual Studioホスティングプロセスが必要ない場合:

オプションのチェックを外します:Project-> Properties-> Debug-> Enable the Visual Studio Hosting Process

そしてビルドします。

それでも問題が解決しない場合:

[プロジェクト]-> [プロパティ]-> [ビルドイベント]-> [ビルド後のイベントコマンドライン]に移動し、以下を貼り付けます。

call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86
"$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)"  /LARGEADDRESSAWARE

次に、プロジェクトをビルドします。

0
Yasir Arafat

まあ、大規模なデータセットで同様の問題が発生し、アプリケーションに大量のデータを使用させようとするのは、実際には正しい選択肢ではありません。可能な限り、小さなチャンクでデータを処理することをお勧めします。大量のデータを扱うため、問題は遅かれ早かれ戻ってきます。さらに、アプリケーションを実行する各マシンの構成を知ることができないため、別のPCで例外が発生するリスクが常にあります。

0
Francis B.