Finden Sie die Größe der Objektinstanz in Bytes in c #


112

Für jede beliebige Instanz (Sammlungen verschiedener Objekte, Kompositionen, einzelner Objekte usw.)

Wie kann ich die Größe in Bytes bestimmen?

(Ich habe derzeit eine Sammlung verschiedener Objekte und versuche, die aggregierte Größe zu bestimmen.)

EDIT: Hat jemand eine Erweiterungsmethode für Object geschrieben, die dies tun könnte? Das wäre ziemlich ordentlich imo.



Antworten:


59

Zuallererst eine Warnung: Was folgt, ist ausschließlich im Bereich hässlicher, undokumentierter Hacks. Verlassen Sie sich nicht darauf, dass dies funktioniert - auch wenn es jetzt für Sie funktioniert, funktioniert es möglicherweise morgen mit kleineren oder größeren .NET-Updates nicht mehr.

Sie können die Informationen in diesem Artikel zu CLR-Interna verwenden. MSDN Magazine Ausgabe 2005 Mai - Einführung in .NET Framework- Interna, um zu sehen, wie die CLR Laufzeitobjekte erstellt - Zuletzt habe ich überprüft, dass sie noch anwendbar ist. Hier ist, wie dies gemacht wird (es ruft das interne Feld "Basic Instance Size" über TypeHandleden Typ ab).

object obj = new List<int>(); // whatever you want to get the size of
RuntimeTypeHandle th = obj.GetType().TypeHandle;
int size = *(*(int**)&th + 1);
Console.WriteLine(size);

Dies funktioniert mit 3.5 SP1 32-Bit. Ich bin mir nicht sicher, ob die Feldgrößen bei 64-Bit gleich sind. Möglicherweise müssen Sie die Typen und / oder Offsets anpassen, wenn dies nicht der Fall ist.

Dies funktioniert für alle "normalen" Typen, für die alle Instanzen dieselben, genau definierten Typen haben. Diejenigen, für die dies nicht zutrifft, sind mit Sicherheit Arrays und Strings, und ich glaube auch StringBuilder. Für sie müssen Sie die Größe aller enthaltenen Elemente zu ihrer Basisinstanzgröße hinzufügen.


Nein. Es gibt keinen "richtigen" Weg, dies zu tun, da es sich nicht in erster Linie um eine gut erzogene .NET-Anwendung handelt. Das oben Gesagte wirkt sich direkt auf die internen Datenstrukturen einer bestimmten Implementierung von CLR aus (die sich beispielsweise in der nächsten Version von .NET leicht ändern können).
Pavel Minaev

3
soll das in C # funktionieren oder nur in C ++? Es ist bisher nicht glücklich in C #, dass ich es versucht habe:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
Die .NET 4-Version benötigt nicht einmal unsicheren Code: Funktioniert Marshal.ReadInt32(type.TypeHandle.Value, 4)für x86 und x64. Ich habe nur Struktur- und Klassentypen getestet. Beachten Sie, dass dies die Boxgröße für Werttypen zurückgibt . @ Pavel Vielleicht könnten Sie Ihre Antwort aktualisieren.
jnm2

2
gut @ sab669, ersetzt typemit obj.GetType()in seinem Beispiel. Es spielt keine Rolle, welches Framework Sie verwenden, nur welche CLR (v2 oder v4 oder CoreCLR). Ich habe dies bei CoreCLR nicht versucht.
jnm2

2
@SamGoldberg Das manuelle Berechnen ist eine Menge Arbeit mit einer Million Randfällen. Sizeof gibt die statische Größe eines Objekts an, nicht den Speicherverbrauch eines Laufzeitdiagramms von Objekten. Die Speicher- und CPU-Profilerstellung von VS2017 ist sehr gut, ebenso wie die von ReSharper und anderen Tools, und das würde ich zum Messen verwenden.
jnm2

20

