Domanda

I cercato di determinare l'overhead dell'intestazione su un array NET (in un processo a 32 bit) utilizzando questo codice:

long bytes1 = GC.GetTotalMemory(false);
object[] array = new object[10000];
    for (int i = 0; i < 10000; i++)
        array[i] = new int[1];
long bytes2 = GC.GetTotalMemory(false);
array[0] = null; // ensure no garbage collection before this point

Console.WriteLine(bytes2 - bytes1);
// Calculate array overhead in bytes by subtracting the size of 
// the array elements (40000 for object[10000] and 4 for each 
// array), and dividing by the number of arrays (10001)
Console.WriteLine("Array overhead: {0:0.000}", 
                  ((double)(bytes2 - bytes1) - 40000) / 10001 - 4);
Console.Write("Press any key to continue...");
Console.ReadKey();

Il risultato è stato

    204800
    Array overhead: 12.478

In un processo a 32 bit, un oggetto [1] dovrebbe essere la stessa dimensione come int [1], ma in realtà l'overhead salta da 3,28 byte

    237568
    Array overhead: 15.755

Qualcuno sa perché?

(A proposito, se qualcuno curioso, l'overhead per oggetti non matrice, ad esempio (oggetto) i nel ciclo precedente, è di circa 8 byte (8.384). Ho sentito che è 16 byte in processi a 64 bit.)

È stato utile?

Soluzione

Ecco un programma breve ma completa un po 'più ordinato (IMO) per dimostrare la stessa cosa:

using System;

class Test
{
    const int Size = 100000;

    static void Main()
    {
        object[] array = new object[Size];
        long initialMemory = GC.GetTotalMemory(true);
        for (int i = 0; i < Size; i++)
        {
            array[i] = new string[0];
        }
        long finalMemory = GC.GetTotalMemory(true);
        GC.KeepAlive(array);

        long total = finalMemory - initialMemory;

        Console.WriteLine("Size of each element: {0:0.000} bytes",
                          ((double)total) / Size);
    }
}

Ma ottengo gli stessi risultati - l'overhead per ogni matrice tipo di riferimento è di 16 byte, mentre l'overhead per qualsiasi matrice tipo di valore è di 12 byte. Sto ancora cercando di capire perché questo è, con l'aiuto delle specifiche CLI. Non dimenticate che gli array di tipo riferimento sono covarianti, che può essere rilevante ...

EDIT: Con l'aiuto di cordbg, posso confermare la risposta di Brian - il puntatore del tipo di una matrice di riferimento di tipo è lo stesso indipendentemente dal tipo di elemento effettivo. Presumibilmente c'è qualche funky in object.GetType() (che non è virtuale, ricordate) per tenere conto di questo.

Così, con il codice di:

object[] x = new object[1];
string[] y = new string[1];
int[] z = new int[1];
z[0] = 0x12345678;
lock(z) {}

Si finisce con qualcosa di simile al seguente:

Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>

Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z

Si noti che ho scaricato la parola di memoria 1 prima il valore della variabile stessa.

Per x e y, i valori sono:

  • Il blocco di sincronizzazione, utilizzato per bloccare il codice hash (o un Blocco sottile - vedi il commento di Brian)
  • tipo di puntatore
  • Dimensioni di array
  • Tipo elemento puntatore
  • Null riferimento (primo elemento)

Per z, i valori sono:

  • blocco Sync
  • tipo di puntatore
  • Dimensioni di array
  • 0x12345678 (primo elemento)

differenti matrici tipo di valore (byte [], int [] ecc) finiscono con diversi puntatori tipo, mentre tutti gli array del tipo di riferimento utilizzano lo stesso tipo puntatore, ma hanno un diverso tipo puntatore elemento. Il puntatore tipo di elemento è lo stesso valore come ci si trova come il puntatore del tipo per un oggetto di quel tipo. Quindi, se abbiamo guardato la memoria di un oggetto stringa nella corsa sopra, avrebbe un puntatore tipo di 0x00329134.

La parola prima che il puntatore tipo ha certamente qualcosa a che fare sia con il monitor o il codice hash: chiamando GetHashCode() popola quel po 'di memoria, e credo che il object.GetHashCode() predefinita ottiene un blocco di sincronizzazione per garantire unicità codice hash per tutta la vita dell'oggetto. Tuttavia, solo facendo lock(x){} non ha fatto nulla, che mi ha sorpreso ...

Tutto ciò è valido solo per tipi "vettoriale", tra l'altro - in CLR, un tipo "vettore" è una matrice unidimensionale con un basso-bound di 0. Altri array avrà un diverso layout - per prima cosa, che avevano bisogno del limite inferiore memorizzato ...

Finora questo è stato la sperimentazione, ma ecco le congetture - il motivo per il sistema in corso di attuazione il modo in cui ha. Da qui in poi, io davvero sto solo indovinando.

  • Tutti gli array object[] possono condividere lo stesso codice JIT. Stanno andando a comportarsi allo stesso modo in termini di allocazione di memoria, di accesso agli array, la proprietà Length e (soprattutto) la disposizione di riferimenti per il GC. Confronti che, con array tipo di valore, in cui diversi tipi di valore possono avere diverse "impronte" GC (per esempio si potrebbe avere un byte e quindi un punto di riferimento, gli altri non avranno riferimenti a tutti, ecc).
  • Ogni volta che si assegna un valore all'interno di un object[] il runtime deve controllare che sia valida. Ha bisogno di verificare che il tipo di oggetto il cui riferimento si sta utilizzando per il nuovo valore elemento è compatibile con il tipo di elemento della matrice. Per esempio:

    object[] x = new object[1];
    object[] y = new string[1];
    x[0] = new object(); // Valid
    y[0] = new object(); // Invalid - will throw an exception
    

Questa è la covarianza ho menzionato in precedenza. Ora dato che questo sta per accadere per ogni singolo incarico , ha senso per ridurre il numero di indirections. In particolare, ho il sospetto che non si vuole veramente far saltare in aria la cache dal dover andare al tipo di oggetto per ogni assigment per ottenere il tipo di elemento. I sospettato (e il mio assembly x86 non è abbastanza buono per verificare questo) che il test è qualcosa di simile:

  • è il valore da copiare un riferimento null? Se è così, va bene. (Fatto).
  • Scarica il puntatore tipo di the oggetto i punti di riferimento a.
  • E 'questo tipo puntatore lo stesso del puntatore tipo di elemento (semplice controllo di uguaglianza binario)? Se è così, va bene. (Fatto).
  • E 'questo tipo di puntatore assegnazione compatibile con il puntatore del tipo di elemento? (Controllo molto più complicato, con l'ereditarietà e interfacce coinvolti.) In caso affermativo, va bene -. In caso contrario, un'eccezione

