'System.OutOfMemoryException' foi lançado quando ainda havia muita memória livre

92

Este é o meu código:

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

Exceção: a exceção do tipo 'System.OutOfMemoryException' foi lançada.

Eu tenho 4 GB de memória nesta máquina. 2,5 GB estão livres quando eu começo esta execução, há claramente espaço suficiente no PC para lidar com 762 MB de 1.000.000 de números aleatórios. Preciso armazenar tantos números aleatórios quanto possível, dada a memória disponível. Quando eu entrar em produção, haverá 12 GB na caixa e quero fazer uso disso.

O CLR me limita a uma memória máxima padrão para começar? e como faço para solicitar mais?

Atualizar

Achei que dividir isso em pedaços menores e adicionar incrementalmente aos meus requisitos de memória ajudaria se o problema for devido à fragmentação de memória , mas não é porque não consigo passar de um tamanho total de ArrayList de 256 MB, independentemente do que eu fizer ajustando blockSize .

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

Do meu método principal:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
m3ntat
fonte
6
Eu recomendaria rearquivar seu aplicativo para que você não precise usar tanta memória. O que você está fazendo para precisar de cem milhões de números todos na memória de uma vez?
Eric Lippert de
2
você não desabilitou seu arquivo de paginação ou algo bobo assim, não é?
jalf de
@EricLippert, estou encontrando isso ao trabalhar no problema P vs. NP ( claymath.org/millenium-problems/p-vs-np-problem ). Você tem alguma sugestão para reduzir o uso da memória de trabalho? (por exemplo, serializando e armazenando blocos de dados no disco rígido, usando tipo de dados C ++, etc.)
devinbost
@bosit este é um site de perguntas e respostas. Se você tiver uma pergunta técnica específica sobre o código real, poste-a como uma pergunta.
Eric Lippert
@bostIT o link para o problema P vs. NP em seu comentário não é mais válido.
RBT

Respostas:

140

Você pode querer ler isto: " “ Out Of Memory ”não se refere à memória física ", de Eric Lippert.

Resumindo, e muito simplificado, "Sem memória" não significa realmente que a quantidade de memória disponível seja muito pequena. O motivo mais comum é que, no espaço de endereço atual, não há nenhuma parte contígua da memória grande o suficiente para atender à alocação desejada. Se você tem 100 blocos, cada um com 4 MB, isso não vai te ajudar quando você precisa de um bloco de 5 MB.

Pontos chave:

  • o armazenamento de dados que chamamos de “memória de processo” é, em minha opinião, melhor visualizado como um arquivo enorme em disco .
  • RAM pode ser visto apenas como uma otimização de desempenho
  • A quantidade total de memória virtual que seu programa consome não é muito relevante para seu desempenho
  • "Ficar sem RAM" raramente resulta em um erro de "falta de memória". Em vez de um erro, isso resulta em desempenho ruim porque o custo total do fato de o armazenamento estar realmente no disco repentinamente se torna relevante.
Fredrik Mörk
fonte
"Se você tem 100 blocos, cada um com 4 MB de largura, isso não vai te ajudar quando você precisa de um bloco de 5 MB" - acho que seria melhor formulado com uma pequena correção: "Se você tiver 100 " blocos de " orifícios " .
OfirD
31

Verifique se você está criando um processo de 64 bits, e não de 32 bits, que é o modo de compilação padrão do Visual Studio. Para fazer isso, clique com o botão direito em seu projeto, Propriedades -> Construir -> destino da plataforma: x64. Como qualquer processo de 32 bits, os aplicativos do Visual Studio compilados em 32 bits têm um limite de memória virtual de 2 GB.

Os processos de 64 bits não têm essa limitação, pois usam ponteiros de 64 bits, de modo que seu espaço de endereço máximo teórico (o tamanho de sua memória virtual) é de 16 exabytes (2 ^ 64). Na realidade, o Windows x64 limita a memória virtual dos processos a 8 TB. A solução para o problema de limite de memória é, então, compilar em 64 bits.

No entanto, o tamanho do objeto no Visual Studio ainda é limitado a 2 GB, por padrão. Você poderá criar vários arrays cujo tamanho combinado será maior que 2 GB, mas não pode por padrão criar arrays maiores que 2 GB. Felizmente, se você ainda deseja criar matrizes maiores do que 2 GB, pode fazê-lo adicionando o seguinte código ao arquivo app.config:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
Tecnologia Shift
fonte
Você me salvou. Obrigado!
Tee Zad Awk
25