Sie können die Größe möglicherweise approximieren, indem Sie so tun, als würden Sie sie mit einem binären Serializer serialisieren (aber die Ausgabe in Vergessenheit geraten lassen), wenn Sie mit serialisierbaren Objekten arbeiten.

class Program
{
    static void Main(string[] args)
    {
        A parent;
        parent = new A(1, "Mike");
        parent.AddChild("Greg");
        parent.AddChild("Peter");
        parent.AddChild("Bobby");

        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
           new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        SerializationSizer ss = new SerializationSizer();
        bf.Serialize(ss, parent);
        Console.WriteLine("Size of serialized object is {0}", ss.Length);
    }
}

[Serializable()]
class A
{
    int id;
    string name;
    List<B> children;
    public A(int id, string name)
    {
        this.id = id;
        this.name = name;
        children = new List<B>();
    }

    public B AddChild(string name)
    {
        B newItem = new B(this, name);
        children.Add(newItem);
        return newItem;
    }
}

[Serializable()]
class B
{
    A parent;
    string name;
    public B(A parent, string name)
    {
        this.parent = parent;
        this.name = name;
    }
}

class SerializationSizer : System.IO.Stream
{
    private int totalSize;
    public override void Write(byte[] buffer, int offset, int count)
    {
        this.totalSize += count;
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return true; }
    }

    public override void Flush()
    {
        // Nothing to do
    }

    public override long Length
    {
        get { return totalSize; }
    }

    public override long Position
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    public override long Seek(long offset, System.IO.SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }
}

6
Dies kann natürlich zu einer Mindestgröße führen, sagt jedoch nichts über die Größe des Speichers aus.
John Saunders

Lol, die nächste Glühbirne, die ich hatte, bevor ich zurückkam, um die Antworten zu überprüfen, benutzte den binären Serializer. John, wie würde dir das nicht die tatsächliche Größe im Speicher geben?
Janie

2
Es würde Ihnen die serialisierte Größe geben, die der Größe entspricht, die der Serializer für "Serializer" -Zwecke wollte. Diese unterscheiden sich wahrscheinlich von den "Sit-in-Memory" -Zwecken. Möglicherweise speichert der Serializer beispielsweise kleinere Ganzzahlen in drei Bytes.
John Saunders

4
Wie ich schon sagte, es ist nur eine Annäherung. Es ist nicht perfekt, aber ich würde nicht zustimmen, dass es Ihnen "nichts" über die Größe im Speicher sagt. Ich würde sagen, dass es Ihnen eine Idee gab - größere Serialisierungen würden im Allgemeinen mit größeren In-Memory-Größen korrelieren. Es gibt eine Beziehung.
BlueMonkMN

Ich stimme zu - es ist nützlich, eine Schätzung der Größe eines .NET-Objektdiagramms zu erhalten.
Craig Shearer

8

Für nicht verwaltete Typen, auch Werttypen genannt, Strukturen:

        Marshal.SizeOf(object);

Für verwaltete Objekte ist eine Annäherung umso näher, je näher ich komme.

        long start_mem = GC.GetTotalMemory(true);

        aclass[] array = new aclass[1000000];
        for (int n = 0; n < 1000000; n++)
            array[n] = new aclass();

        double used_mem_median = (GC.GetTotalMemory(false) - start_mem)/1000000D;

Verwenden Sie keine Serialisierung. Ein binärer Formatierer fügt Header hinzu, sodass Sie Ihre Klasse ändern und eine alte serialisierte Datei in die geänderte Klasse laden können.

Außerdem wird weder die tatsächliche Größe des Speichers angegeben noch die Speicherausrichtung berücksichtigt.

[Bearbeiten] Wenn Sie BiteConverter.GetBytes (Prop-Wert) rekursiv für jede Eigenschaft Ihrer Klasse verwenden, erhalten Sie den Inhalt in Bytes, der das Gewicht der Klasse oder der Referenzen nicht berücksichtigt, aber der Realität viel näher kommt. Ich würde empfehlen, ein Byte-Array für Daten und eine nicht verwaltete Proxy-Klasse zu verwenden, um mithilfe der Zeigerumwandlung auf Werte zuzugreifen, wenn die Größe eine Rolle spielt Dies ist erheblich schneller, da die Minimierung der Größe zum Lesen aus dem RAM eine größere Auswirkung hat als die Nichtausrichtung.


