[c#] 사용 가능한 메모리가 아직 충분할 때 ‘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은 시작하기 위해 기본 최대 메모리로 제한합니까? 추가 요청은 어떻게합니까?

최신 정보

나는 이것을 더 작은 덩어리로 나누고 내 메모리 요구 사항에 점진적으로 추가하면 문제가 메모리 조각화 로 인한 경우 도움이 될 것이라고 생각 했지만 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;



답변

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

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

키 포인트:

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


답변

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>


답변

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

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


답변

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

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 프로그램을 실행 했기 때문에 게시했습니다 .


답변

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

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

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

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

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

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


답변

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


답변

이러한 큰 구조가 필요한 경우 메모리 매핑 파일을 활용할 수 있습니다. 이 문서가 도움이 될 수 있습니다.
http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, 데잔