Jaki jest rozmiar wartości logicznej w języku C #? Czy to naprawdę zajmuje 4 bajty?


137

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 booleanzajmuje 4 bajty pamięci. Idealnie boolean byłoby zająć tylko jeden bit ( falselub true, 0lub 1, itd.).

Co tu się dzieje? Czy ten booleantyp naprawdę jest tak nieefektywny?


7
Jest to jedno z najbardziej ironicznych starć w toczącej się walce z przyczynami wstrzymania: dwie doskonałe odpowiedzi Johna i Hansa właśnie to zrobiły, mimo że odpowiedzi na to pytanie będą prawie całkowicie oparte na opiniach, a nie faktach, odniesieniach, lub konkretnej wiedzy.
Dzień

12
@TaW: Domyślam się, że głosy zbliżające nie wynikały z odpowiedzi, ale z pierwotnego tonu PO, kiedy po raz pierwszy zadali pytanie - wyraźnie zamierzali rozpocząć walkę i wyraźnie pokazali to w usuniętych teraz komentarzach. Większość skorupy została zamieciona pod dywan, ale sprawdź historię zmian, aby zobaczyć, o co mi chodzi.
BoltClock

1
Dlaczego nie użyć BitArray?
ded '16

Odpowiedzi:


242

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 BOOLktó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 # boolto 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.



2
Czy dla zainteresowanego, ale niedoinformowanego czytelnika mógłbyś wyjaśnić, czy ostatni akapit powinien rzeczywiście czytać 32 bajty, a nie bity ?
Silly Freak

3
Nie jestem pewien, dlaczego po prostu to przeczytałem (ponieważ nie potrzebuję tylu szczegółów), ale to jest fascynujące i dobrze napisane.
Frank V

2
@Silly - to bajty . AVX używa 512-bitowych zmiennych do obliczeń na 8 wartościach zmiennoprzecinkowych za pomocą jednej instrukcji. Taka 512-bitowa zmienna wymaga wyrównania do 32.
Hans Passant

3
Łał! jeden post dał cholernie wiele tematów do zrozumienia. Dlatego po prostu lubię czytać najważniejsze pytania.
Chaitanya Gadkari

151

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:

using System;
class Program 
{ 
    static void Main(string[] args) 
    { 
        int size = 10000000;
        object array = null;
        long before = GC.GetTotalMemory(true); 
        array = new bool[size];
        long after = GC.GetTotalMemory(true); 

        double diff = after - before; 

        Console.WriteLine("Per value: " + diff / size);

        // Stop the GC from messing up our measurements 
        GC.KeepAlive(array); 
    } 
}

Teraz, jeśli chodzi o uporządkowanie tablic według wartości, tak jak ty, dokumentacja mówi:

Gdy właściwość MarshalAsAttribute.Value jest ustawiona na ByValArray, pole SizeConst musi być ustawione tak, aby wskazywało liczbę elementów w tablicy. ArraySubTypePole może opcjonalnie zawierać UnmanagedTypeelementy macierzy, gdy jest to konieczne dla rozróżnienia między typami łańcuchów. Możesz tego użyć UnmanagedTypetylko w tablicy, której elementy pojawiają się jako pola w strukturze.

Więc patrzymy ArraySubTypei mamy dokumentację:

Możesz ustawić ten parametr na wartość z UnmanagedTypewyliczenia, aby określić typ elementów tablicy. Jeśli typ nie zostanie określony, zostanie użyty domyślny niezarządzany typ odpowiadający typowi elementu zarządzanej tablicy.

Patrząc teraz UnmanagedType, jest:

Bool
4-bajtowa wartość logiczna (prawda! = 0, fałsz = 0). To jest typ Win32 BOOL.

Jest to więc wartość domyślna dla booli wynosi 4 bajty, ponieważ odpowiada to typowi Win32 BOOL - więc jeśli pracujesz z kodem oczekującym BOOLtablicy, robi dokładnie to, czego chcesz.

Teraz możesz zamiast tego określić ArraySubTypeas I1, co jest udokumentowane jako:

1-bajtowa liczba całkowita ze znakiem. Możesz użyć tego elementu członkowskiego, aby przekształcić wartość logiczną w 1-bajtową wartość bool w stylu C (true = 1, false = 0).

Więc jeśli kod, z którym współpracujesz, oczekuje 1 bajtu na wartość, po prostu użyj:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;

Twój kod pokaże, że zgodnie z oczekiwaniami zajmuje 1 bajt na wartość.

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.