2013-05-09 6 views
5

Ho un'app in cui sto cercando di creare un "cubo" molto grande di byte. Un array tridimensionale (~ 1000x1000x500) salva tutti i valori a cui sono interessato, ma sto eliminando problemi di memoria. Mentre ciò era previsto, il COMPORTAMENTO dei vari messaggi di OOM che ho ricevuto è stato abbastanza confuso. Primo:Confusione di memoria

Foo[,,] foo1 = new Foo[1000, 1000, 500];

riesce con un errore OOM, ma questo non:
Foo[,,] foo1 = new Foo[250, 1000, 500];
Foo[,,] foo2 = new Foo[250, 1000, 500];
Foo[,,] foo3 = new Foo[250, 1000, 500];
Foo[,,] foo4 = new Foo[250, 1000, 500];

Non dovrebbe questi due insiemi di codici consumare essenzialmente la stessa quantità di memoria?

Inoltre, in origine ricevevo il messaggio di errore quando ~ 1.5 GB era stato consumato, ma supponevo che passando a un'applicazione a 64 bit avrei potuto usare MOLTA più memoria prima di fallire.

Sono in esecuzione in limiti di spazio dello stack? E se è così, come posso istanziare questa struttura interamente nell'heap senza che debba mai esistere (come una singola entità) nello stack?

Grazie in anticipo, non vedo l'ora di accendere la luce che chiunque possa ignorare questo comportamento.

+0

Penso che se si compila per 64x si sta andando bene. Penso anche che vai oltre il limite per un oggetto con il primo array. quindi sì, le due istruzioni prendono approssimativamente lo stesso spazio in memoria ma è solo perché si supera il limite per oggetto di .net (almeno per 32x) –

+3

L'uso di "nuovo" lo metterà in pila. Probabilmente funziona quando lo dividi perché non stai più richiedendo 500 milioni di pezzi _contiguous_ "Foo". –

+1

Sei proprio sicuro di aver bisogno di un array pre-allocato di 'Foo'.Spiegare cosa si sta cercando di ottenere usando un array di tale dimensione, ci possono essere modi migliori per gestirlo (forse un dizionario annidato che contiene solo valori popolati è necessario 'Dictionary >>') –

risposta

3

EDIT

Stavo riflettendo su un'attuazione più completamente descritto della mia risposta, ho pensato di accodamento. Non sono sicuro che la parallelizzazione possa essere d'aiuto, forse dipende dallo initializer.

using System; 
using System.Linq; 

public static T[][][] NewJagged<T>(
     int h, 
     int w, 
     ing d, 
     Func<int, int, int, T> initializer = null, 
     bool parallelize = true) 
{ 
    if (h < 1) 
    { 
     throw new ArgumentOutOfRangeException("h", h, "Dimension less than 1.") 
    } 

    if (w < 1) 
    { 
     throw new ArgumentOutOfRangeException("w", w, "Dimension less than 1.") 
    } 

    if (d < 1) 
    { 
     throw new ArgumentOutOfRangeException("d", d, "Dimension less than 1.") 
    } 

    if (initializer == null) 
    { 
     initializer = (i, j, k) => default(T); 
    } 

    if (parallelize) 
    { 
     return NewJaggedParalellImpl(h, w, d, initializer); 
    } 

    return NewJaggedImpl(h, w, d, initializer); 
} 

private static T[][][] NewJaggedImpl<T>(
     int h, 
     int w, 
     int d, 
     Func<int, int, int, T> initializer) 
{ 
    var result = new T[h][][]; 
    for (var i = 0; i < h; i++) 
    { 
     result[i] = new T[w][]; 
     for (var j = 0; j < w; j++) 
     { 
      result[i][j] = new T[d]; 
      for (var k = 0; k < d; k++) 
      { 
       result[i][j][k] = initializer(i, j, k); 
      } 
     } 
    } 

    return result; 
} 

private static T[][][] NewJaggedParalellImpl<T>(
     int h, 
     int w, 
     int d, 
     Func<int, int, int, T> initializer) 
{ 
    var result = new T[h][][]; 
    ParallelEnumerable.Range(0, h).ForAll(i => 
    { 
     result[i] = new T[w][]; 
     ParallelEnumerable.Range(0, w).ForAll(j => 
     { 
      result[i][j] = new T[d]; 
      ParallelEnumerable.Range(0, d).ForAll(k => 
      { 
       result[i][j][k] = initializer(i, j, k); 
      }); 
     }); 
    }); 

    return result; 
}    

Questo rende la funzione del tutto generico, ma ancora si lascia la sintassi semplice,

var foo1 = NewJagged<Foo>(1000, 1000, 500); 

Si potrebbe comunque ottenere l'immaginazione e popolare in paralell su inizializzazione,