Se siamo in grado di interrompere la ricerca nei primi tre passi, non c'è un sacco di riferimento indiretto - che è buono per qualcosa che sta per accadere più spesso le assegnazioni di array. Niente di tutto questo deve accadere per le assegnazioni tipo di valore, perché è staticamente verificabile.

Quindi, è per questo che credo che tipo di riferimento array sono leggermente più grande di array tipo di valore.

Grande questione - davvero interessante per approfondire esso:)

Altri suggerimenti

Array è un tipo di riferimento. Tutti i tipi di riferimento portano due campi di parole aggiuntive. Il riferimento tipo e un campo indice SyncBlock, che tra l'altro viene utilizzato per implementare serrature nel CLR. Così il tipo alto su tipi di riferimento è di 8 byte su 32 bit. In cima a che l'array memorizza anche la lunghezza che è un altro 4 byte. Questo porta l'overhead totale di 12 byte.

E ho appena appreso dalla risposta di Jon Skeet, array di tipi di riferimento ha un ulteriore 4 byte in testa. Ciò può essere confermato utilizzando WinDbg. Risulta che la parola aggiuntiva è un altro tipo di riferimento per il tipo memorizzato nella matrice. Tutti gli array di tipi di riferimento sono memorizzati internamente come object[], con il riferimento supplementare all'oggetto tipo di tipo effettivo. Quindi un string[] è in realtà solo un object[] con un tipo di ulteriore riferimento al tipo string. Per i dettagli si prega di vedere sotto.

valori memorizzati in array: Array di tipi di riferimento tengono riferimenti agli oggetti, in modo che ogni voce nella matrice è la dimensione di un riferimento (cioè 4 byte su 32 bit). Array di tipi di valore memorizzano la linea valori e quindi ogni elemento assumerà la dimensione del tipo in questione.

Questa domanda può anche essere di interesse: C # List dimensioni vs double [] dimensione

Gory Dettagli

Si consideri il seguente codice

var strings = new string[1];
var ints = new int[1];

strings[0] = "hello world";
ints[0] = 42;

Collegamento WinDbg mostra il seguente:

Per prima cosa diamo un'occhiata a matrice tipo di valore.

0:000> !dumparray -details 017e2acc 
Name: System.Int32[]
MethodTable: 63b9aa40
EEClass: 6395b4d4
Size: 16(0x10) bytes
Array: Rank 1, Number of elements 1, Type Int32
Element Methodtable: 63b9aaf0
[0] 017e2ad4
    Name: System.Int32
    MethodTable 63b9aaf0
    EEClass: 6395b548
    Size: 12(0xc) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    63b9aaf0  40003f0        0         System.Int32  1 instance       42 m_value <=== Our value

0:000> !objsize 017e2acc 
sizeof(017e2acc) =           16 (        0x10) bytes (System.Int32[])

