Frage

Ich habe versucht, den Aufwand des Headers auf einem .NET-Array (in einem 32-Bit-Prozess) mit diesem Code zu bestimmen:

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

Das Ergebnis war

    204800
    Array overhead: 12.478

in einem 32-Bit-Verfahren, das Gegenstand [1] soll die gleiche Größe wie int [1], aber in Wirklichkeit der Overhead-Sprünge von 3,28 Bytes

    237568
    Array overhead: 15.755

Wer weiß, warum?

(Übrigens, wenn jemand die gespannt, der Overhead für Nicht-Array-Objekte, zB (Objekt) i in der Schleife oben, über 8 Bytes ist (8,384). I gehört, es ist 16 Bytes in 64-Bit-Prozesse).

War es hilfreich?

Lösung

Hier ist ein etwas sauberere (IMO) kurzes, aber vollständiges Programm, das Gleiche zeigen:

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

Aber ich bekomme die gleichen Ergebnisse - der Aufwand für jeden Referenztyp Array ist 16 Bytes, während der Aufwand für einen beliebigen Wert Typ Array 12 Bytes ist. Ich versuche immer noch herauszufinden, warum das so ist, mit Hilfe der CLI-Spezifikation. Vergessen Sie nicht, dass die Referenztyp-Arrays sind covariant, die relevant sein können ...

EDIT: Mit Hilfe von cordbg kann ich Brians Antwort bestätigen - der Typ Zeiger eines Referenz-Typ Array das gleiche ist, unabhängig von dem tatsächlichen Elementtyp. Vermutlich gibt es einige funkiness in object.GetType() (die nicht virtuell ist, erinnern) zur Rechenschaft für diese.

Also, mit Code:

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

Wir arbeiten mit so etwas wie die folgenden am Ende:

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

Beachten Sie, dass ich den Speicher 1 Wort abgeladen haben vor der Wert der Variablen selbst.

Für x und y, die Werte sind:

  • Der Sync-Block, der für den Hash-Code Sperren (oder eine dünne Sperre - siehe Brians Kommentar)
  • Typ Zeiger
  • Size of array
  • Elementtyp Zeiger
  • NULL (erstes Element)

Für z, die Werte sind:

  • Sync-Block
  • Typ Zeiger
  • Size of array
  • 0x12345678 (erstes Element)

anderen Wert Typ-Arrays (byte [], int [] etc) am Ende mit anderen Typ Zeiger, während alle Referenztyp-Arrays die gleiche Art Zeiger verwenden, haben jedoch einen anderen Elementtyp-Zeiger. Der Elementtyp Zeiger ist der gleiche Wert wie Sie als Typ Zeiger für ein Objekt dieser Art finden würden. Wenn wir also in einem String-Objekt Speicher in denen oben laufen sehen, wäre es eine Art Zeiger von 0x00329134 hat.

Das Wort vor dem Typ Zeiger hat sicherlich etwas zu tun mit entweder dem Monitor oder dem Hash-Code: Aufruf GetHashCode() bevölkert, dass wenig Speicher, und ich glaube, das Standard-object.GetHashCode() erhält einen Sync-Block, um sicherzustellen, Hash-Code Einzigartigkeit für die gesamte Lebensdauer des Objekts. Allerdings nur lock(x){} tun hat nichts getan, was überrascht mich ...

All dies gilt nur für „Vektor“ Typen, nebenbei gesagt - in der CLR, ein „Vektor“ Typ ist ein eindimensionales Array mit einer Untergrenze von 0. Anderen Arrays wird ein anderes Layout hat - für eine Sache, bräuchten sie die untere gespeichert bound ...

