isbits Union Optimizations

В Julia тип Array хранит как "битовые" значения, так и "упакованные" значения, выделенные в куче. Различие заключается в том, хранится ли само значение встроенно (в непосредственно выделенной памяти массива) или память массива представляет собой просто коллекцию указателей на объекты, выделенные в другом месте. С точки зрения производительности доступ к встроенным значениям явно является преимуществом по сравнению с необходимостью следовать указателю к фактическому значению. Определение "isbits" обычно означает любой тип Julia с фиксированным, определенным размером, что означает отсутствие полей "указателей", см. ?isbitstype.

Julia также поддерживает объединения типов, буквально объединение набора типов. Определения пользовательских объединений типов могут быть чрезвычайно полезны для приложений, желающих "пересекать" номинальную типовую систему (т.е. явные подтиповые отношения) и определять методы или функциональность для этих, иначе не связанных, наборов типов. Однако задача компилятора заключается в том, как обрабатывать эти объединения типов. Наивный подход (и действительно, то, что сама Julia делала до версии 0.7) заключается в том, чтобы просто создать "коробку", а затем указатель в коробке на фактическое значение, аналогично ранее упомянутым "упакованным" значениям. Это, однако, неудачно, поскольку существует множество небольших, примитивных типов "битов" (подумайте о UInt8, Int32, Float64 и т.д.), которые легко могли бы поместиться в этой "коробке" без необходимости в какой-либо индирекции для доступа к значению. Существует два основных способа, которыми Julia может воспользоваться этой оптимизацией начиная с версии 0.7: поля объединений isbits в типах и массивы объединений isbits.

isbits Union Structs

Julia теперь включает оптимизацию, при которой поля "isbits Union" в типах (mutable struct, struct и т. д.) будут храниться инлайн. Это достигается путем определения "инлайн размера" типа Union (например, Union{UInt8, Int16} будет иметь размер два байта, что представляет собой размер, необходимый для самого большого типа Union Int16), и, кроме того, выделяя дополнительный "байт метки типа" (UInt8), значение которого указывает на тип фактического значения, хранящегося инлайн в "байтах Union". Значение байта метки типа — это индекс типа фактического значения в порядке типов типа Union. Например, значение метки типа 0x02 для поля с типом Union{Nothing, UInt8, Int16} будет указывать на то, что значение Int16 хранится в 16 битах поля в памяти структуры; значение 0x01 будет указывать на то, что значение UInt8 хранится в первых 8 битах из 16 бит памяти поля. Наконец, значение 0x00 сигнализирует о том, что для этого поля будет возвращено значение nothing, хотя, как тип одиночка с единственным экземпляром типа, он технически имеет размер 0. Байты метки типа для поля Union типа хранятся непосредственно после вычисленной памяти Union поля.

isbits Union Memory

Юлия теперь также может хранить значения "isbits Union" непосредственно в памяти, вместо того чтобы требовать косвенной упаковки. Оптимизация достигается за счет хранения дополнительной "памяти для тегов типов" в байтах, по одному байту на элемент, наряду с байтами фактических данных. Эта память для тегов типов выполняет ту же функцию, что и поле типа: ее значение указывает на тип фактически хранимого значения Union. "Память для тегов типов" непосредственно следует за обычным пространством данных. Таким образом, формула для доступа к байтам тегов типов массива isbits Union выглядит как a->data + a->length * a->elsize.