Qual è la dimensione di un booleano in C #? Ci vogliono davvero 4 byte?


137

Ho due strutture con matrici di byte e booleane:

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

E il seguente codice:

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

Questo mi dà il seguente risultato:

sizeof array of bytes: 3
sizeof array of bools: 12

Sembra che a booleanrichieda 4 byte di memoria. Idealmente a boolean richiederebbe solo un bit ( falseo true, 0o 1, ecc.).

Cosa sta succedendo qui? Il booleantipo è davvero così inefficiente?


7
Questo è uno degli scontri più ironici nella battaglia in corso di motivi di presa: due eccellenti risposte di John e Hans hanno appena fatto, anche se le risposte a questa domanda tenderanno ad essere quasi interamente basate su opinioni, piuttosto che su fatti, riferimenti, o competenza specifica.
Ho visto il

12
@TaW: La mia ipotesi è che i voti stretti non fossero dovuti alle risposte ma al tono originale dell'OP quando hanno presentato la domanda per la prima volta - intendevano chiaramente iniziare un combattimento e lo hanno mostrato chiaramente nei commenti ora cancellati. Gran parte dell'innesto è stato spazzato sotto il tappeto, ma controlla la cronologia delle revisioni per avere un'idea di cosa intendo.
BoltClock

1
Perché non usare un BitArray?
dedotto il "

Risposte:


242

Il tipo bool ha una storia a scacchi con molte scelte incompatibili tra i tempi di esecuzione della lingua. Questo è iniziato con una scelta storica di design fatta da Dennis Ritchie, il ragazzo che ha inventato il linguaggio C. Non aveva un tipo bool , l'alternativa era int dove un valore di 0 rappresenta falso e qualsiasi altro valore era considerato vero .

Questa scelta è stata portata avanti in Winapi, il motivo principale per usare pinvoke, ha un typedef per il BOOLquale è un alias per la parola chiave int del compilatore C. Se non si applica un attributo esplicito [MarshalAs], un bool C # viene convertito in un BOOL, producendo così un campo lungo 4 byte.

Qualunque cosa tu faccia, la tua dichiarazione di struct deve corrispondere alla scelta di runtime fatta nella lingua con cui interagisci. Come notato, BOOL per winapi ma la maggior parte delle implementazioni C ++ ha scelto byte , la maggior parte dell'interoperabilità di COM Automation utilizza VARIANT_BOOL che è una breve .

La dimensione effettiva di un C # boolè di un byte. Un forte obiettivo di progettazione del CLR è che non puoi scoprirlo. Il layout è un dettaglio dell'implementazione che dipende troppo dal processore. I processori sono molto esigenti riguardo ai tipi di variabili e all'allineamento, scelte errate possono influire in modo significativo sulle prestazioni e causare errori di runtime. Rendendo il layout irrintracciabile, .NET può fornire un sistema di tipo universale che non dipende dall'implementazione effettiva del runtime.

In altre parole, devi sempre eseguire il marshalling di una struttura in fase di esecuzione per inchiodare il layout. In quel momento viene effettuata la conversione dal layout interno al layout di interoperabilità. Questo può essere molto veloce se il layout è identico, lento quando i campi devono essere riorganizzati poiché ciò richiede sempre la creazione di una copia della struttura. Il termine tecnico per questo è blittable , passare una struttura blittable al codice nativo è veloce perché il marshaller pinvoke può semplicemente passare un puntatore.

Le prestazioni sono anche il motivo principale per cui un bool non è un singolo bit. Ci sono pochi processori che rendono un po 'direttamente indirizzabile, l'unità più piccola è un byte. È necessaria un'istruzione aggiuntiva per estrarre il bit dal byte, che non viene fornito gratuitamente. E non è mai atomico.

