'System.OutOfMemoryException' se lanzó cuando todavía hay mucha memoria libre

92

Este es mi codigo:

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

Excepción: se lanzó una excepción del tipo 'System.OutOfMemoryException'.

Tengo 4GB de memoria en esta máquina. 2.5GB están libres cuando empiezo a ejecutar, claramente hay suficiente espacio en la PC para manejar los 762mb de 100000000 números aleatorios. Necesito almacenar tantos números aleatorios como sea posible dada la memoria disponible. Cuando entre en producción, habrá 12 GB en la caja y quiero hacer uso de ellos.

¿El CLR me limita a una memoria máxima predeterminada para comenzar? y como pido mas?

Actualizar

Pensé que dividir esto en trozos más pequeños y agregar incrementalmente a mis requisitos de memoria ayudaría si el problema se debe a la fragmentación de la memoria , pero no es así. No puedo superar un tamaño total de ArrayList de 256 MB, independientemente de lo que haga modificando 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()));                
  }
}

De mi 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
fuente
6
Recomendaría volver a diseñar su aplicación para que no tenga que usar tanta memoria. ¿Qué estás haciendo para que necesites cien millones de números todos en la memoria a la vez?
Eric Lippert
2
no has desactivado tu archivo de paginación o algo así, ¿verdad?
jalf
@EricLippert, me encuentro con esto cuando trabajo en el problema P vs. NP ( claymath.org/millenium-problems/p-vs-np-problem ). ¿Tiene alguna sugerencia para reducir el uso de la memoria de trabajo? (por ejemplo, serializar y almacenar fragmentos de datos en el disco duro, usar el tipo de datos C ++, etc.)
devinbost
@bosit este es un sitio de preguntas y respuestas. Si tiene una pregunta técnica específica sobre el código real, publíquela como pregunta.
Eric Lippert
@bostIT, el enlace para el problema P vs. NP en su comentario ya no es válido.
RBT

Respuestas:

140

Es posible que desee leer esto: " “ Fuera de memoria ”no se refiere a la memoria física " por Eric Lippert.

En resumen, y muy simplificado, "Sin memoria" no significa realmente que la cantidad de memoria disponible sea demasiado pequeña. La razón más común es que dentro del espacio de direcciones actual, no hay una porción contigua de memoria que sea lo suficientemente grande para servir la asignación deseada. Si tiene 100 bloques, cada uno de 4 MB de tamaño, eso no le ayudará cuando necesite un bloque de 5 MB.

Puntos clave:

  • En mi opinión, el almacenamiento de datos que llamamos "memoria de proceso" se visualiza mejor como un archivo masivo en disco .
  • La RAM puede verse simplemente como una optimización del rendimiento
  • La cantidad total de memoria virtual que consume su programa no es realmente muy relevante para su rendimiento.
  • "quedarse sin RAM" rara vez da como resultado un error de "memoria insuficiente". En lugar de un error, da como resultado un rendimiento deficiente porque el costo total del hecho de que el almacenamiento esté en el disco de repente se vuelve relevante.
Fredrik Mörk
fuente
"Si usted tiene 100 bloques, cada uno de 4 MB grandes, que no va a ayudar cuando se necesita una cuadra 5 MB" - creo que debería formularse con una pequeña corrección: "Si usted tiene 100 '' agujero bloques" .
OfirD
31

Compruebe que está creando un proceso de 64 bits, y no uno de 32 bits, que es el modo de compilación predeterminado de Visual Studio. Para hacer esto, haga clic derecho en su proyecto, Propiedades -> Construir -> destino de la plataforma: x64. Como cualquier proceso de 32 bits, las aplicaciones de Visual Studio compiladas en 32 bits tienen un límite de memoria virtual de 2 GB.

Los procesos de 64 bits no tienen esta limitación, ya que utilizan punteros de 64 bits, por lo que su espacio de direcciones máximo teórico (el tamaño de su memoria virtual) es de 16 exabytes (2 ^ 64). En realidad, Windows x64 limita la memoria virtual de procesos a 8TB. La solución al problema del límite de memoria es compilar en 64 bits.

Sin embargo, el tamaño del objeto en Visual Studio todavía está limitado a 2 GB de forma predeterminada. Podrá crear varias matrices cuyo tamaño combinado será superior a 2 GB, pero no puede crear matrices de más de 2 GB de forma predeterminada. Con suerte, si aún desea crear matrices de más de 2 GB, puede hacerlo agregando el siguiente código a su archivo app.config:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
Tecnología de cambio
fuente
Me salvaste. ¡Gracias!
Tee Zad Awk
25

