Wszyscy wiemy, że CPU i pamięć to dwa najważniejsze wskaźniki programu, więc ile osób naprawdę zastanawiało się nad pytaniem: Ile bajtów zajmuje instancja typu (wartość typu lub typu referencji) w pamięci? Wielu z nas nie potrafi odpowiedzieć. C# oferuje kilka operatorów i API do obliczania rozmiarów, ale żaden z nich nie rozwiązuje całkowicie problemu, o który właśnie pytałem. Ten artykuł przedstawia metodę obliczania liczby bajtów pamięci zajmowanych przez instancje typów wartości i typów odniesień. Kod źródłowy można pobrać stąd.
1. Rozmiar operatora 2. Metoda Marshal.SizeOf 3. Metoda Unsafe.SizeOf > 4. Czy można to obliczyć na podstawie typu członka terenowego? 5. Układ typów wartości i typów aplikacji 6. Dyrektywa LDFLDA 7. Oblicz liczbę bajtów typu wartości 8. Policz liczbę bajtów typu cytowania 9. Pełne obliczenia
1. Rozmiar operatora
Rozmiar operacji służy do określenia liczby bajtów zajmowanych przez instancję typu, ale może być stosowany tylko do typów niezarządzanych. Tak zwany typ Unmanaged jest ograniczony do:
Typy prymitywne: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double i Single) Typ dziesiętny Typ enumeracji Typ wskaźnika Struktury zawierające wyłącznie dane typu Unmanaged Jak sama nazwa wskazuje, typ Unmanaged to typ wartości, a odpowiadająca mu instancja nie może zawierać żadnej odnośniki do zarządzanego obiektu. Jeśli zdefiniujemy metodę generyczną taką do wywołania operatora rozmiaru, parametr generyczny T musi dodać do metody ograniczenie niezarządzane oraz niebezpieczny tag.
Tylko natywne i enum typy mogą bezpośrednio korzystać z operatora sizeof, który musi zostać dodany, jeśli jest stosowany do innych typów (wskaźników i niestandardowych struktur)./unsafetagów kompilacyjnych i również należy je umieścić wniebezpiecznyw kontekście.
Ponieważ następująca struktura Foobar nie jest typem Unmanaged, program wystąpi z błędem kompilacji.
2. Metoda Marshal.SizeOf
Typy statyczne Marshal definiuje serię API, które pomagają nam alokować i kopiować pamięć niezarządzaną, konwertować między typami zarządzanymi i niezarządzanymi oraz wykonywać szereg innych operacji na pamięci niezarządzanej (Marshal w nauce obliczeniowej odnosi się do operacji konwersji obiektów pamięci na odpowiedni format do przechowywania lub transferu danych). Statyczny, który obejmuje następujące 4 przeciążenia metody SizeOf w celu określenia liczby bajtów danego typu lub obiektu.
Metoda Marshal.SizeOf nie ma ograniczenia co do określonego typu dla typu Unmanaged, ale nadal wymaga jego określeniaTyp wartości。 Jeśli obiekt przychodzący jest obiektem, musi być także pudełkiem dla typu wartości.
Ponieważ następujący Foobar jest zdefiniowany jako:rodzaj, więc wywołania obu metod SizeOf wygenerują wyjątek ArgumentException i prompt: Typ 'Foobar' nie może być zorganizowany jako struktura niezarządzana; nie da się obliczyć znaczącego rozmiaru ani przesunięcia.
Metoda Marshal.SizeOfGeneryki nie są obsługiwane, ale ma także wymagania dotyczące ułożenia konstrukcji, która wspieraSekwencyjnegoiWyraźnyTryb układu. Ponieważ struktura Foobar pokazana poniżej przyjmuje tryb automatycznego układu (Auto, który nie obsługuje "dynamicznego planowania" układu pamięci na podstawie członków pola ze względu na surowsze wymagania dotyczące układu pamięci w środowiskach niezarządzanych), wywołania metody SizeOf nadal wygenerują ten sam wyjątek ArgumentException co powyżej.
3. Metoda Unsafe.SizeOf
Statyczny Niebezpieczny zapewnia więcej niskopoziomowych operacji dla pamięci niezarządzanej, a podobne metody SizeIOf są również zdefiniowane w tym typie. Metoda nie ma żadnych ograniczeń co do określonego typu, ale jeśli określisz typ odniesienia, zwracaLiczba bajtów wskaźnika"(IntPtr.Size)。
4. Czy można to obliczyć na podstawie typu członka terenowego?
Wiemy, że zarówno typy wartości, jak i odniesień są mapowane jako fragment ciągły (lub przechowywane bezpośrednio w rejestrze). Celem typu jest określenie układu pamięci obiektu, a instancje tego samego typu mają ten sam układ, a liczba bajtów jest naturalnie taka sama (dla pól typu referencyjnego przechowuje się tylko adres referencji w tej sekwencji bajtów). Ponieważ długość bajtu jest określana przez typ, jeśli potrafimy określić typ każdego członka pola, czy nie będziemy w stanie obliczyć liczby bajtów odpowiadających temu typowi? W rzeczywistości nie jest to możliwe.
Na przykład wiemy, że bajty bajtów, short, int i long to 1, 2, 4 i 8, więc liczba bajtów dla binarnego bajta wynosi 2, ale dla kombinacji typów bajt + short, bajt + int i bajt + long, odpowiadające bajty to nie 3, 5 i 9, lecz 3, 8 i 16. Ponieważ wiąże się to z kwestią wyrównania pamięci.
5. Układ typów wartości i typów odniesień
Liczba bajtów zajmowanych przez instancje typu i podtypu referencji również różni się dla dokładnie tego samego członka danych. Jak pokazano na poniższym obrazku, sekwencja bajtów instancji typu wartościWszyscy są pracownikami terenowymi używanymi do przechowywania。 Dla przykładów typów referencyjnych adres odpowiadającej typowej tabeli metod jest również przechowywany przed sekwencją bajtów pól. Tabela metod dostarcza niemal wszystkich metadanych opisujących typ i używamy tego odniesienia, aby określić, do którego typu należy instancja. Na samym początku znajdują się także dodatkowe bajty, które nazwiemyNagłówek obiektuNie służy tylko do przechowywania stanu zablokowanego obiektu, ale wartość skrótu może być również tutaj buforowana. Gdy tworzymy zmienną typu referencyjnego, ta zmiennaNie wskazuje pierwszego bajtu pamięci zajmowanej przez instancję, lecz miejsce, gdzie przechowywany jest adres tabeli metod。
6. Dyrektywa LDFLDA
Jak już wspomniano powyżej, operator sizeof oraz metoda SizeOf udostępniona przez statyczny typ Marshal/Unsafe nie są w stanie rozwiązywać obliczenia długości bajtu zajmowanego przez instancje. Z tego co wiem, tego problemu nie da się rozwiązać wyłącznie w dziedzinie C#, ale jest on realizowany na poziomie ILLdfldaInstrukcje mogą pomóc rozwiązać ten problem. Jak sama nazwa wskazuje, Ldflda oznacza Load Field Address, co pomaga nam uzyskać adres pola w instancji. Ponieważ ta instrukcja IL nie posiada odpowiadającego API w C#, możemy używać jej tylko w następującej formie za pomocą IL Emit.
Jak pokazano w fragmentie kodu powyżej, mamy metodę GenerateFieldAddressAccessor w typie SizeCalculator, która generuje delegata typu Func<object?, long[]> na podstawie listy pól określonego typu, co pomaga nam zwrócić adres pamięci określonego obiektu i wszystkich jego pól. Dzięki adresowi samego obiektu i adresowi każdego pola możemy naturalnie uzyskać przesunięcie każdego pola, a następnie łatwo obliczyć liczbę bajtów pamięci zajmowanej przez całą instancję.
7. Oblicz liczbę bajtów typu wartości
Ponieważ typy wartości i typy odniesień mają różne układy w pamięci, musimy również stosować różne obliczenia. Ponieważ bajt struktury stanowi zawartość wszystkich pól w pamięci, stosujemy sprytny sposób jej obliczania. Załóżmy, że musimy ustalić liczbę bajtów struktury typu T, wtedy tworzymy krotkę ValueTuple<T,T> a przesunięcie jej drugiego pola Item2 to liczba bajtów struktury T. Konkretna metoda obliczeń została odzwierciedlona w następującej metodzie CalculateValueTypeInstance.
Jak pokazano w fragmentie kodu powyżej, zakładając, że typ struktury, który musimy obliczyć, to T, używamy metody GetDefaultAsObject, aby uzyskać obiekt default(T) w postaci odbicia, a następnie tworzymy Tuple Value<T,T>Tuple. Po wywołaniu metody GenerateFieldAddressAccessor w celu uzyskania delegata Func<object?, long[]> do obliczenia instancji i jej adresów pól, nazywamy tego delegata argumentem. Dla trzech adresów pamięci, które otrzymujemy, krotka kodowa oraz adresy pól 1 i 2 są takie same, używamy trzeciego adresu reprezentującego Item2 minus pierwszy adres i otrzymujemy pożądany wynik.
8. Policz liczbę bajtów typu cytowania
Obliczanie bajtów dla typów odniesień jest bardziej skomplikowane, wykorzystując ten pomysł: po uzyskaniu adresu samej instancji i każdego pola, sortujemy adresy, aby uzyskać przesunięcie ostatniego pola. Dodaj ten offset do liczby bajtów samego ostatniego pola, a następnie dodajmy niezbędne "pierwsze i ostatnie bajty" do wyniku, który chcemy, co odzwierciedla się w następującej metodzie CalculateReferneceTypeInstance.
Jak pokazano w powyższym fragmencie kodu, jeśli określony typ nie ma zdefiniowanych pól, CalculateReferneceTypeInstance zwraca minimalną liczbę bajtów instancji typu referencyjnego: 3 razy więcej bajtów wskaźnika adresu. W architekturach x86 obiekt typu aplikacji zajmuje co najmniej 12 bajtów, w tym ObjectHeader (4 bajty), wskaźniki tabeli metod (bajty) oraz co najmniej 4 bajty zawartości pola (te 4 bajty są wymagane nawet jeśli żaden typ nie jest zdefiniowany bez pól). W przypadku architektury x64 ta minimalna liczba bajtów wynosi 24, ponieważ wskaźnik tabeli metod i minimalna zawartość pola wyniosą się do 8 bajtów, choć poprawna zawartość nagłówka obiektu zajmuje tylko 4 bajty, a na początku dodane zostaną 4 bajty wypełnienia.
Rozliczenie bajtów zajmowanych przez ostatnie pole jest również bardzo proste: jeśli typ jest typem wartości, to metoda CalculateValueTypeInstance zdefiniowana wcześniej jest wywoływana do obliczeń, jeśli jest to typ referencyjny, treść przechowywana w polu to tylko adres pamięci obiektu docelowego, więc długość to IntPtr.Size. Ponieważ instancje typów referencji są domyślnie wyrównane z IntPtr.Size w pamięci, robi się to również tutaj. Na koniec nie zapominaj, że referencja instancji typu referencyjnego nie wskazuje na pierwszy bajt pamięci, lecz na bajt, który przechowuje wskaźnik tabeli metody, więc musisz dodać liczbę bajtów ObjecthHeader (IntPtr.Size).
9. Pełne obliczenia
Dwie metody użyte do obliczania liczby bajtów typu wartości i instancji typu referencyjnego są stosowane w następującej metodzie SizeOf. Ponieważ wywołanie instrukcji Ldflda musi dostarczyć odpowiadającą instancję, ta metoda zapewnia delegata do uzyskania odpowiedniej instancji oprócz podania typu docelowego. Parametry odpowiadające temu delegatowi mogą być domyślne, a dla typu wartości domyślnej użyjemy. Dla typów referencji spróbujemy również stworzyć obiekt docelowy używając domyślnego konstruktora. Jeśli obiekt delegata nie jest dostępny i docelowa instancja nie może zostać utworzona, metoda SizeOf wyrzuca wyjątek. Chociaż musimy podać docelową instancję, obliczony wynik jest związany tylko z typem, więc buforujemy obliczony wynik. Dla ułatwienia wywołań oferujemy także <T>inną ogólną metodę SizeOf.
W poniższym fragmencie kodu używamy go do wypisania liczby bajtów dwóch struktur i typów o tej samej definicji pola. W następnym artykule otrzymamy pełną zawartość binarną instancji w pamięci na podstawie obliczonej liczby bajtów, więc bądźcie czujni.
Oryginalny link:Logowanie do linku jest widoczne. |