isbits Union Optimizations

在 Julia 中,Array 类型同时包含 "bits" 值和堆分配的 "boxed" 值。区别在于值本身是存储在内联(在数组的直接分配内存中),还是数组的内存仅仅是指向其他地方分配的对象的指针集合。在性能方面,内联访问值显然比必须跟随指针访问实际值更具优势。"isbits" 的定义通常意味着任何具有固定、确定大小的 Julia 类型,这意味着没有 "pointer" 字段,参见 ?isbitstype

Julia 还支持联合类型,字面意思是一个类型集合的联合。自定义联合类型定义对于希望“跨越”名义类型系统(即显式子类型关系)的应用程序非常方便,并在这些本无关联的类型集合上定义方法或功能。然而,编译器面临的一个挑战是如何处理这些联合类型。简单的方法(实际上,Julia 在 0.7 之前也是这样做的)是简单地创建一个“盒子”,然后在盒子中指向实际值的指针,类似于之前提到的“盒装”值。然而,这种做法是不幸的,因为许多小的、原始的“位”类型(想想 UInt8Int32Float64 等)可以轻松地在这个“盒子”中内联,而无需任何间接访问值。自 0.7 以来,Julia 可以利用这种优化的主要方式有两种:类型中的 isbits 联合字段和 isbits 联合数组。

isbits Union Structs

Julia 现在包含了一种优化,其中类型中的“isbits Union”字段(mutable structstruct 等)将被内联存储。这是通过确定 Union 类型的“内联大小”来实现的(例如,Union{UInt8, Int16} 的大小为两个字节,这表示所需的最大 Union 类型 Int16 的大小),此外,还分配了一个额外的“类型标签字节”(UInt8),其值表示存储在“Union 字节”内联的实际值的类型。类型标签字节的值是实际值类型在 Union 类型的类型顺序中的索引。例如,对于类型为 Union{Nothing, UInt8, Int16} 的字段,类型标签值 0x02 表示在结构内存的 16 位字段中存储了一个 Int16 值;而 0x01 值则表示在字段内存的前 8 位中存储了一个 UInt8 值。最后,值 0x00 表示该字段将返回 nothing 值,尽管作为一个具有单一类型实例的单例类型,它在技术上具有 0 的大小。类型的 Union 字段的类型标签字节直接存储在字段计算的 Union 内存之后。

isbits Union Memory

Julia 现在也可以将 "isbits Union" 值内联存储在内存中,而不需要一个间接的盒子。这个优化是通过在实际数据的字节旁边存储一个额外的 "类型标签内存" 字节(每个元素一个字节)来实现的。这个类型标签内存的功能与类型字段案例相同:它的值指示实际存储的 Union 值的类型。 "类型标签内存" 直接跟在常规数据空间之后。因此,访问 isbits Union 数组的类型标签字节的公式是 a->data + a->length * a->elsize