0:000> dd 017e2acc -0x4
017e2ac8  00000000 63b9aa40 00000001 0000002a <=== That's the value

Per prima cosa scarichiamo matrice e quella elemento con valore di 42. Come si può vedere la dimensione è di 16 byte. Cioè 4 byte per il valore int32 stessa, 8 byte per regolare tipo di riferimento testa e altri 4 byte per la lunghezza della matrice.

Il dump raw mostra la SyncBlock, la tabella dei metodi per int[], la lunghezza e il valore di 42 (2a in esadecimale). Si noti che lo SyncBlock si trova proprio di fronte il riferimento all'oggetto.

Avanti, diamo un'occhiata al string[] per scoprire che cosa la parola aggiuntiva viene utilizzato per.

0:000> !dumparray -details 017e2ab8 
Name: System.String[]
MethodTable: 63b74ed0
EEClass: 6395a8a0
Size: 20(0x14) bytes
Array: Rank 1, Number of elements 1, Type CLASS
Element Methodtable: 63b988a4
[0] 017e2a90
    Name: System.String
    MethodTable: 63b988a4
    EEClass: 6395a498
    Size: 40(0x28) bytes <=== Size of the string
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     hello world    
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    63b9aaf0  4000096        4         System.Int32  1 instance       12 m_arrayLength
    63b9aaf0  4000097        8         System.Int32  1 instance       11 m_stringLength
    63b99584  4000098        c          System.Char  1 instance       68 m_firstChar
    63b988a4  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  00226438:017e1198 <<
    63b994d4  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  00226438:017e1760 <<

0:000> !objsize 017e2ab8 
sizeof(017e2ab8) =           60 (        0x3c) bytes (System.Object[]) <=== Notice the underlying type of the string[]

0:000> dd 017e2ab8 -0x4
017e2ab4  00000000 63b74ed0 00000001 63b988a4 <=== Method table for string
017e2ac4  017e2a90 <=== Address of the string in memory

0:000> !dumpmt 63b988a4
EEClass: 6395a498
Module: 63931000
Name: System.String
mdToken: 02000024  (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0x10
ComponentSize: 0x2
Number of IFaces in IFaceMap: 7
Slots in VTable: 196

Per prima cosa scarichiamo la matrice e la stringa. Poi abbiamo scarichiamo le dimensioni del string[]. Si noti che WinDbg elenca il tipo come System.Object[] qui. Le dimensioni dell'oggetto in questo caso prevede la stringa stessa, quindi la dimensione totale è la 20 dalla matrice più il 40 per la stringa.

scarico il byte grezzi dell'istanza possiamo vedere la seguente: Innanzitutto abbiamo la SyncBlock, segue poi la tabella dei metodi per object[], allora la lunghezza della matrice. Dopo che troviamo i 4 byte supplementari con il riferimento alla tabella dei metodi per la stringa. Ciò può essere verificato dal comando dumpmt come mostrato sopra. Infine troviamo il singolo riferimento all'istanza stringa effettiva.

In conclusione

L'overhead per array può essere suddiviso come segue (da 32 bit che è)

  • 4 byte SyncBlock
  • 4 byte per tabella Metodo (tipo di riferimento) per la matrice stessa
  • 4 byte per la lunghezza di array
  • Arrays di tipi di riferimento aggiunge altri 4 byte per contenere la tabella procedimento del tipo elemento effettivo (tipo array di riferimento sono object[] sotto il cofano)

vale a dire. l'overhead è 12 byte per matrici di tipo valore e 16 byte per gli array del tipo di riferimento .

Credo che si stanno facendo alcune ipotesi errate durante la misurazione, come l'allocazione di memoria (via GetTotalMemory) durante il ciclo può essere diverso da quello della memoria effettiva necessaria solo per gli array - la memoria può essere allocata in blocchi più grandi, ci possono essere altri oggetti in memoria che sono recuperati durante il ciclo, ecc.

Ecco alcune informazioni per voi su array di testa:

Poiché la gestione mucchio (dal momento che si tratta con GetTotalMemory) può allocare solo piuttosto grandi blocchi, che queste ultime vengono assegnati da blocchi più piccoli per scopi programmatore dal CLR.

Mi dispiace per l'offtopic ma ho trovato informazioni interessanti sulla memoria overheading solo la mattina di oggi.

Abbiamo un progetto che opera enorme quantità di dati (fino a 2GB). In qualità di principale stoccaggio usiamo Dictionary<T,T>. Migliaia di dizionari vengono creati in realtà. Dopo modificarlo per List<T> per le chiavi e List<T> per i valori (abbiamo implementato stessi IDictionary<T,T>) l'utilizzo della memoria è diminuita in circa il 30-40%.

Perché?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top