Ich habe zwei Strukturen mit Arrays von Bytes und Booleschen Werten:
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;
}
Und der folgende Code:
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();
}
}
Das gibt mir folgende Ausgabe:
sizeof array of bytes: 3
sizeof array of bools: 12
Es scheint, dass a boolean
4 Bytes Speicher benötigt. Idealerweise boolean
würde a nur ein Bit dauern ( false
oder true
, 0
oder 1
, etc ..).
Was passiert hier? Ist der boolean
Typ wirklich so ineffizient?
Antworten:
Der Bool- Typ hat eine wechselvolle Historie mit vielen inkompatiblen Auswahlmöglichkeiten zwischen Sprachlaufzeiten. Dies begann mit einer historischen Designentscheidung von Dennis Ritchie, dem Erfinder der C-Sprache. Es gab keinen Bool- Typ, die Alternative war int, wobei ein Wert von 0 falsch darstellt und jeder andere Wert als wahr angesehen wurde .
Diese Auswahl wurde in Winapi übernommen, dem Hauptgrund für die Verwendung von Pinvoke. Es hat ein typedef, für
BOOL
das ein Alias für das Schlüsselwort int des C-Compilers ist . Wenn Sie ein explizites [MarshalAs] Attribut nicht gelten dann ein C # Bool auf eine BOOL umgewandelt wird, wodurch ein Feld erzeugt , das 4 Bytes lang ist.Was auch immer Sie tun, Ihre Strukturdeklaration muss mit der Laufzeitauswahl in der Sprache übereinstimmen, mit der Sie interagieren. Wie bereits erwähnt, BOOL für den winapi aber die meist C ++ Implementierungen wählte Byte , die meisten COM Automation Interop verwendet VARIANT_BOOL die eine ist kurz .
Die tatsächliche Größe eines C #
bool
beträgt ein Byte. Ein starkes Designziel der CLR ist, dass Sie es nicht herausfinden können. Das Layout ist ein Implementierungsdetail, das zu stark vom Prozessor abhängt. Prozessoren sind sehr wählerisch in Bezug auf Variablentypen und Ausrichtung. Falsche Entscheidungen können die Leistung erheblich beeinträchtigen und Laufzeitfehler verursachen. Indem das Layout nicht entdeckt werden kann, kann .NET ein universelles Typsystem bereitstellen, das nicht von der tatsächlichen Laufzeitimplementierung abhängt.Mit anderen Worten, Sie müssen zur Laufzeit immer eine Struktur marshallen, um das Layout festzulegen. Zu diesem Zeitpunkt erfolgt die Konvertierung vom internen Layout zum Interop-Layout. Dies kann sehr schnell sein, wenn das Layout identisch ist, langsam, wenn Felder neu angeordnet werden müssen, da dafür immer eine Kopie der Struktur erstellt werden muss. Der Fachbegriff hierfür ist blittable . Das Übergeben einer blittable Struktur an nativen Code ist schnell, da der Pinvoke-Marshaller einfach einen Zeiger übergeben kann.
Leistung ist auch der Hauptgrund, warum ein Bool kein einzelnes Bit ist. Es gibt nur wenige Prozessoren, die ein Bit direkt adressierbar machen. Die kleinste Einheit ist ein Byte. Eine zusätzliche Anweisung ist erforderlich, um das Bit aus dem Byte zu fischen, das nicht kostenlos ist. Und es ist niemals atomar.
Der C # -Compiler scheut sich nicht, Ihnen mitzuteilen, dass es 1 Byte dauert
sizeof(bool)
. Dies ist immer noch kein fantastischer Prädiktor für die Anzahl der Bytes, die ein Feld zur Laufzeit benötigt. Die CLR muss auch das .NET-Speichermodell implementieren und verspricht, dass einfache Variablenaktualisierungen atomar sind . Dies erfordert, dass die Variablen im Speicher richtig ausgerichtet sind, damit der Prozessor sie mit einem einzigen Speicherbuszyklus aktualisieren kann. Ziemlich oft benötigt ein Bool aus diesem Grund tatsächlich 4 oder 8 Bytes Speicher. Zusätzliche Polsterung, die hinzugefügt wurde, um sicherzustellen, dass das nächste Element richtig ausgerichtet ist.Die CLR nutzt den Vorteil, dass das Layout nicht entdeckt werden kann. Sie kann das Layout einer Klasse optimieren und die Felder neu anordnen, sodass die Auffüllung minimiert wird. Wenn Sie also eine Klasse mit einem bool + int + bool-Mitglied haben, würde es 1 + (3) + 4 + 1 + (3) Bytes Speicher benötigen, (3) ist das Auffüllen für insgesamt 12 Bytes. 50% Abfall. Das automatische Layout wird auf 1 + 1 + (2) + 4 = 8 Byte umgestellt. Nur eine Klasse hat ein automatisches Layout, Strukturen haben standardmäßig ein sequentielles Layout.
Noch düsterer ist, dass ein Bool in einem C ++ - Programm, das mit einem modernen C ++ - Compiler kompiliert wurde, der den AVX-Befehlssatz unterstützt, bis zu 32 Byte benötigen kann. Was eine 32-Byte-Ausrichtungsanforderung auferlegt, kann die Bool-Variable mit 31 Bytes Auffüllen enden. Auch der Hauptgrund, warum ein .NET-Jitter keine SIMD-Anweisungen ausgibt, kann die Ausrichtungsgarantie nicht erhalten, es sei denn, er ist explizit verpackt.
quelle
Erstens ist dies nur die Größe für Interop. Es repräsentiert nicht die Größe im verwalteten Code des Arrays. Das ist 1 Byte pro
bool
- zumindest auf meinem Computer. Sie können es selbst mit diesem Code testen:Für das Marshalling von Arrays nach Wert wie in der Dokumentation heißt es in der Dokumentation :
Also schauen wir uns an
ArraySubType
, und das hat Dokumentation von:Jetzt
UnmanagedType
gibt es:Das ist also die Standardeinstellung für
bool
und es sind 4 Bytes, da dies dem Win32 BOOL-Typ entspricht. Wenn Sie also mit Code zusammenarbeiten, der einBOOL
Array erwartet , macht es genau das, was Sie wollen.Jetzt können Sie stattdessen das
ArraySubType
as angeben, das wieI1
folgt dokumentiert ist:Wenn der Code, mit dem Sie zusammenarbeiten, 1 Byte pro Wert erwartet, verwenden Sie einfach:
Ihr Code zeigt dann an, dass erwartungsgemäß 1 Byte pro Wert belegt wird.
quelle