102

これは私のコードです:

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

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

このマシンには 4GB のメモリがあります。この実行を開始すると 2.5GB が空いています。PC には明らかに 762MB の 100000000 の乱数を処理するのに十分なスペースがあります。利用可能なメモリを考慮して、できるだけ多くの乱数を保存する必要があります。生産に行くと、ボックスに 12GB あり、それを利用したいと考えています。

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

アップデート

問題がメモリの断片化によるものである場合、これを小さなチャンクに分割し、メモリ要件を段階的に追加すると役立つと思いましたが、 blockSize を微調整しても、 ArrayList の合計サイズ 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;
4

14 に答える 14

149

次の記事をお読みになることをお勧めします: 「“メモリ不足” は物理メモリを参照していません」 by Eric Lippert.

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

キーポイント:

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

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

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

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

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
于 2013-06-26T13:56:06.753 に答える
26

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

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

おそらくお気づきかもしれませんが、問題は、メモリの断片化が原因で機能しない、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それを変数に格納します。次に、リストを調べ始めるたびに、元のシードを使用して新しい乱数インスタンスを作成します。

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.9GBを使用しています。アドレスが3の倍数ではないすべてのオブジェクトを削除すると、0.6 GBのメモリが使用されますが、2024バイトのオープンブロックを挟んでアドレス空間全体に分散されます。.2GBのオブジェクトを作成する必要がある場合、それを収めるのに十分な大きさのブロックがなく、追加のスペースを取得できないため(32ビット環境を想定)、作成できません。この問題に対する考えられる解決策は、より小さなオブジェクトを使用する、メモリに格納するデータの量を減らす、またはメモリ管理アルゴリズムを使用してメモリの断片化を制限/防止するなどです。大量のメモリを使用する大規模なプログラムを開発していない限り、これは問題にならないことに注意してください。また、

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


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

于 2012-12-24T23:15:42.543 に答える
5

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

于 2012-08-31T21:04:27.410 に答える
5

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

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

ただし、Windows は通常、32 ビット プロセスを 2GB のアドレス空間に制限します。しかし、だからと言って 2GB を割り当てられると期待する必要はありません!

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

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

いずれにせよ、これを 12 GB のマシンにデプロイする場合は、これを 64 ビット アプリケーションとして実行することをお勧めします。これにより、すべての問題が解決されるはずです。

于 2009-07-20T14:05:46.637 に答える
2

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

LP、デジャン

于 2009-07-20T14:09:09.693 に答える
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 か所に保管する必要がある場合は、メモリではなくファイルに保存します。

于 2009-07-20T17:05:39.003 に答える
1

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

于 2009-07-20T14:03:07.857 に答える
0

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

于 2017-03-30T10:57:24.537 に答える
0

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

于 2009-07-20T14:05:36.653 に答える
-2

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

于 2009-07-20T13:53:33.730 に答える