사용 가능한 메모리가 아직 충분할 때 'System.OutOfMemoryException'이 발생했습니다.


93

이것은 내 코드입니다.

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은 시작하기 위해 기본 최대 메모리로 제한합니까? 추가 요청은 어떻게합니까?

최신 정보

나는 이것을 더 작은 덩어리로 나누고 내 메모리 요구 사항에 점진적으로 추가하면 문제가 메모리 조각화 로 인한 경우 도움이 될 것이라고 생각 했지만 blockSize 조정에 관계없이 256MB의 총 ArrayList 크기를 초과 할 수는 없습니다 .

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 ++ 데이터 유형을 사용하여 하드 디스크에 데이터 청크 직렬화 및 저장)
devinbost

@bosit 이것은 질문 및 답변 사이트입니다. 실제 코드에 대한 구체적인 기술적 질문이있는 경우 질문으로 게시하십시오.
Eric Lippert 2014

@bostIT 귀하의 의견에있는 P 대 NP 문제에 대한 링크가 더 이상 유효하지 않습니다.
RBT

답변:


142

이것을 읽을 수 있습니다 : " "메모리 부족 "은 실제 메모리를 참조하지 않음 ": Eric Lippert.

간단히 말해서 "메모리 부족"이 실제로 사용 가능한 메모리 양이 너무 적다는 것을 의미하지는 않습니다. 가장 일반적인 이유는 현재 주소 공간 내에 원하는 할당을 제공 할만큼 충분히 큰 메모리의 연속적인 부분이 없기 때문입니다. 100 개의 블록 (각 4MB 크기)이 있다면 5MB 블록 하나가 필요할 때 도움이되지 않습니다.

키 포인트:

  • 우리가 "프로세스 메모리"라고 부르는 데이터 저장소는 디스크에 대용량 파일 로 시각화하는 것이 가장 좋습니다 .
  • RAM은 단순히 성능 최적화로 볼 수 있습니다.
  • 프로그램이 사용하는 가상 메모리의 총량은 실제로 성능과 크게 관련이 없습니다.
  • "RAM 부족"으로 인해 "메모리 부족"오류가 발생하는 경우는 거의 없습니다. 오류 대신에 스토리지가 실제로 디스크에 있다는 사실에 대한 전체 비용이 갑자기 관련되기 때문에 성능이 저하됩니다.

"100 개의 블록, 각각 4MB의 큰 블록이 있으면 5MB 블록 하나가 필요할 때 도움이되지 않을 것입니다." - "만약 100 개의 "구멍 " 블록 이있는 경우" 라는 작은 수정으로 표현하는 것이 더 나을 것 같습니다. " .
OfirD

31

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>

당신이 나를 구해주었습니다. 감사합니다!
Tee Zad Awk

25

762MB를 할당하기위한 연속적인 메모리 블록이없고 메모리가 조각화되어 할당자가 필요한 메모리를 할당 할 수있는 충분한 구멍을 찾을 수 없습니다.

  1. / 3GB로 작업 할 수 있습니다 (다른 사람들이 제안한대로).
  2. 또는 64 비트 OS로 전환하십시오.
  3. 또는 큰 메모리 덩어리가 필요하지 않도록 알고리즘을 수정하십시오. 더 작은 (상대적으로) 메모리 청크를 할당 할 수 있습니다.

7

아마 알고 있듯이 문제는 메모리 조각화로 인해 작동하지 않는 하나의 큰 연속 메모리 블록을 할당하려고한다는 것입니다. 당신이하는 일을해야한다면 다음과 같이 할 것입니다.

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);
}

Fredrik Mörk의 답변에 링크 된 블로그는이 문제가 일반적으로 주소 공간 부족으로 인한 것으로 나타 났지만 2GB CLR 개체 크기 제한 (의견에 언급 된 같은 블로그의 ShuggyCoUk), 메모리 조각화에 대해 설명하고 페이지 파일 크기의 영향 (및 CreateFileMapping함수 사용으로 해결 방법)을 언급하지 않았습니다 .

