Znajdź rozmiar wystąpienia obiektu w bajtach w języku C #


114

Dowolny przypadek (zbiory różnych obiektów, kompozycje, pojedyncze obiekty itp.)

Jak mogę określić jego rozmiar w bajtach?

(Obecnie mam zbiór różnych obiektów i próbuję określić ich łączny rozmiar)

EDYCJA: Czy ktoś napisał metodę rozszerzenia dla Object, która mogłaby to zrobić? To byłoby całkiem fajne imo.



Odpowiedzi:


60

Przede wszystkim ostrzeżenie: to, co następuje, dotyczy wyłącznie brzydkich, nieudokumentowanych hacków. Nie polegaj na tym, że działa - nawet jeśli teraz zadziała, jutro może przestać działać, z każdą mniejszą lub większą aktualizacją .NET.

Możesz skorzystać z informacji zawartych w tym artykule na temat wewnętrznych elementów środowiska CLR MSDN Magazine, wydanie maj 2005 r. - Drążenie do elementów wewnętrznych .NET Framework, aby zobaczyć, jak CLR tworzy obiekty środowiska wykonawczego - ostatnio sprawdzałem, nadal ma zastosowanie. Oto jak to się robi (pobiera wewnętrzne pole „Basic Instance Size” przez TypeHandletyp).

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

Działa to w wersji 3.5 SP1 32-bitowej. Nie jestem pewien, czy rozmiary pól są takie same w wersji 64-bitowej - może być konieczne dostosowanie typów i / lub przesunięć, jeśli nie są.

Będzie to działać dla wszystkich „normalnych” typów, dla których wszystkie instancje mają takie same, dobrze zdefiniowane typy. Te, dla których nie jest to prawdą, to na pewno tablice i łańcuchy, i wierzę również StringBuilder. W ich przypadku będziesz musiał dodać rozmiar wszystkich zawartych elementów do ich podstawowego rozmiaru instancji.


Nie. Nie ma „właściwego” sposobu, aby to zrobić, ponieważ nie jest to coś, czym powinna się zajmować dobrze działająca aplikacja .NET. Powyższe szkodzi bezpośrednio wewnętrznym strukturom danych konkretnej implementacji CLR (które mogą łatwo ulec zmianie np. W następnej wersji .NET).
Pavel Minaev

3
czy to ma działać w C # czy tylko zarządzane C ++? nie jest szczęśliwy w C # do tej pory, że próbowałem:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
Wersja .NET 4 nie potrzebuje nawet niebezpiecznego kodu: Marshal.ReadInt32(type.TypeHandle.Value, 4)działa dla x86 i x64. Testowałem tylko typy struktur i klas. Należy pamiętać, że zwraca to rozmiar w pudełku dla typów wartości. @Pavel Może mógłbyś zaktualizować swoją odpowiedź.
jnm2

2
@ sab669 dobrze, wymienić typez obj.GetType()jego przykładu. Nie ma znaczenia, którego frameworka używasz, tylko jakiego CLR (v2 lub v4 lub CoreCLR). Nie próbowałem tego na CoreCLR.
jnm2

2
@SamGoldberg Obliczanie tego ręcznie wymaga dużo pracy z milionem przypadków brzegowych. Sizeof informuje o statycznym rozmiarze obiektu, a nie o zużyciu pamięci przez wykres obiektów w czasie wykonywania. Profilowanie pamięci i procesora VS2017 jest bardzo dobre, podobnie jak narzędzia ReSharper i inne, i właśnie tego użyłbym do pomiaru.
jnm2

21

Możesz być w stanie przybliżyć rozmiar, udając, że serializuje go za pomocą serializatora binarnego (ale kierujesz dane wyjściowe do zapomnienia), jeśli pracujesz z obiektami możliwymi do serializacji.

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
Oczywiście może to zapewnić minimalny rozmiar, ale nic nie mówi o rozmiarze w pamięci.
John Saunders,

Lol, następna żarówka, którą miałem przed powrotem, aby sprawdzić odpowiedzi, używała serializatora binarnego. John, jak to nie dałoby ci rzeczywistego rozmiaru w pamięci?
Janie

2
Dałoby ci to rozmiar serializowany, który będzie rozmiarem, jakiego chciał serializator, dla celów "serializatora". Prawdopodobnie różnią się one od celów „zasiadania w pamięci”. Może na przykład serializer przechowuje mniejsze liczby całkowite w trzech bajtach.
John Saunders,

4
Jak powiedziałem, to tylko przybliżenie. Nie jest doskonały, ale nie zgodziłbym się z tym, że nie mówi ci „nic” o rozmiarze pamięci. Powiedziałbym, że dało ci to pewien pomysł - większe serializacje byłyby generalnie skorelowane z większymi rozmiarami w pamięci. Jest jakiś związek.
BlueMonkMN

Zgadzam się - przydaje się oszacowanie wielkości wykresu obiektu .NET.
Craig Shearer