No tiene un bloque continuo de memoria para asignar 762 MB, su memoria está fragmentada y el asignador no puede encontrar un agujero lo suficientemente grande para asignar la memoria necesaria.

  1. Puede intentar trabajar con / 3GB (como otros sugirieron)
  2. O cambie a un sistema operativo de 64 bits.
  3. O modifique el algoritmo para que no necesite una gran cantidad de memoria. quizás asigne algunos trozos más pequeños (relativamente) de memoria.
Shay Erlichmen
fuente
7

Como probablemente se habrá dado cuenta, el problema es que está intentando asignar un bloque grande de memoria contiguo, que no funciona debido a la fragmentación de la memoria. Si tuviera que hacer lo que estás haciendo, haría lo siguiente:

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

Luego, para obtener un índice particular, usaría randomNumbers[i / sizeB][i % sizeB].

Otra opción si siempre accede a los valores en orden podría ser utilizar el constructor sobrecargado para especificar la semilla. De esta manera, obtendría un número semi aleatorio (como el DateTime.Now.Ticks) almacenarlo en una variable, luego, cuando comience a revisar la lista, creará una nueva instancia aleatoria utilizando la semilla 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);
}

Es importante tener en cuenta que, si bien el blog vinculado en la respuesta de Fredrik Mörk indica que el problema generalmente se debe a la falta de espacio de direcciones , no enumera una serie de otros problemas, como la limitación de tamaño del objeto CLR de 2GB (mencionado en un comentario de ShuggyCoUk en el mismo blog), pasa por alto la fragmentación de la memoria y no menciona el impacto del tamaño del archivo de página (y cómo se puede abordar con el uso de la CreateFileMappingfunción ).

La limitación de 2 GB significa que randomNumbers debe ser inferior a 2 GB. Dado que las matrices son clases y tienen algo de sobrecarga en sí mismas, esto significa que una matriz doubledeberá ser más pequeña que 2 ^ 31. No estoy seguro de cuánto más pequeño que 2 ^ 31 tendría que ser la longitud, pero ¿la sobrecarga de una matriz .NET? indica 12-16 bytes.

La fragmentación de la memoria es muy similar a la fragmentación del disco duro. Es posible que tenga 2 GB de espacio de direcciones, pero a medida que crea y destruye objetos, habrá espacios entre los valores. Si estos espacios son demasiado pequeños para su objeto grande, y no se puede solicitar espacio adicional, obtendrá elSystem.OutOfMemoryException. Por ejemplo, si crea 2 millones de objetos de 1024 bytes, entonces está utilizando 1,9 GB. Si elimina todos los objetos en los que la dirección no es un múltiplo de 3, utilizará 0,6 GB de memoria, pero se distribuirá por todo el espacio de direcciones con bloques abiertos de 2024 bytes en el medio. Si necesita crear un objeto de 0,2 GB, no podrá hacerlo porque no hay un bloque lo suficientemente grande para que quepa y no se puede obtener espacio adicional (asumiendo un entorno de 32 bits). Las posibles soluciones a este problema son cosas como el uso de objetos más pequeños, la reducción de la cantidad de datos que almacena en la memoria o el uso de un algoritmo de administración de memoria para limitar / prevenir la fragmentación de la memoria. Cabe señalar que, a menos que esté desarrollando un programa grande que use una gran cantidad de memoria, esto no será un problema. También,

Dado que la mayoría de los programas solicitan memoria de trabajo del sistema operativo y no solicitan una asignación de archivos, estarán limitados por la RAM del sistema y el tamaño del archivo de página. Como se señala en el comentario de Néstor Sánchez (Néstor Sánchez) en el blog, con código administrado como C # estás pegado a la limitación de RAM / archivo de página y al espacio de direcciones del sistema operativo.


Eso fue mucho más largo de lo esperado. Ojalá ayude a alguien. Lo publiqué porque encontré la System.OutOfMemoryExceptionejecución de un programa x64 en un sistema con 24 GB de RAM a pesar de que mi matriz solo contenía 2 GB de material.

Trisped
fuente
5

Aconsejaría contra la opción de arranque de Windows / 3GB. Aparte de todo lo demás (es excesivo hacer esto para una aplicación que se porta mal y probablemente no resolverá su problema de todos modos), puede causar mucha inestabilidad.

Muchos controladores de Windows no se prueban con esta opción, por lo que muchos de ellos asumen que los punteros del modo de usuario siempre apuntan a los 2 GB inferiores del espacio de direcciones. Lo que significa que pueden romperse horriblemente con / 3GB.

Sin embargo, Windows normalmente limita un proceso de 32 bits a un espacio de direcciones de 2 GB. ¡Pero eso no significa que debas esperar poder asignar 2GB!

El espacio de direcciones ya está plagado de todo tipo de datos asignados. Está la pila y todos los ensamblados que están cargados, variables estáticas, etc. No hay garantía de que haya 800 MB de memoria contigua sin asignar en ningún lugar.