5

Dies gilt nicht für die aktuelle .NET-Implementierung. Beachten Sie jedoch, dass sich die zugewiesene Größe eines Objekts während der gesamten Lebensdauer des Programms ändern kann. Beispielsweise müssen einige Generations-Garbage-Collectors (wie der Generational / Ulterior Reference Counting Hybrid-Collector ) nur bestimmte Informationen speichern, nachdem ein Objekt aus dem Kinderzimmer in den ausgereiften Bereich verschoben wurde.

Dies macht es unmöglich, eine zuverlässige, generische API zu erstellen, um die Objektgröße verfügbar zu machen.


Interessant. Was tun Menschen also, um die Größe ihrer Objekte / Objektsammlungen dynamisch zu bestimmen?
Janie

2
Es kommt darauf an, wofür sie es brauchen. Wenn für P / Invoke (natives Code-Interop) Marshal.SizeOf (typeof (T)) verwendet wird. Wenn für die Speicherprofilerstellung ein separater Profiler verwendet wird, der mit der Ausführungsumgebung zusammenarbeitet, um die Informationen bereitzustellen. Wenn Sie an der Elementausrichtung in einem Array interessiert sind, können Sie den SizeOf IL-Opcode in einer DynamicMethod verwenden (ich glaube nicht, dass es im .NET Framework einen einfacheren Weg dafür gibt).
Sam Harwell

5

sichere Lösung mit einigen Optimierungen CyberSaving / MemoryUsage-Code . ein Fall:

/* test nullable type */      
TestSize<int?>.SizeOf(null) //-> 4 B

/* test StringBuilder */    
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append("わたしわたしわたしわ");
TestSize<StringBuilder>.SizeOf(sb ) //-> 3132 B

/* test Simple array */    
TestSize<int[]>.SizeOf(new int[100]); //-> 400 B

/* test Empty List<int>*/    
var list = new List<int>();  
TestSize<List<int>>.SizeOf(list); //-> 205 B

/* test List<int> with 100 items*/
for (int i = 0; i < 100; i++) list.Add(i);
TestSize<List<int>>.SizeOf(list); //-> 717 B

Es funktioniert auch mit Klassen:

class twostring
{
    public string a { get; set; }
    public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B

Dies ist der Ansatz, den ich auch verfolgen würde. Sie können eine Reihe zuvor angetroffener Objekte in ein Diagramm einfügen, um a) eine unendliche Rekursion und b) das zweimalige Hinzufügen desselben Speichers zu vermeiden.
Mafu

4

Dies ist zur Laufzeit nicht möglich.

Es gibt jedoch verschiedene Speicherprofiler, die die Objektgröße anzeigen.

BEARBEITEN : Sie können ein zweites Programm schreiben, das das erste mithilfe der CLR-Profiling-API profiliert und über Remoting oder ähnliches mit ihm kommuniziert.


17
Wenn dies zur Laufzeit nicht möglich ist, wie stellen die Speicherprofiler die Informationen bereit?
Janie

2
Mithilfe der Profiling-API. Ein Programm kann sich jedoch nicht profilieren
SLaks

Interessant. Was wäre, wenn ich wollte, dass der Code Fälle behandelt, in denen Objekte zu viel Speicher verbrauchen?
Janie

4
Dann würden Sie sich mit selbstbewusster Software beschäftigen, und ich hätte große Angst. :-) Im Ernst, "Single Responsibility Principal" - lassen Sie das Programm das Programm sein, lassen Sie einen anderen Code auf Objekte achten, die zu viel Speicher beanspruchen.
John Saunders