8

W przypadku niezarządzanych typów, czyli typów wartości, struktury:

        Marshal.SizeOf(object);

W przypadku obiektów zarządzanych, im bliżej jestem, jest przybliżeniem.

        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;

Nie używaj serializacji.Binarny program formatujący dodaje nagłówki, więc możesz zmienić klasę i załadować stary zserializowany plik do zmodyfikowanej klasy.

Nie poda też rzeczywistego rozmiaru w pamięci ani nie uwzględni wyrównania pamięci.

[Edytuj] Używając BiteConverter.GetBytes (prop-value) rekurencyjnie dla każdej właściwości swojej klasy, otrzymałeś zawartość w bajtach, co nie liczy wagi klasy ani referencji, ale jest znacznie bliższe rzeczywistości. Zalecałbym użycie tablicy bajtów dla danych i niezarządzanej klasy proxy, aby uzyskać dostęp do wartości za pomocą rzutowania wskaźników, jeśli rozmiar ma znaczenie, pamiętaj, że byłaby to pamięć nie wyrównana, więc na starych komputerach będzie wolna, ale OGROMNE zestawy danych na MODERN RAM będą znacznie szybciej, ponieważ zmniejszenie rozmiaru do odczytu z pamięci RAM będzie miało większy wpływ niż niewyrównanie.


5

Nie dotyczy to bieżącej implementacji platformy .NET, ale jedną rzeczą, o której należy pamiętać w przypadku środowisk wykonawczych zbieranych / zarządzanych bezużytecznych, jest to, że przydzielony rozmiar obiektu może się zmieniać przez cały okres istnienia programu. Na przykład, niektóre generacyjne zbieracze śmieci (takie jak Generational / Ulterior Reference Counting Hybrid ) muszą przechowywać pewne informacje tylko po przeniesieniu obiektu z pokoju dziecinnego do przestrzeni dojrzałej.

Uniemożliwia to utworzenie niezawodnego, ogólnego interfejsu API w celu ujawnienia rozmiaru obiektu.


Ciekawy. Co więc robią ludzie, aby dynamicznie określać rozmiar swoich obiektów / zbiorów obiektów?
Janie,

2
To zależy od tego, do czego go potrzebują. Jeśli dla P / Invoke (współdziałanie z kodem natywnym), używają Marshal.SizeOf (typeof (T)). Jeśli do profilowania pamięci, używają oddzielnego profilera, który współpracuje ze środowiskiem wykonawczym w celu dostarczenia informacji. Jeśli jesteś zainteresowany wyrównywaniem elementów w tablicy, możesz użyć kodu operacji SizeOf IL w DynamicMethod (nie sądzę, że jest do tego łatwiejszy sposób w ramach .NET).
Sam Harwell,

5

bezpieczne rozwiązanie z pewnymi optymalizacjami CyberSaving / MemoryUsage code . jakaś sprawa:

/* 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

Działa również z zajęciami:

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

Takie podejście też bym wybrał. Możesz dodać zestaw wcześniej napotkanych obiektów do wykresu, aby uniknąć a) nieskończonej rekurencji oraz b) uniknięcia podwójnego dodawania tej samej pamięci.
mafu

4

Nie można tego zrobić w czasie wykonywania.

Istnieją jednak różne programy profilujące pamięci, które wyświetlają rozmiar obiektu.

EDYCJA : Możesz napisać drugi program, który profiluje pierwszy z nich za pomocą CLR Profiling API i komunikuje się z nim za pomocą zdalnego lub czegoś podobnego.


17
Jeśli nie można tego zrobić w czasie wykonywania, w jaki sposób profilery pamięci dostarczają informacji?
Janie,

2
Korzystając z Profiling API. Jednak program nie może się profilować
SLaks

Ciekawy. Co jeśli chciałbym, aby kod zajmował się przypadkami, gdy obiekty zużywały zbyt dużo pamięci?
Janie,

4
Wtedy miałbyś do czynienia z samoświadomym oprogramowaniem i bardzo bym się bał. :-) Poważnie, „zasada pojedynczej odpowiedzialności” - niech program będzie programem, niech inny fragment kodu wypatruje obiektów zajmujących zbyt dużo pamięci.
John Saunders,

2
@Janie: można też założyć, jakie znaczenie ma rozmiar i jaki ma związek z wydajnością. Myślę, że chciałbyś być prawdziwym ekspertem niskiego poziomu wydajności CLR (takim, który już wie o Profiling API), zanim to zrobisz. W przeciwnym razie możesz zastosować swoje wcześniejsze doświadczenia w sytuacji, w której nie mają one zastosowania.
John Saunders,


2

AFAIK, nie możesz tego zrobić bez dokładnego zliczenia rozmiaru każdego elementu członkowskiego w bajtach. Ale znowu, czy rozmiar elementu członkowskiego (podobnie jak elementy wewnątrz kolekcji) liczy się do rozmiaru obiektu, czy też wskaźnik do tego elementu liczy się do rozmiaru obiektu? Zależy, jak to zdefiniujesz.

Wcześniej spotkałem się z taką sytuacją, w której chciałem ograniczyć liczbę obiektów w mojej pamięci podręcznej na podstawie zajmowanej pamięci.

Cóż, jeśli jest jakaś sztuczka, aby to zrobić, z przyjemnością się o tym dowiem!


2

W przypadku typów wartości możesz użyć Marshal.SizeOf. Oczywiście zwraca liczbę bajtów wymaganych do zorganizowania struktury w pamięci niezarządzanej, co niekoniecznie jest tym, czego używa środowisko CLR.


SizeOf (Object) może być niedostępne w przyszłych wersjach. Zamiast tego użyj SizeOf <T> (). Aby uzyskać więcej informacji, przejdź do go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Możesz użyć odbicia, aby zebrać wszystkie publiczne informacje o składowych lub właściwościach (biorąc pod uwagę typ obiektu). Nie ma jednak sposobu na określenie rozmiaru bez przejrzenia poszczególnych danych o obiekcie.


1

Dla każdego, kto szuka rozwiązania niewymagającego [Serializable]zajęć i którego wynik jest przybliżeniem zamiast nauk ścisłych. Najlepszą metodą, jaką udało mi się znaleźć, jest serializacja json do strumienia pamięci przy użyciu kodowania UTF32.

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

Nie, to nie da dokładnego rozmiaru, który byłby używany w pamięci. Jak wspomniano wcześniej, nie jest to możliwe. Ale da ci to przybliżoną ocenę.

Zauważ, że jest to również dość powolne.


1

Od Pawła i jnm2:

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

Na marginesie należy zachować ostrożność, ponieważ działa tylko z sąsiadującymi obiektami pamięci


1

Stworzyłem test porównawczy dla różnych kolekcji w .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Wyniki są następujące dla platformy .NET Core 2.2 z 1 000 000 obiektów z przydzielonymi 3 właściwościami:

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

Do testu pamięci znalazłem najlepszy do użycia

GC.GetAllocatedBytesForCurrentThread()

1

Dla tablic struktur / wartości mam różne wyniki z:

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

(zbyt uproszczony przykład)

Niezależnie od podejścia, naprawdę musisz zrozumieć, jak działa .Net, aby poprawnie interpretować wyniki. Na przykład zwrócony rozmiar elementu to „wyrównany” rozmiar elementu z pewnym dopełnieniem. Narzut, a tym samym rozmiar, jest różny w zależności od zastosowania typu: „w pudełku” na stercie GC, na stosie, jako pole, jako element tablicy.

(Chciałem wiedzieć, jaki wpływ na pamięć miałoby użycie pustych struktur „fikcyjnych” (bez żadnego pola) do naśladowania „opcjonalnych” argumentów typów generycznych; wykonując testy z różnymi układami zawierającymi puste struktury, widzę, że pusta struktura używa ( co najmniej) 1 bajt na element; niejasno pamiętam, że to dlatego, że .Net potrzebuje innego adresu dla każdego pola, co nie działałoby, gdyby pole było naprawdę puste / o rozmiarze 0).


0

Najprostszy sposób to: int size = *((int*)type.TypeHandle.Value + 1)

Wiem, że jest to szczegół implementacji, ale GC polega na nim i musi być tak blisko początku tabeli metod, aby zwiększyć wydajność, a także wziąć pod uwagę, jak złożony jest kod GC, nikt nie odważy się go zmienić w przyszłości. W rzeczywistości działa z każdą mniejszą / główną wersją .NET Framework + .NET Core. (Obecnie nie można przetestować dla 1.0)
Jeśli chcesz bardziej niezawodny sposób, wyemituj strukturę w zestawie dynamicznym [StructLayout(LayoutKind.Auto)]z dokładnie tymi samymi polami w tej samej kolejności, weź jej rozmiar z Ale jeśli twoja klasa pochodzi z innej klasy, musisz znaleźć każdy rozmiar klasy bazowej oddzielnie i dodaj je + 2 * Inptr.Size ponownie dla nagłówka. Możesz to zrobić, pobierając pola z flagą. sizeof instrukcji IL. Możesz chcieć wyemitować statyczną metodę wewnątrz struktury, która po prostu zwraca tę wartość. Następnie dodaj 2 * IntPtr.Size dla nagłówka obiektu. To powinno dać ci dokładną wartość.
BindingFlags.DeclaredOnly
Tablice i łańcuchy po prostu dodają ten rozmiar do długości * rozmiaru elementu. Dla skumulowanego rozmiaru obiektów agregatów należy wdrożyć bardziej wyrafinowane rozwiązanie, które polega na odwiedzeniu każdego pola i sprawdzeniu jego zawartości.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.