var foo2 = NewJagged<Foo>(
    1000, 
    1000, 
    5000, 
    (i, j, k) => 
     { 
      var pos = (i * 1000 * 500) + (j * 500) + k; 
      return ((pos % 2) == 0) ? new Foo() : null; 
     }); 

in questo caso , popolando con un effetto a scacchiera (credo);


Questo può non sembrare inizialmente per rispondere il vostro problema ...

Se si ha una funzione, qualcosa di simile

public static T[][][] ThreeDimmer<T>(int h, int w, int d) where T : new() 
{ 
    var result = new T[h][][]; 
    for (var i = 0; i < h; i++) 
    { 
     result[i] = new T[w][]; 
     for (var j = 0; j < w; j++) 
     { 
      result[i][j] = new T[d]; 
      for (var k = 0; k < d; k++) 
      { 
       result[i][j][k] = new T(); 
      } 
     } 
    } 

    return result; 
} 

allora si avrebbe incapsulato l'inizializzazione di un 3 dimensionale matrice frastagliata di tipi di riferimento. Ciò consentirebbe di fare,

Foo[][][] foo1 = ThreeDimmer<Foo>(1000, 1000, 500); 

Questo permetterebbe di evitare i problemi di frammentazione della memoria di array multidimensionali. Eviterebbe anche other pitfalls and limitations, offrendo invece una matrice più veloce e più flessibile.

+0

Wow - questo è stato DI gran lunga il post più utile che abbia mai fatto. Letteralmente ogni singola risposta è stata di enorme aiuto. Questa soluzione, combinata con l'aggiornamento al framework 4.5, ha risolto tutti i miei problemi! Grazie a tutti! – eejai42

3

Per me, sembra un problema di frammentazione della memoria. Inoltre, notare che new utilizza l'heap.

Nel primo esempio si richiede una porzione di memoria molto consistente ed è possibile che il sistema operativo, indipendentemente dalla quantità di RAM presente nel sistema, potrebbe non essere in grado di trovare un blocco contiguo di memoria di grandi dimensioni .

Le allocazioni più piccole funzionano perché i più piccoli blocchi contigui di memoria sono sempre più numerosi di quelli più grandi.

+0

Nitpicking: In C# 'new' non è necessariamente uguale a un'allocazione dell'heap. –

4

Questo è, quando si assegna un array, è necessario memoria contigua.

È più probabile trovare 10 blocchi di memoria contigua nella RAM con una dimensione di 10 MB ciascuno rispetto a 1 blocco enorme di 100 MB.

Diciamo che avevi 100 byte di RAM con gli indirizzi da 0 a 99. Se hai assegnato un blocco di memoria con dimensione 1 byte alla posizione 23 per esempio, anche se hai 99 byte di RAM rimanenti, se volevi allocare un blocco di memoria con una dimensione di 99 byte, non si riuscirà perché la memoria deve essere contigua per. Il blocco più grande che potresti assegnare in questo caso sarebbe lungo 76 byte.

+0

Ottima spiegazione pure. Non avevo considerato l'impatto del fatto che doveva essere contiguo. Grazie! – eejai42

4

Un'app a 32 bit è limitata a 4 GB di spazio indirizzo, quindi questo è il limite superiore. Se il processo viene eseguito su un sistema operativo a 32 bit, questo è ulteriormente limitato a 2 o 3 GB a seconda dell'impostazione dell'app e del sistema operativo.

Nel primo caso si sta allocando un grande array. Negli array .NET sono allocati nell'heap, quindi lo spazio dello stack non è il problema qui. Dati i numeri nella domanda, suppongo che l'array sia di circa 1,5 GB. Per gestire il CLR è necessario un blocco di memoria contiguo. Se si assegna lo stesso numero di byte in blocchi più piccoli (come nel secondo esempio) il runtime avrà una migliore possibilità di allocare la memoria secondo necessità.

Ancora, con almeno 2 GB disponibili si potrebbe pensare che 1,5 GB non dovrebbero essere un problema, ma il fatto è che un processo non utilizza lo spazio degli indirizzi strettamente da un'estremità all'altra. Una volta che le DLL sono state caricate nel processo, lo spazio degli indirizzi è frammentato.

Nella mia esperienza le app gestite a 32 bit (su sistema operativo a 32 bit) sono in genere limitate a circa 1,5 GB di spazio heap, il che spiegherebbe la OOM che si sta vedendo.

L'esecuzione della stessa app su SO a 64 bit consente all'app di accedere all'intero spazio di indirizzi di 4 GB, che influirà sullo spazio disponibile per l'heap gestito.

Trasformando l'app in un'app a 64 bit, si modifica la dimensione dello spazio indirizzo da 4 GB a 8 TB. Tuttavia, anche in questo caso è necessario tenere presente che la dimensione massima predefinita di qualsiasi oggetto .NET è 2 GB. Vedi this question per i dettagli.