Mam dwie struktury z tablicami bajtów i wartości logicznych:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] values;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public bool[] values;
}
I następujący kod:
class main
{
public static void Main()
{
Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
Console.ReadKey();
}
}
To daje mi następujący wynik:
sizeof array of bytes: 3
sizeof array of bools: 12
Wygląda na to, że a boolean
zajmuje 4 bajty pamięci. Idealnie boolean
byłoby zająć tylko jeden bit ( false
lub true
, 0
lub 1
, itd.).
Co tu się dzieje? Czy ten boolean
typ naprawdę jest tak nieefektywny?
Odpowiedzi:
Typ bool ma burzliwą historię z wieloma niezgodnymi opcjami między środowiskami wykonawczymi języka. Zaczęło się od historycznego wyboru projektu dokonanego przez Dennisa Ritchiego, gościa, który wynalazł język C. Nie miał typu bool , alternatywą było int, gdzie wartość 0 reprezentuje fałsz, a każda inna wartość była uważana za prawdziwą .
Ten wybór został przeniesiony do Winapi, głównego powodu używania pinvoke, ma typedef, dla
BOOL
którego jest aliasem dla słowa kluczowego int kompilatora C. Jeśli nie zastosujesz jawnego atrybutu [MarshalAs], C # bool zostanie przekonwertowany na BOOL, tworząc w ten sposób pole o długości 4 bajtów.Cokolwiek robisz, Twoja deklaracja struktury musi być zgodna z wyborem środowiska wykonawczego dokonanym w języku, z którym współpracujesz. Jak zauważono, BOOL dla winapi, ale większość implementacji C ++ wybiera bajt , większość międzyoperacyjności automatyzacji COM używa VARIANT_BOOL, który jest krótki .
Rzeczywisty rozmiar C #
bool
to jeden bajt. Mocnym celem projektowym CLR jest to, że nie możesz się tego dowiedzieć. Układ to szczegół implementacji, który zbytnio zależy od procesora. Procesory są bardzo wybredne, jeśli chodzi o typy zmiennych i wyrównanie, niewłaściwe wybory mogą znacząco wpłynąć na wydajność i powodować błędy w czasie wykonywania. Dzięki temu, że układ jest niemożliwy do wykrycia, .NET może zapewnić uniwersalny system typów, który nie zależy od rzeczywistej implementacji środowiska wykonawczego.Innymi słowy, zawsze musisz zorganizować strukturę w czasie wykonywania, aby dopracować układ. W którym momencie następuje konwersja z układu wewnętrznego do układu międzyoperacyjnego. Może to być bardzo szybkie, jeśli układ jest identyczny, powolne, gdy pola muszą zostać ponownie ułożone, ponieważ zawsze wymaga to utworzenia kopii struktury. Techniczny termin na to jest kopiowalny , przekazywanie struktury możliwej do kopiowania do kodu natywnego jest szybkie, ponieważ pinvoke marshaller może po prostu przekazać wskaźnik.
Wydajność jest również głównym powodem, dla którego bool nie jest pojedynczym bitem. Jest kilka procesorów, które sprawiają, że bit jest bezpośrednio adresowalny, najmniejszą jednostką jest bajt. Dodatkowa instrukcja jest wymagane do prowadzenia połowów bitu z bajtu, że nie przychodzi za darmo. I nigdy nie jest atomowy.
Kompilator C # nie jest nieśmiały w informowaniu Cię, że zajmuje 1 bajt, użyj
sizeof(bool)
. To wciąż nie jest fantastyczny predyktor, ile bajtów zajmuje pole w czasie wykonywania, środowisko CLR musi również implementować model pamięci .NET i obiecuje, że proste aktualizacje zmiennych są niepodzielne . Wymaga to prawidłowego wyrównania zmiennych w pamięci, aby procesor mógł je aktualizować w jednym cyklu magistrali pamięci. Dość często bool faktycznie wymaga 4 lub 8 bajtów pamięci z tego powodu. Dodatkowe wypełnienie, które zostało dodane, aby zapewnić prawidłowe wyrównanie następnego elementu.Środowisko CLR faktycznie korzysta z tego, że układ jest nie do wykrycia, może zoptymalizować układ klasy i ponownie rozmieścić pola, aby zminimalizować wypełnienie. Powiedzmy, że jeśli masz klasę z elementem bool + int + bool, zajmie to 1 + (3) + 4 + 1 + (3) bajtów pamięci, (3) to wypełnienie, w sumie 12 bajtów. 50% odpadów. Automatyczny układ zmienia się na 1 + 1 + (2) + 4 = 8 bajtów. Tylko klasa ma układ automatyczny, struktury mają domyślnie układ sekwencyjny.
Co gorsza, bool może wymagać aż 32 bajtów w programie C ++ skompilowanym za pomocą nowoczesnego kompilatora C ++, który obsługuje zestaw instrukcji AVX. Co nakłada wymóg wyrównania 32-bajtowego, zmienna bool może mieć 31 bajtów wypełnienia. Również główny powód, dla którego jitter .NET nie emituje instrukcji SIMD, o ile nie jest jawnie opakowany, nie może uzyskać gwarancji wyrównania.
źródło
Po pierwsze, jest to tylko rozmiar międzyoperacyjny. Nie reprezentuje rozmiaru w zarządzanym kodzie tablicy. To 1 bajt na
bool
- przynajmniej na moim komputerze. Możesz to przetestować samodzielnie za pomocą tego kodu:Teraz, jeśli chodzi o uporządkowanie tablic według wartości, tak jak ty, dokumentacja mówi:
Więc patrzymy
ArraySubType
i mamy dokumentację:Patrząc teraz
UnmanagedType
, jest:Jest to więc wartość domyślna dla
bool
i wynosi 4 bajty, ponieważ odpowiada to typowi Win32 BOOL - więc jeśli pracujesz z kodem oczekującymBOOL
tablicy, robi dokładnie to, czego chcesz.Teraz możesz zamiast tego określić
ArraySubType
asI1
, co jest udokumentowane jako:Więc jeśli kod, z którym współpracujesz, oczekuje 1 bajtu na wartość, po prostu użyj:
Twój kod pokaże, że zgodnie z oczekiwaniami zajmuje 1 bajt na wartość.
źródło