Il compilatore C # non è altrimenti timido nel dirti che ci vuole 1 byte, usare sizeof(bool). Questo non è ancora un predittore fantastico per quanti byte un campo impiega in fase di esecuzione, il CLR deve anche implementare il modello di memoria .NET e promette che i semplici aggiornamenti delle variabili sono atomici . Ciò richiede che le variabili siano correttamente allineate nella memoria in modo che il processore possa aggiornarlo con un singolo ciclo del bus di memoria. Abbastanza spesso, un bool in realtà richiede 4 o 8 byte in memoria per questo motivo. Imbottitura aggiuntiva aggiunta per garantire che il membro successivo sia allineato correttamente.

Il CLR in realtà sfrutta il fatto che il layout è da scoprire, può ottimizzare il layout di una classe e riorganizzare i campi in modo da ridurre al minimo il riempimento. Quindi, diciamo, se hai una classe con un membro bool + int + bool, allora ci vorrebbe 1 + (3) + 4 + 1 + (3) byte di memoria, (3) è il riempimento, per un totale di 12 byte. 50% di rifiuti. Il layout automatico viene riorganizzato su 1 + 1 + (2) + 4 = 8 byte. Solo una classe ha un layout automatico, le strutture hanno un layout sequenziale di default.

Ancora più cupamente, un bool può richiedere fino a 32 byte in un programma C ++ compilato con un moderno compilatore C ++ che supporta il set di istruzioni AVX. Il che impone un requisito di allineamento a 32 byte, la variabile bool può finire con 31 byte di riempimento. Anche il motivo principale per cui un jitter .NET non emette istruzioni SIMD, a meno che non sia esplicitamente racchiuso, non può ottenere la garanzia di allineamento.



2
Per un lettore interessato ma non informato, chiariresti se l'ultimo paragrafo dovrebbe davvero leggere 32 byte e non bit ?
Silly Freak,

3
Non so perché ho appena letto tutto questo (dato che non ho bisogno di così tanti dettagli) ma è affascinante e ben scritto.
Frank V,

2
@Silly - sono byte . AVX utilizza variabili a 512 bit per eseguire la matematica su 8 valori in virgola mobile con una singola istruzione. Una tale variabile a 512 bit richiede l'allineamento a 32.
Hans Passant

3
Wow! un post ha dato un sacco di argomenti da capire. Ecco perché mi piace solo leggere le domande più frequenti.
Chaitanya Gadkari,

151

Innanzitutto, questa è solo la dimensione per l'interoperabilità. Non rappresenta la dimensione nel codice gestito dell'array. Questo è 1 byte per bool- almeno sulla mia macchina. Puoi testarlo tu stesso con questo codice:

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

Ora, per le matrici di smistamento in base al valore, come sei, la documentazione dice:

Quando la proprietà MarshalAsAttribute.Value è impostata su ByValArray, il campo SizeConst deve essere impostato per indicare il numero di elementi nell'array. Il ArraySubTypecampo può contenere facoltativamente gli UnmanagedTypeelementi dell'array quando è necessario differenziare i tipi di stringa. Puoi usarlo UnmanagedTypesolo su un array i cui elementi appaiono come campi in una struttura.

Quindi guardiamo ArraySubType, e che ha la documentazione di:

È possibile impostare questo parametro su un valore UnmanagedTypedall'enumerazione per specificare il tipo di elementi dell'array. Se non viene specificato un tipo, viene utilizzato il tipo non gestito predefinito corrispondente al tipo di elemento dell'array gestito.

Ora guardando UnmanagedType, c'è:

Bool
Un valore booleano a 4 byte (vero! = 0, falso = 0). Questo è il tipo BOOL Win32.

Quindi è l'impostazione predefinita per bool, ed è 4 byte perché corrisponde al tipo BOOL Win32 - quindi se stai interagendo con il codice in attesa di un BOOLarray, fa esattamente quello che vuoi.

Ora puoi specificare invece ArraySubTypecome I1, che è documentato come:

Un numero intero con segno a 1 byte. È possibile utilizzare questo membro per trasformare un valore booleano in un bool di tipo C a 1 byte (true = 1, false = 0).

Quindi se il codice con cui stai interagendo prevede 1 byte per valore, usa solo:

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

Il codice mostrerà quindi che occupa 1 byte per valore, come previsto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.