La asignación de 2 fragmentos de 400 MB probablemente funcionaría mejor. O 4 trozos de 200 MB. Es mucho más fácil encontrar espacio para asignaciones más pequeñas en un espacio de memoria fragmentado.

De todos modos, si va a implementar esto en una máquina de 12 GB de todos modos, querrá ejecutar esto como una aplicación de 64 bits, lo que debería resolver todos los problemas.

jalf
fuente
Dividir el trabajo en partes más pequeñas no parece ayudar a ver mi actualización anterior.
m3ntat
4

Cambiar de 32 a 64 bits funcionó para mí; vale la pena intentarlo si está en una PC de 64 bits y no necesita un puerto.

chris
fuente
1

Windows de 32 bits tiene un límite de memoria de proceso de 2 GB. La opción de arranque / 3GB que otros han mencionado hará que estos 3GB con solo 1gb restante para el uso del kernel del sistema operativo. Siendo realistas, si desea utilizar más de 2 GB sin problemas, se requiere un sistema operativo de 64 bits. Esto también soluciona el problema por el cual, aunque puede tener 4 GB de RAM física, el espacio de direcciones requerido para la tarjeta de video puede inutilizar una cantidad considerable de esa memoria, generalmente alrededor de 500 MB.

redcalx
fuente
1

En lugar de asignar una matriz masiva, ¿podría intentar utilizar un iterador? Estos se ejecutan con retraso, lo que significa que los valores se generan solo cuando se solicitan en una instrucción foreach; no debería quedarse sin memoria de esta manera:

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

Lo anterior generará tantos números aleatorios como desee, pero solo los generará cuando se soliciten a través de una declaración foreach. No se quedará sin memoria de esa manera.

Alternativamente, si debe tenerlos todos en un solo lugar, guárdelos en un archivo en lugar de en la memoria.

Judá Gabriel Himango
fuente
Enfoque interesante, pero necesito almacenar tanto como sea posible como un repositorio de números aleatorios en cualquier tiempo de inactividad del resto de mi aplicación, ya que esta aplicación se ejecuta en un reloj de 24 horas que admite múltiples regiones geográficas (múltiples ejecuciones de simulación de monte carlo), alrededor del 70% de la carga máxima de la CPU del día, las horas restantes a lo largo del día quiero almacenar números aleatorios en todo el espacio de memoria libre. El almacenamiento en disco es demasiado lento y, en cierto modo, anula cualquier ganancia que pudiera hacer al almacenar en búfer en este caché de memoria de números aleatorios.
m3ntat
0

Bueno, tengo un problema similar con un gran conjunto de datos y tratar de forzar a la aplicación a usar tantos datos no es realmente la opción correcta. El mejor consejo que puedo darte es que proceses tus datos en pequeñas porciones si es posible. Debido a que se trata de tantos datos, el problema volverá tarde o temprano. Además, no puede conocer la configuración de cada máquina que ejecutará su aplicación, por lo que siempre existe el riesgo de que la excepción ocurra en otra PC.

Francis B.
fuente
En realidad, conozco la configuración de la máquina, se está ejecutando en un solo servidor y puedo escribir esto para esas especificaciones. Esto es para una simulación masiva de monte carlo y estoy intentando optimizar almacenando números aleatorios por adelantado.
m3ntat
0

Tuve un problema similar, se debió a un StringBuilder.ToString ();

Ricardo Rix
fuente
no dejes que el constructor de cuerdas se vuelva tan grande
Ricardo Rix
0

Convierta su solución a x64. Si aún enfrenta un problema, otorgue la longitud máxima a todo lo que arroje una excepción como la siguiente:

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

Si no necesita el proceso de alojamiento de Visual Studio:

Desmarque la opción: Proyecto-> Propiedades-> Depurar-> Habilitar el proceso de alojamiento de Visual Studio

Y luego construye.

Si aún enfrenta el problema:

Vaya a Proyecto-> Propiedades-> Eventos de compilación-> Línea de comando de evento posterior a la compilación y pegue lo siguiente:

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

Ahora, construya el proyecto.

Yasir Arafat
fuente
-2

Aumente el límite del proceso de Windows a 3 gb. (a través de boot.ini o administrador de arranque de Vista)

leppie
fuente
¿De Verdad? ¿Cuál es la memoria de proceso máxima predeterminada? ¿Y cómo cambiarlo? Si juego un juego o algo en mi PC, puede usar fácilmente más de 2 GB de un solo EXE / Proceso, no creo que este sea el problema aquí.
m3ntat
/ 3GB es excesivo para esto y puede causar mucha inestabilidad ya que muchos conductores asumen que los indicadores del espacio de usuario siempre apuntan a los 2GB más bajos.
jalf
1
m3ntat: No, en Windows de 32 bits, un solo proceso está limitado a 2 GB. El kernel usa los 2 GB restantes del espacio de direcciones.
jalf