Você não tem um bloco contínuo de memória para alocar 762 MB, sua memória está fragmentada e o alocador não consegue encontrar um buraco grande o suficiente para alocar a memória necessária.

  1. Você pode tentar trabalhar com / 3GB (como outros sugeriram)
  2. Ou mude para o sistema operacional de 64 bits.
  3. Ou modifique o algoritmo para que ele não precise de um grande pedaço de memória. talvez aloque alguns pedaços menores (relativamente) de memória.
Shay Erlichmen
fonte
7

Como você provavelmente percebeu, o problema é que você está tentando alocar um grande bloco contíguo de memória, que não funciona devido à fragmentação da memória. Se eu precisasse fazer o que você está fazendo, faria o seguinte:

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

Então, para obter um índice específico, você usaria randomNumbers[i / sizeB][i % sizeB].

Outra opção se você sempre acessar os valores em ordem pode ser usar o construtor sobrecarregado para especificar a semente. Dessa forma, você obteria um número semi-aleatório (como o DateTime.Now.Ticks) e o armazenaria em uma variável, então sempre que começar a examinar a lista, você criaria uma nova instância Random usando a semente original:

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

É importante notar que, embora o blog com link na resposta de Fredrik Mörk indique que o problema geralmente é devido à falta de espaço de endereço, ele não lista uma série de outros problemas, como a limitação de tamanho do objeto CLR de 2 GB (mencionado em um comentário de ShuggyCoUk no mesmo blog), ignora a fragmentação da memória e não menciona o impacto do tamanho do arquivo de página (e como ele pode ser tratado com o uso da CreateFileMappingfunção ).

A limitação de 2 GB significa que randomNumbers deve ser inferior a 2 GB. Como os arrays são classes e têm alguma sobrecarga, isso significa que um array de doubleprecisará ser menor que 2 ^ 31. Não tenho certeza de quanto menor então 2 ^ 31 o comprimento teria que ser, mas sobrecarga de um array .NET? indica 12 - 16 bytes.

A fragmentação da memória é muito semelhante à fragmentação do HDD. Você pode ter 2 GB de espaço de endereço, mas conforme você cria e destrói objetos, haverá lacunas entre os valores. Se essas lacunas forem muito pequenas para o seu objeto grande e não for possível solicitar espaço adicional, você obterá oSystem.OutOfMemoryException. Por exemplo, se você criar objetos de 2 milhões e 1.024 bytes, estará usando 1,9 GB. Se você excluir todos os objetos em que o endereço não for um múltiplo de 3, você usará 0,6 GB de memória, mas ela estará espalhada pelo espaço de endereço com blocos abertos de 2024 bytes entre eles. Se você precisar criar um objeto com .2 GB, você não poderá fazê-lo porque não há um bloco grande o suficiente para caber nele e não é possível obter espaço adicional (assumindo um ambiente de 32 bits). As soluções possíveis para esse problema são coisas como usar objetos menores, reduzir a quantidade de dados que você armazena na memória ou usar um algoritmo de gerenciamento de memória para limitar / evitar a fragmentação da memória. Deve-se observar que, a menos que você esteja desenvolvendo um programa grande que usa uma grande quantidade de memória, isso não será um problema. Além disso,

Como a maioria dos programas requer memória de trabalho do sistema operacional e não solicita um mapeamento de arquivo, eles serão limitados pela RAM do sistema e pelo tamanho do arquivo de página. Conforme observado no comentário de Néstor Sánchez (Néstor Sánchez) no blog, com código gerenciado como C # você fica preso à limitação do arquivo RAM / página e ao espaço de endereço do sistema operacional.


Isso foi muito mais longo do que o esperado. Espero que ajude alguém. Eu postei porque corri para System.OutOfMemoryExceptionrodar um programa x64 em um sistema com 24 GB de RAM, embora meu array tivesse apenas 2 GB de material.

Trisped
fonte
5

Eu desaconselho a opção de inicialização do Windows / 3GB. Além de tudo mais (é um exagero fazer isso para um aplicativo malcomportado e provavelmente não resolverá o seu problema de qualquer maneira), pode causar muita instabilidade.

Muitos drivers do Windows não são testados com esta opção, portanto, alguns deles assumem que os ponteiros do modo de usuário sempre apontam para os 2 GB inferiores do espaço de endereço. O que significa que eles podem quebrar terrivelmente com / 3GB.

No entanto, o Windows normalmente limita um processo de 32 bits a um espaço de endereço de 2 GB. Mas isso não significa que você deve esperar conseguir alocar 2 GB!

O espaço de endereço já está cheio de todos os tipos de dados alocados. Existe a pilha e todos os assemblies carregados, variáveis ​​estáticas e assim por diante. Não há garantia de que haverá 800 MB de memória contígua não alocada em qualquer lugar.