2GB 제한은 2GB randomNumbers 미만이어야 함을 의미 합니다. 배열은 클래스이고 자체적으로 오버 헤드가 있으므로 배열이 double2 ^ 31보다 작아야 함을 의미합니다 . 길이가 2 ^ 31보다 얼마나 작아야하는지 모르겠지만 .NET 배열의 오버 헤드?12-16 바이트를 나타냅니다.

메모리 조각화는 HDD 조각화와 매우 유사합니다. 2GB의 주소 공간이있을 수 있지만 개체를 ​​만들고 제거하면 값 사이에 간격이 생깁니다. 이 간격이 큰 물체에 비해 너무 작아서 추가 공간을 요청할 수없는 경우System.OutOfMemoryException. 예를 들어 2 백만, 1024 바이트 개체를 만드는 경우 1.9GB를 사용하는 것입니다. 주소가 3의 배수가 아닌 모든 객체를 삭제하면 .6GB의 메모리가 사용되지만 그 사이에 2024 바이트의 열린 블록이있는 주소 공간에 분산됩니다. .2GB 크기의 객체를 생성해야하는 경우 해당 객체에 들어갈만큼 큰 블록이없고 추가 공간을 확보 할 수 없기 때문에이를 수행 할 수 없습니다 (32 비트 환경 가정). 이 문제에 대한 가능한 해결책은 작은 개체를 사용하거나, 메모리에 저장하는 데이터 양을 줄이거 나, 메모리 관리 알고리즘을 사용하여 메모리 조각화를 제한 / 방지하는 것입니다. 많은 양의 메모리를 사용하는 대용량 프로그램을 개발하지 않는 한 이것은 문제가되지 않는다는 점에 유의해야합니다. 또한,

대부분의 프로그램은 OS에서 작업 메모리를 요청하고 파일 매핑을 요청하지 않기 때문에 시스템의 RAM 및 페이지 파일 크기에 의해 제한됩니다. 블로그의 Néstor Sánchez (Néstor Sánchez)의 의견에서 언급했듯이 C #과 같은 관리 코드를 사용하면 RAM / 페이지 파일 제한 및 운영 체제의 주소 공간에 갇혀 있습니다.


예상보다 훨씬 길었습니다. 누군가에게 도움이되기를 바랍니다. System.OutOfMemoryException내 어레이가 2GB의 물건을 보유하고 있음에도 불구하고 24GB RAM이있는 시스템에서 x64 프로그램을 실행 했기 때문에 게시했습니다 .


5

/ 3GB Windows 부팅 옵션에 대해 조언하겠습니다. 다른 모든 것 외에 ( 하나 를 위해 이것을하는 것은 과잉입니다. 잘못 동작하는 응용 프로그램에 이며 어쨌든 문제를 해결하지 못할 것임)과 많은 불안정성을 유발할 수 있습니다.

많은 Windows 드라이버가이 옵션으로 테스트되지 않았으므로 상당수의 Windows 드라이버가 사용자 모드 포인터가 항상 주소 공간의 하위 2GB를 가리키는 것으로 가정합니다. 즉, / 3GB로 끔찍하게 깨질 수 있습니다.

그러나 Windows는 일반적으로 32 비트 프로세스를 2GB 주소 공간으로 제한합니다. 그러나 이것이 2GB를 할당 할 수 있다고 기대해야한다는 의미는 아닙니다!

주소 공간은 이미 모든 종류의 할당 된 데이터로 가득 차 있습니다. 스택과로드 된 모든 어셈블리, 정적 변수 등이 있습니다. 800MB의 연속적인 할당되지 않은 메모리가 어디에나 있다는 보장은 없습니다.

400MB 청크 2 개를 할당하는 것이 아마도 더 좋을 것입니다. 또는 4 개의 200MB 청크. 작은 할당은 조각난 메모리 공간에서 공간을 찾기가 훨씬 쉽습니다.

어쨌든 이것을 12GB 머신에 배포하려는 경우 64 비트 애플리케이션으로 실행하여 모든 문제를 해결할 수 있습니다.


작업을 더 작은 덩어리로 나누는 것은 위의 업데이트를 보는 데 도움이되지 않는 것 같습니다.
m3ntat

4

32 비트에서 64 비트로 변경하는 것이 저에게 효과적이었습니다. 64 비트 PC를 사용하고 있고 이식 할 필요가 없다면 시도해 볼 가치가 있습니다.



