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


92

これは私のコードです:

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

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

このマシンに4 GBのメモリを搭載していますが、この実行を開始すると2.5 GBは無料です。PCに100 000000の762 mbの乱数を処理するのに十分なスペースが明らかにあります。使用可能なメモリを考慮して、できるだけ多くの乱数を保存する必要があります。私が生産に行くとき、箱に12GBがあるでしょう、そしてそれを利用したいと思います。

CLRは、最初からデフォルトの最大メモリに制限していますか?どうすればもっとリクエストできますか?

更新

これを小さなチャンクに分割し、メモリ要件を徐々に追加すると、問題がメモリの断片化に起因する場合に役立つと思いましたが、blockSizeの調整に関係なく、ArrayListの合計サイズが256 MBを超えることはありません

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;

6
あまりメモリを使用する必要がないように、アプリケーションを再設計することをお勧めします。一度にメモリ内に1億の数値が必要な場合、何をしていますか?
Eric Lippert、

2
あなたはあなたのページファイルまたはそのような愚かな何かを無効にしていませんか?
jalf

@ EricLippert、P対NPの問題に取り組んでいるときにこれに遭遇しています(claymath.org/millenium-problems/p-vs-np-problem)。作業メモリの使用量を減らすための提案はありますか?(例:C ++データ型を使用した、ハードディスク上のデータのチャンクの
シリアル

@bositこれは質疑応答サイトです。実際のコードに関する特定の技術的な質問がある場合は、質問として投稿してください。
Eric Lippert、2014

@bostITコメント内のP対NP問題のリンクが無効になりました。
RBT 2016

回答:


140

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

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

キーポイント:

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

「100 MBのブロックがあり、それぞれが4 MBの大きさである場合、5 MBのブロックが1つ必要な場合には役に立ちません」 - 「100の「穴」ブロックがある場合」と小さな修正を加えた方がいいと思います。
OfirD

31

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

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

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

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

あなたは私を救いました。ありがとうございました!
Tee Zad Awk

25

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

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

7

おそらくご想像のとおり、問題は、メモリの断片化のために機能しない、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関数を使用して対処する方法)については触れていません。

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

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

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


それは予想よりもずっと長かった。うまくいけば、それは誰かを助けます。System.OutOfMemoryException私の配列は2GBのものしか保持していなかったのに、24GBのRAMを搭載したシステムでx64プログラムを実行していたため、それを投稿しました。


5

/ 3GBのWindowsブートオプションはお勧めしません。それ以外のことは別として(1つの不適切に動作するアプリケーションでこれを行うのはやり過ぎであり、おそらく問題が解決されないでしょう)、多くの不安定性を引き起こす可能性があります。

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

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

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

2つの400MBチャンクを割り当てると、おそらくもっとうまくいくでしょう。または4つの200MBチャンク。割り当てが小さいほど、断片化されたメモリ空間にスペースを見つけるのがはるかに簡単になります。

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


ジョブを小さなチャンクに分割しても、上記の私の更新を確認するのには役立ちません。
m3ntat 2009

4

32ビットから64ビットに変更するとうまくいきました。64ビットPCを使用していて、移植する必要がない場合は、試してみる価値があります。



1

32ビットのウィンドウには2GBのプロセスメモリ制限があります。他の人が言及した/ 3GB起動オプションは、OSカーネルで使用するために残り1GBで3GBにします。現実的には、手間をかけずに2 GB以上を使用する場合は、64ビットOSが必要です。これにより、4 GBの物理RAMがある場合でも、ビデオカードに必要なアドレス空間により、そのメモリのかなり大きな部分が使用できなくなり、通常は約500 MBになるという問題も克服されます。


1

大規模な配列を割り当てるのではなく、イテレータを利用してみませんか?これらは遅延実行されます。つまり、値は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か所に置く必要がある場合は、メモリではなくファイルに保存します。


反復的なアプローチですが、このアプリは複数の地理的領域(複数のモンテカルロシミュレーションの実行)をサポートする24時間クロックで実行されるため、アプリケーションの残りのアイドル時間にできるだけ多くの乱数リポジトリを格納する必要があります(約70%) 1日の最大CPU負荷の1日の残り時間。すべての空きメモリ領域に乱数をバッファリングします。ディスクへの保存は遅すぎて、この乱数メモリキャッシュにバッファリングすることで得られる利益をある程度損なうものです。
m3ntat 2009

0

まあ、私は大きなデータセットで同様の問題を抱えており、アプリケーションに多くのデータを使用させようとすることは実際には適切なオプションではありません。私があなたに与えることができる最良のヒントは、可能であればデータを小さなチャンクで処理することです。大量のデータを処理するため、問題は遅かれ早かれ再発するでしょう。さらに、アプリケーションを実行する各マシンの構成を知ることができないため、例外が別のPCで発生するリスクが常にあります。


実際、私はマシンの構成を知っています。これは1つのサーバーでのみ実行されており、これらの仕様のためにこれを書くことができます。これは大規模なモンテカルロシミュレーション用で、乱数を事前にバッファリングして最適化を試みています。
m3ntat 2009


0

ソリューションをx64に変換します。それでも問題が解決しない場合は、以下のような例外をスローするものすべてに最大長を許可してください。

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

0

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

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

そしてビルドします。

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

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

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

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


-2

Windowsプロセスの制限を3GBに増やします。(boot.iniまたはVistaブートマネージャ経由)


本当に?デフォルトの最大プロセスメモリは何ですか?そしてそれを変更するには?私のPCでゲームや何かをプレイする場合、1つのEXE /プロセスから簡単に2+ GBを使用できますが、これはここでの問題ではないと思います。
m3ntat 2009

/ 3GBはこれには過剰であり、多くのドライバはユーザースペースポインタが常に下位2GBを指すと想定しているため、多くの不安定性を引き起こす可能性があります。
2009

1
m3ntat:いいえ、32ビットWindowsでは、単一のプロセスが2GBに制限されています。残りの2GBのアドレス空間はカーネルによって使用されます。
2009

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