isbits Union Optimizations

Juliaでは、Array型は「ビット」値とヒープに割り当てられた「ボックス化」値の両方を保持します。区別は、値自体がインラインで保存されているか(配列の直接割り当てメモリ内)、または配列のメモリが他の場所に割り当てられたオブジェクトへのポインタのコレクションであるかどうかです。パフォーマンスの観点から、インラインで値にアクセスすることは、実際の値へのポインタをたどる必要があるよりも明らかに利点です。「isbits」の定義は、一般的に固定された決定的なサイズを持つ任意のJulia型を意味し、「ポインタ」フィールドを持たないことを示します。詳細は?isbitstypeを参照してください。

Juliaは、型の集合の和、つまりUnion型をサポートしています。カスタムUnion型の定義は、名義型システム(すなわち、明示的なサブタイプ関係)を「横断」し、これらの、そうでなければ無関係な型の集合に対してメソッドや機能を定義したいアプリケーションにとって非常に便利です。しかし、コンパイラの課題は、これらのUnion型をどのように扱うかを決定することです。単純なアプローチ(実際、Julia自身が0.7以前に行っていたこと)は、「ボックス」を作成し、そのボックス内に実際の値へのポインタを置くことです。これは、前述の「ボックス化された」値と似ています。しかし、これは不幸なことです。なぜなら、UInt8Int32Float64などの小さな原始的な「ビット」型が、この「ボックス」にインラインで収まることができ、値へのアクセスのために間接参照を必要としないからです。0.7以降、Juliaがこの最適化を活用できる主な方法は、型内のisbits Unionフィールドとisbits Union配列の2つです。

isbits Union Structs

Julia には、タイプ(mutable structstruct など)の "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 です。