isbits Union Optimizations
Juliaでは、Array
型は「ビット」値とヒープに割り当てられた「ボックス化」値の両方を保持します。区別は、値自体がインラインで保存されているか(配列の直接割り当てメモリ内)、または配列のメモリが単に他の場所に割り当てられたオブジェクトへのポインタのコレクションであるかです。パフォーマンスの観点から、インラインで値にアクセスすることは、実際の値へのポインタをたどる必要があることに比べて明らかに利点です。「isbits」の定義は、一般的に固定された決定的なサイズを持つ任意のJulia型を意味し、「ポインタ」フィールドを持たないことを示します。詳細は?isbitstype
を参照してください。
Juliaは、型の集合の和、つまりUnion型をサポートしています。カスタムUnion型の定義は、名義型システム(すなわち、明示的なサブタイプ関係)を「横断」し、これらの、そうでなければ無関係な型の集合に対してメソッドや機能を定義したいアプリケーションにとって非常に便利です。しかし、コンパイラの課題は、これらのUnion型をどのように扱うかを決定することです。単純なアプローチ(実際、Julia自身が0.7以前に行ったこと)は、単に「ボックス」を作成し、そのボックス内に実際の値へのポインタを置くことです。これは、前述の「ボックス化された」値と似ています。しかし、これは不幸なことです。なぜなら、UInt8
、Int32
、Float64
などの小さな原始的な「ビット」型が、この「ボックス」にインラインで収まることができ、値へのアクセスのために間接参照を必要としないからです。0.7以降、Juliaがこの最適化を活用できる主な方法は2つあります:型内のisbits Unionフィールドとisbits Union配列です。
isbits Union Structs
Juliaは現在、型(mutable struct
、struct
など)の「isbits Union」フィールドをインラインで保存する最適化を含んでいます。これは、Union型の「インラインサイズ」を決定することによって実現されます(例えば、Union{UInt8, Int16}
は2バイトのサイズを持ち、これは最大のUnion型Int16
に必要なサイズを表します)。さらに、実際の値が「Unionバイト」のインラインに保存されているタイプを示す値を持つ追加の「タイプタグバイト」(UInt8
)を割り当てます。タイプタグバイトの値は、Union型のタイプの順序における実際の値の型のインデックスです。例えば、Union{Nothing, UInt8, Int16}
型のフィールドに対して0x02
のタイプタグ値は、構造体のメモリ内のフィールドの16ビットにInt16
値が保存されていることを示します;0x01
の値は、フィールドのメモリの最初の8ビットにUInt8
値が保存されていることを示します。最後に、0x00
の値は、このフィールドに対してnothing
値が返されることを示しますが、単一の型インスタンスを持つシングルトン型として、技術的にはサイズが0です。型のUnionフィールドのタイプタグバイトは、フィールドの計算されたUnionメモリのすぐ後に直接保存されます。
isbits Union Memory
Juliaは、インダイレクションボックスを必要とせずに、メモリ内に「isbits Union」値をインラインで格納できるようになりました。この最適化は、実際のデータのバイトに加えて、要素ごとに1バイトの「タイプタグメモリ」を格納することによって実現されます。このタイプタグメモリは、タイプフィールドケースと同じ機能を果たします。その値は、実際に格納されたUnion値のタイプを示します。「タイプタグメモリ」は、通常のデータスペースのすぐ後に続きます。したがって、isbits Union配列のタイプタグバイトにアクセスするための式は a->data + a->length * a->elsize
です。