2
@Janie: Sie würden auch Annahmen über die Bedeutung der Größe und ihre Beziehung zur Leistung treffen. Ich denke, Sie möchten ein echter CLR-Leistungsexperte auf niedriger Ebene sein (der Typ, der bereits über die Profiling-API Bescheid weiß), bevor Sie dies tun. Andernfalls wenden Sie Ihre früheren Erfahrungen möglicherweise auf eine Situation an, in der sie nicht zutreffen.
John Saunders


2

AFAIK können Sie nicht, ohne die Größe jedes Mitglieds in Bytes zu zählen. Aber zählt auch hier die Größe eines Elements (wie Elemente in einer Sammlung) zur Größe des Objekts oder ein Zeiger auf dieses Element zur Größe des Objekts? Kommt darauf an, wie du es definierst.

Ich bin zuvor auf diese Situation gestoßen, in der ich die Objekte in meinem Cache basierend auf dem von ihnen verbrauchten Speicher einschränken wollte.

Nun, wenn es einen Trick gibt, würde ich mich freuen, davon zu erfahren!


2

Für Werttypen können Sie verwenden Marshal.SizeOf. Natürlich wird die Anzahl der Bytes zurückgegeben, die zum Marshalling der Struktur im nicht verwalteten Speicher erforderlich sind. Dies wird nicht unbedingt von der CLR verwendet.


SizeOf (Object) ist in zukünftigen Versionen möglicherweise nicht verfügbar. Verwenden Sie stattdessen SizeOf <T> (). Weitere Informationen finden Sie unter go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Sie können Reflection verwenden, um alle Informationen zu öffentlichen Mitgliedern oder Eigenschaften zu sammeln (abhängig vom Objekttyp). Es gibt jedoch keine Möglichkeit, die Größe zu bestimmen, ohne die einzelnen Daten des Objekts durchzugehen.


1

Für alle, die nach einer Lösung suchen, die keinen [Serializable]Unterricht erfordert und deren Ergebnis eine Annäherung anstelle einer exakten Wissenschaft ist. Die beste Methode, die ich finden konnte, ist die JSON-Serialisierung in einen Memorystream mithilfe der UTF32-Codierung.

private static long? GetSizeOfObjectInBytes(object item)
{
    if (item == null) return 0;
    try
    {
        // hackish solution to get an approximation of the size
        var jsonSerializerSettings = new JsonSerializerSettings
        {
            DateFormatHandling = DateFormatHandling.IsoDateFormat,
            DateTimeZoneHandling = DateTimeZoneHandling.Utc,
            MaxDepth = 10,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        };
        var formatter = new JsonMediaTypeFormatter { SerializerSettings = jsonSerializerSettings };
        using (var stream = new MemoryStream()) { 
            formatter.WriteToStream(item.GetType(), item, stream, Encoding.UTF32);
            return stream.Length / 4; // 32 bits per character = 4 bytes per character
        }
    }
    catch (Exception)
    {
        return null;
    }
}

Nein, dies gibt Ihnen nicht die genaue Größe, die im Speicher verwendet werden würde. Wie bereits erwähnt, ist dies nicht möglich. Aber es gibt Ihnen eine grobe Schätzung.

Beachten Sie, dass dies auch ziemlich langsam ist.


1

Von Pavel und jnm2:

private int DumpApproximateObjectSize(object toWeight)
{
   return Marshal.ReadInt32(toWeight.GetType().TypeHandle.Value, 4);
}

Nebenbei bemerkt, seien Sie vorsichtig, da es nur mit zusammenhängenden Speicherobjekten funktioniert


1

Ich habe einen Benchmark-Test für verschiedene Sammlungen in .NET erstellt: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Die Ergebnisse für .NET Core 2.2 mit 1.000.000 Objekten mit 3 zugewiesenen Eigenschaften sind wie folgt:

Testing with string: 1234567
Hashtable<TestObject>:                                     184 672 704 B
Hashtable<TestObjectRef>:                                  136 668 560 B
Dictionary<int, TestObject>:                               171 448 160 B
Dictionary<int, TestObjectRef>:                            123 445 472 B
ConcurrentDictionary<int, TestObject>:                     200 020 440 B
ConcurrentDictionary<int, TestObjectRef>:                  152 026 208 B
HashSet<TestObject>:                                       149 893 216 B
HashSet<TestObjectRef>:                                    101 894 384 B
ConcurrentBag<TestObject>:                                 112 783 256 B
ConcurrentBag<TestObjectRef>:                               64 777 632 B
Queue<TestObject>:                                         112 777 736 B
Queue<TestObjectRef>:                                       64 780 680 B
ConcurrentQueue<TestObject>:                               112 784 136 B
ConcurrentQueue<TestObjectRef>:                             64 783 536 B
ConcurrentStack<TestObject>:                               128 005 072 B
ConcurrentStack<TestObjectRef>:                             80 004 632 B

Für den Gedächtnistest fand ich das Beste

GC.GetAllocatedBytesForCurrentThread()

1

Für Arrays von Strukturen / Werten habe ich unterschiedliche Ergebnisse mit:

first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;

(stark vereinfachtes Beispiel)

Unabhängig vom Ansatz müssen Sie wirklich verstehen, wie .Net funktioniert, um die Ergebnisse richtig zu interpretieren. Beispielsweise ist die zurückgegebene Elementgröße die "ausgerichtete" Elementgröße mit einigen Auffüllungen. Der Overhead und damit die Größe ist je nach Verwendung eines Typs unterschiedlich: "boxed" auf dem GC-Heap, auf dem Stack, als Feld, als Array-Element.

(Ich wollte wissen, wie sich die Verwendung von "Dummy" -Leerststrukturen (ohne Feld) auf den Speicher auswirkt, um "optionale" Argumente von Generika nachzuahmen. Wenn ich Tests mit verschiedenen Layouts mit leeren Strukturen durchführe, kann ich sehen, dass eine leere Struktur verwendet ( mindestens) 1 Byte pro Element; ich erinnere mich vage daran, dass .Net für jedes Feld eine andere Adresse benötigt, was nicht funktionieren würde, wenn ein Feld wirklich leer wäre (Größe 0).


0

Der einfachste Weg ist: int size = *((int*)type.TypeHandle.Value + 1)

Ich weiß, dass dies ein Implementierungsdetail ist, aber GC verlässt sich darauf und es muss so nah am Beginn der Methodentabelle sein, um die Effizienz zu steigern und zu berücksichtigen, wie komplex der GC-Code ist. Niemand wird es wagen, ihn in Zukunft zu ändern. Tatsächlich funktioniert es für alle Neben- / Hauptversionen von .net Framework + .net Core. (Derzeit kann 1.0 nicht getestet werden.)
Wenn Sie eine zuverlässigere Methode wünschen, geben Sie eine Struktur in einer dynamischen Assembly mit [StructLayout(LayoutKind.Auto)]genau denselben Feldern in derselben Reihenfolge aus und nehmen Sie ihre Größe mit. Wenn Ihre Klasse jedoch von einer anderen Klasse abgeleitet ist, müssen Sie jede Größe finden der Basisklasse separat und füge sie hinzu + 2 * Inptr.Size erneut für Header. Sie können dies tun, indem Sie Felder mit Flag abrufen. Anweisung sizeof IL. Möglicherweise möchten Sie eine statische Methode innerhalb von struct ausgeben, die diesen Wert einfach zurückgibt. Fügen Sie dann 2 * IntPtr.Size für den Objektheader hinzu. Dies sollte Ihnen einen genauen Wert geben.
BindingFlags.DeclaredOnly
Arrays und Strings fügen dieser Größe lediglich ihre Länge * Elementgröße hinzu. Für die kumulative Größe von Aggreagate-Objekten müssen Sie eine komplexere Lösung implementieren, bei der jedes Feld besucht und dessen Inhalt überprüft wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.