Alocar 2 blocos de 400 MB provavelmente seria melhor. Ou 4 blocos de 200 MB. É muito mais fácil encontrar espaço para alocações menores em um espaço de memória fragmentado.

De qualquer forma, se você vai implantar isso em uma máquina de 12 GB, você vai querer executá-lo como um aplicativo de 64 bits, o que deve resolver todos os problemas.

Jalf
fonte
Dividir o trabalho em pedaços menores não parece ajudar a ver minha atualização acima.
m3ntat em
4

Mudar de 32 para 64 bits funcionou para mim - vale a pena tentar se você estiver em um PC de 64 bits e ele não precisa ser transferido.

chris
fonte
1

O Windows de 32 bits tem um limite de memória de processo de 2 GB. A opção de inicialização / 3GB que outros mencionaram fará com que sejam 3 GB com apenas 1 GB restante para uso do kernel do sistema operacional. Realisticamente, se você quiser usar mais de 2 GB sem problemas, é necessário um sistema operacional de 64 bits. Isso também supera o problema pelo qual, embora você possa ter 4 GB de RAM física, o espaço de endereço necessário para a placa de vídeo pode inutilizar uma parte considerável dessa memória - geralmente em torno de 500 MB.

redcalx
fonte
1

Em vez de alocar uma grande matriz, você poderia tentar utilizar um iterador? Eles são executados com atraso, o que significa que os valores são gerados apenas quando solicitados em uma instrução foreach; você não deve ficar sem memória desta forma:

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

O código acima irá gerar quantos números aleatórios você desejar, mas apenas gerá-los conforme eles são solicitados por meio de uma instrução foreach. Você não vai ficar sem memória dessa forma.

Como alternativa, se você precisar mantê-los todos em um só lugar, armazene-os em um arquivo em vez de na memória.

Judah Gabriel Himango
fonte
Abordagem iterativa, mas preciso armazenar o máximo possível como um repositório de números aleatórios em qualquer tempo ocioso do resto do meu aplicativo, pois este aplicativo é executado em um relógio de 24 horas com suporte para várias regiões geográficas (várias simulações de monte carlo), cerca de 70% da carga máxima de CPU do dia, o restante do dia desejo armazenar números aleatórios em todo o espaço livre da memória. O armazenamento no disco é muito lento e meio que anula qualquer ganho que eu pudesse fazer no buffer neste cache de memória de número aleatório.
m3ntat em
0

Bem, eu tenho um problema semelhante com um grande conjunto de dados e tentar forçar o aplicativo a usar tantos dados não é realmente a opção certa. A melhor dica que posso dar é processar seus dados em pequenos pedaços, se possível. Por lidar com tantos dados, o problema voltará mais cedo ou mais tarde. Além disso, você não pode saber a configuração de cada máquina que executará seu aplicativo, então sempre existe o risco de que a exceção aconteça em outro computador.

Francis B.
fonte
Na verdade eu conheço a configuração da máquina, ela está rodando em apenas um servidor e posso escrever isso para essas especificações. Isso é para uma simulação de monte carlo enorme e estou tentando otimizar o buffer de números aleatórios antecipadamente.
m3ntat em
0

Eu tive um problema semelhante, foi devido a um StringBuilder.ToString ();

Ricardo Rix
fonte
não deixe o construtor de cordas ficar tão grande
Ricardo Rix
0

Converta sua solução para x64. Se você ainda enfrentar um problema, conceda comprimento máximo a tudo que lança uma exceção como a seguir:

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

Se você não precisa do processo de hospedagem do Visual Studio:

Desmarque a opção: Projeto-> Propriedades-> Depurar-> Habilitar o Processo de Hospedagem do Visual Studio

E então construir.

Se você ainda enfrenta o problema:

Vá para Projeto-> Propriedades-> Eventos de compilação-> Linha de comando do evento de pós-compilação e cole o seguinte:

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

Agora, construa o projeto.

Yasir Arafat
fonte
-2

Aumente o limite de processo do Windows para 3 gb. (via boot.ini ou gerenciador de inicialização Vista)

leppie
fonte
realmente? qual é a memória máxima de processo padrão? E como mudar isso? Se eu jogar um jogo ou algo no meu PC, ele pode facilmente usar 2+ GB de um único EXE / Processo, não acho que esse seja o problema aqui.
m3ntat em
/ 3GB é um exagero para isso e pode causar muita instabilidade, pois muitos drivers assumem que os ponteiros do espaço do usuário sempre apontam para os 2GB inferiores.
jalf de
1
m3ntat: Não, no Windows de 32 bits, um único processo é restrito a 2 GB. Os 2 GB restantes do espaço de endereço são usados ​​pelo kernel.
jalf de