1

32 비트 창에는 2GB 프로세스 메모리 제한이 있습니다. 다른 사람들이 언급 한 / 3GB 부팅 옵션은 OS 커널 사용을 위해 1GB 만 남아있는이 3GB를 만들 것입니다. 현실적으로 2GB 이상을 번거 로움없이 사용하려면 64 비트 OS가 필요합니다. 이것은 또한 4GB의 물리적 RAM을 가질 수 있지만 비디오 카드에 필요한 주소 공간으로 인해 해당 메모리의 상당 부분을 사용할 수 없게 만드는 문제 (일반적으로 약 500MB)도 해결됩니다.


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 문을 통해 요청한 대로만 생성합니다. 그런 식으로 메모리가 부족하지 않습니다.

또는 모든 파일을 한 곳에 보관해야하는 경우 메모리가 아닌 파일에 저장하십시오.


반복적 인 접근 방식이지만이 앱은 여러 지역 (여러 몬테카를로 시뮬레이션 실행)을 지원하는 24 시간 시계에서 실행되므로 나머지 응용 프로그램의 유휴 시간에 가능한 한 많은 난수 저장소를 저장해야합니다 (여러 몬테카를로 시뮬레이션 실행), 약 70 % 하루 최대 CPU로드 중 남은 시간은 모든 여유 메모리 공간에 난수를 버퍼링하고 싶습니다. 디스크에 저장하는 것은 너무 느리고이 난수 메모리 캐시에 버퍼링 할 수있는 모든 이득을 얻지 못합니다.
m3ntat

0

글쎄, 나는 큰 데이터 세트와 비슷한 문제가 있으며 응용 프로그램이 너무 많은 데이터를 사용하도록 강제하려는 것은 실제로 올바른 옵션이 아닙니다. 제가 드릴 수있는 가장 좋은 팁은 가능하면 데이터를 작은 덩어리로 처리하는 것입니다. 너무 많은 데이터를 처리하기 때문에 조만간 문제가 다시 발생합니다. 또한 응용 프로그램을 실행할 각 컴퓨터의 구성을 알 수 없으므로 항상 다른 PC에서 예외가 발생할 위험이 있습니다.


실제로 나는 기계의 구성을 알고 있으며 이것은 하나의 서버에서만 실행되고 있으며 해당 사양에 대해 이것을 작성할 수 있습니다. 이것은 대규모 몬테카를로 시뮬레이션을위한 것이며 난수를 미리 버퍼링하여 최적화하려고 시도하고 있습니다.
m3ntat


0

솔루션을 x64로 변환하십시오. 여전히 문제가 발생하면 아래와 같이 예외를 발생시키는 모든 항목에 최대 길이를 부여하십시오.

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

0

Visual Studio 호스팅 프로세스가 필요하지 않은 경우 :

옵션을 선택 취소하십시오. 프로젝트-> 속성-> 디버그-> Visual Studio 호스팅 프로세스 활성화

그리고 빌드하십시오.

여전히 문제가있는 경우 :

프로젝트-> 속성-> 빌드 이벤트-> 빌드 후 이벤트 명령 줄로 이동하여 다음을 붙여 넣습니다.

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

이제 프로젝트를 빌드하십시오.


-2

Windows 프로세스 제한을 3GB로 늘립니다. (boot.ini 또는 Vista 부팅 관리자를 통해)


정말? 기본 최대 프로세스 메모리는 얼마입니까? 그리고 그것을 바꾸는 방법? 내 PC에서 게임이나 다른 것을 플레이하면 단일 EXE / 프로세스에서 2GB 이상을 쉽게 사용할 수 있습니다. 여기에서 이것이 문제라고 생각하지 않습니다.
m3ntat

/ 3GB는 과잉이며 많은 드라이버가 사용자 공간 포인터가 항상 낮은 2GB를 가리킨다 고 가정하므로 많은 불안정성을 유발할 수 있습니다.
jalf

1
m3ntat : 아니요, 32 비트 Windows에서는 단일 프로세스가 2GB로 제한됩니다. 나머지 2GB의 주소 공간은 커널에서 사용됩니다.
jalf

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.