isbits Union Optimizations
In Julia hält der Array
-Typ sowohl "Bits"-Werte als auch heap-zugewiesene "verpackte" Werte. Der Unterschied besteht darin, ob der Wert selbst inline (im direkt zugewiesenen Speicher des Arrays) gespeichert ist oder ob der Speicher des Arrays einfach eine Sammlung von Zeigern auf an anderer Stelle zugewiesene Objekte ist. In Bezug auf die Leistung ist der Zugriff auf inline-Werte eindeutig ein Vorteil gegenüber dem Folgen eines Zeigers zum tatsächlichen Wert. Die Definition von "isbits" bedeutet allgemein, dass es sich um jeden Julia-Typ mit einer festen, bestimmten Größe handelt, was bedeutet, dass keine "Zeiger"-Felder vorhanden sind, siehe ?isbitstype
.
Julia unterstützt auch Union-Typen, ganz wörtlich die Vereinigung einer Menge von Typen. Benutzerdefinierte Union-Typdefinitionen können äußerst nützlich für Anwendungen sein, die "über" das nominale Typsystem (d.h. explizite Untertypbeziehungen) hinweg arbeiten möchten und Methoden oder Funktionalitäten für diese ansonsten nicht verwandten Typen definieren. Eine Herausforderung für den Compiler besteht jedoch darin, wie diese Union-Typen behandelt werden sollen. Der naive Ansatz (und tatsächlich das, was Julia selbst vor 0.7 tat), besteht darin, einfach eine "Box" zu erstellen und dann einen Zeiger in der Box auf den tatsächlichen Wert zu setzen, ähnlich den zuvor erwähnten "verpackten" Werten. Dies ist jedoch bedauerlich, da es eine Vielzahl von kleinen, primitiven "Bits"-Typen (denken Sie an UInt8
, Int32
, Float64
usw.) gibt, die sich problemlos inline in dieser "Box" unterbringen lassen, ohne dass eine Indirektion für den Zugriff auf den Wert erforderlich ist. Es gibt zwei Hauptwege, wie Julia ab Version 0.7 von dieser Optimierung profitieren kann: isbits Union-Felder in Typen und isbits Union-Arrays.
isbits Union Structs
Julia umfasst jetzt eine Optimierung, bei der "isbits Union"-Felder in Typen (mutable struct
, struct
usw.) inline gespeichert werden. Dies wird erreicht, indem die "Inline-Größe" des Union-Typs bestimmt wird (z. B. hat Union{UInt8, Int16}
eine Größe von zwei Bytes, was die Größe des größten Union-Typs Int16
darstellt), und zusätzlich wird ein zusätzliches "Typ-Tag-Byte" (UInt8
) zugewiesen, dessen Wert den Typ des tatsächlich inline gespeicherten Wertes in den "Union-Bytes" signalisiert. Der Wert des Typ-Tag-Bytes ist der Index des Typs des tatsächlichen Wertes in der Reihenfolge der Typen des Union-Typs. Zum Beispiel würde ein Typ-Tag-Wert von 0x02
für ein Feld mit dem Typ Union{Nothing, UInt8, Int16}
anzeigen, dass ein Int16
-Wert in den 16 Bits des Feldes im Speicher der Struktur gespeichert ist; ein Wert von 0x01
würde anzeigen, dass ein UInt8
-Wert in den ersten 8 Bits der 16 Bits des Speichers des Feldes gespeichert wurde. Schließlich signalisiert ein Wert von 0x00
, dass der nothing
-Wert für dieses Feld zurückgegeben wird, obwohl er technisch gesehen als Singleton-Typ mit einer einzigen Typinstanz eine Größe von 0 hat. Das Typ-Tag-Byte für ein Union-Feld eines Typs wird direkt nach dem berechneten Union-Speicher des Feldes gespeichert.
isbits Union Memory
Julia kann jetzt auch "isbits Union"-Werte inline in einem Speicher speichern, anstatt eine Indirektionsbox zu benötigen. Die Optimierung wird erreicht, indem ein zusätzliches "Typ-Tag-Speicher" von Bytes, ein Byte pro Element, neben den Bytes der tatsächlichen Daten gespeichert wird. Dieses Typ-Tag-Speicher erfüllt die gleiche Funktion wie das Typfeld: Sein Wert signalisiert den Typ des tatsächlich gespeicherten Union-Wertes. Das "Typ-Tag-Speicher" folgt direkt dem regulären Datenspeicher. Die Formel zum Zugriff auf die Typ-Tag-Bytes eines isbits Union-Arrays lautet a->data + a->length * a->elsize
.