Bisher hat dieses Experiment gewesen, aber hier ist die Vermutung - der Grund für das System die Art und Weise umgesetzt hat. Von hier aus bin Erraten ich wirklich nur.

  • Alle object[] Arrays können den gleichen JIT-Code teilen. Sie werden auf die gleiche Weise in Bezug auf die Speicherzuweisung, Array-Zugriff, Length Eigentum und (wichtiger) das Layout der Referenzen für die GC verhalten. Vergleichen Sie das mit dem Werttyp-Arrays, in denen unterschiedliche Werttypen unterschiedliche GC „Fußabdrücke“ haben kann (zum Beispiel könnte man ein Byte haben und dann einen Verweis, andere werden keine Verweise auf alle haben, etc).
  • Jedes Mal, wenn Sie einen Wert innerhalb eines object[] zuweisen die Laufzeitanforderungen zu überprüfen, ob es gültig ist. Es muss prüfen, ob die Art des Objekts, deren Referenz Sie für die neue Element Wert Verwendung mit dem Elementtyp des Arrays kompatibel ist. Zum Beispiel:

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

Dies ist die Kovarianz ich bereits erwähnt. Nun da dies für passieren wird jede einzelne Zuordnung , macht es Sinn, die Anzahl der Indirekt zu reduzieren. Insbesondere ich vermute, dass Sie nicht wirklich wollen, um den Cache blasen durch für jeden assigment des Typs Objekt gelingt mit dem Elementtyp zu erhalten. I verdächtigt (und meine x86-Assembler nicht gut genug ist, um dies zu überprüfen), dass der Test ist so etwas wie:

  • Ist der Wert auf einen NULL-Verweis kopiert werden? Wenn ja, ist das in Ordnung. (Fertig).
  • Holt den Typ Zeiger von the-Objekt der Referenzpunkte an.
  • Ist dieser Typ Zeiger gleich den Elementtyp Zeiger (einfache binäre Gleichheitsprüfung)? Wenn ja, ist das in Ordnung. (Fertig).
  • Ist dieser Typ Zeiger zuweisungskompatibel mit dem Elementtyp Zeiger? (Viel komplizierter zu überprüfen, mit Vererbung und beteiligten Schnittstellen.) Wenn ja, das ist in Ordnung -. Andernfalls eine Ausnahme aus,

Wenn wir die Suche in den ersten drei Schritten beenden können, gibt es nicht viel indirection - was gut für etwas, das so oft wie Array-Zuweisungen passieren wird. Keine dieser Anforderungen für Wertzuweisungen passieren, denn das statisch überprüfbar ist.

Also, das ist, warum ich Referenzart Arrays glauben sind etwas größer als Werttyp-Arrays.

Gute Frage - wirklich interessant zu tauchen Sie ein in es:)

Andere Tipps

Array ist ein Referenztyp. Alle Referenztypen tragen zwei weitere Wortfelder. Die Typenbezeichnung und ein Synchronisationsblock Indexfeld, das unter anderem verwendet wird, Sperre in der CLR zu implementieren. So ist die Art Overhead auf Referenztypen ist 8 Byte auf 32 Bit. Hinzu kommt, dass das Array selbst speichert auch die Länge, die noch 4 Bytes. Dies bringt die Gesamtoverhead zu 12 Byte.

Und ich gerade von Jon Skeet Antwort gelernt, Arrays von Referenztypen hat zusätzlich 4 Byte-Overhead. Dies kann unter Verwendung von WinDbg bestätigt werden. Es stellt sich heraus, dass das zusätzliche Wort eine andere Art Referenz für den Typ in dem Array gespeichert ist. Alle Arrays von Referenztypen werden intern als object[], mit dem zusätzlichen Hinweis auf die Art des tatsächlichen Objekt-Typs gespeichert. So ein string[] ist wirklich nur ein object[] mit einem zusätzlichen Typ Bezug auf den Typ string. Für Details siehe unten.

gespeicherten Werte in dem Arrays: Arrays von Referenztypen enthalten Referenzen auf Objekte, so dass jeder Eintrag in dem Array ist in der Größe einer Referenz (d.h. 4 Bytes auf 32 Bit). Arrays von Werttypen speichert die Werte inline und damit jedes Element wird die Größe der in Rede stehenden Art in Anspruch nehmen.

Diese Frage auch von Interesse sein können: C # List Größe vs double [] Größe

Gory Details

Betrachten Sie den folgenden Code

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

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

Ansetztechnologie WinDbg zeigt folgendes:

Lassen Sie uns zunächst einen Blick auf den Wert Typ Array.

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

Zuerst haben wir Dump das Array und das ein Element mit einem Wert von 42 kann die Größe ist 16 Byte zu sehen. Das ist 4 Bytes für den Wert selbst int32, 8 Bytes für die regelmäßigen Referenztyp-Overhead und weitere 4 Byte für die Länge des Arrays.

unbe Dump zeigt den Synchronisationsblock, die Methodentabelle für int[], die Länge, und der Wert von 42 (2a in hex). Beachten Sie, dass der Synchronisationsblock direkt vor dem Objekt Referenz befindet.

Als nächstes wollen wir einen Blick auf die string[] um herauszufinden, was das zusätzliche Wort verwendet wird.

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

Zuerst haben wir Dump das Array und die Zeichenfolge. Als nächstes werden wir Dump die Größe des string[]. Beachten Sie, dass hier WinDbg den Typ als System.Object[] auflistet. Die Objektgröße in diesem Fall enthält die Zeichenfolge selbst, so dass die Gesamtgröße die 20 von dem Array 40 sowie die für die Zeichenkette.

Durch das rohe Bytes der Instanz Dumping wir folgendes sehen: Zuerst haben wir den Synchronisationsblock haben, folgt dann die Methodentabelle für object[], dann die Länge des Arrays. Danach haben wir die zusätzlichen 4 Bytes mit dem Verweis auf die Methodentabelle für Zeichenfolge finden. Dies kann durch den dumpmt Befehl überprüft werden, wie oben gezeigt. Schließlich wir den einzigen Hinweis auf die tatsächliche String-Instanz finden.

Fazit

Der Overhead für Arrays können aufgeschlüsselt werden, wie folgt (auf 32 Bit, das ist)

  • 4 Byte Synchronisationsblock
  • 4 Byte für Methodentabelle (Typ Referenz) für das Array selbst
  • 4 Byte für Länge des Arrays
  • Arrays von Referenztypen fügt weitere 4 Bytes der Methodentabelle der aktuellen Elementtyp (Referenztyp-Arrays sind object[] unter der Motorhaube)
  • zu halten,

d. der Aufwand ist 12 Bytes für Werttyp Arrays und 16 Bytes für Arrays Referenztyp .

Ich glaube, Sie einige fehlerhafte Annahmen machen während der Messung, da die Speicherzuweisung (über GetTotalMemory) während der Schleife unterschiedlich sein können als die tatsächliche benötigte Speicher nur für die Arrays - kann der Speicher in größeren Blöcken zugeordnet werden, kann es sein, andere Objekte im Speicher, die während der Schleife zurückgewonnen werden, etc.

Hier einige Informationen für Sie auf Array-Overhead:

Da Heapverwaltung (da Sie mit GetTotalMemory beschäftigen) nur zuweisen ziemlich große Blöcke, die durch kleinere Stücke für Programmierer Zwecke von CLR zugewiesen Letztere werden.

Es tut mir leid für die offtopic, aber ich fand interessante Informationen über Speicher overheading gerade heute Morgen.

Wir haben ein Projekt, das große Datenmengen arbeitet (bis zu 2 GB). Als Hauptspeicher verwenden Dictionary<T,T> wir. Tausende von Wörterbüchern tatsächlich erstellt. Nach Änderung es List<T> für Schlüssel und List<T> für Werte (wir umgesetzt IDictionary<T,T> sich) die Speicherauslastung etwa 30-40% verringert werden.

Warum?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top