Memory layout of Julia Objects

Object layout (jl_value_t)

Структура jl_value_t — это название блока памяти, принадлежащего сборщику мусора Julia, представляющего данные, связанные с объектом Julia в памяти. При отсутствии информации о типе это просто непрозрачный указатель:

typedef struct jl_value_t* jl_pvalue_t;

Каждая структура jl_value_t содержится в структуре jl_typetag_t, которая содержит метаданные о объекте Julia, такие как его тип и доступность для сборщика мусора (gc):

typedef struct {
    opaque metadata;
    jl_value_t value;
} jl_typetag_t;

Тип любого объекта Julia является экземпляром объекта jl_datatype_t типа листа. Функцию jl_typeof() можно использовать для его запроса:

jl_value_t *jl_typeof(jl_value_t *v);

Макет объекта зависит от его типа. Методы рефлексии могут быть использованы для проверки этого макета. Поле можно получить, вызвав один из методов получения поля:

jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i);
jl_value_t *jl_get_field(jl_value_t *o, char *fld);

Если типы полей заранее известны как указатели, значения также можно извлечь напрямую с помощью доступа к массиву:

jl_value_t *v = value->fieldptr[n];

В качестве примера "упакованный" uint16_t хранится следующим образом:

struct {
    opaque metadata;
    struct {
        uint16_t data;        // -- 2 bytes
    } jl_value_t;
};

Этот объект создается с помощью jl_box_uint16(). Обратите внимание, что указатель jl_value_t ссылается на часть данных, а не на метаданные в верхней части структуры.

Значение может храниться "разобранным" во многих обстоятельствах (только данные, без метаданных, и, возможно, даже не хранится, а просто сохраняется в регистрах), поэтому небезопасно предполагать, что адрес коробки является уникальным идентификатором. Вместо этого следует использовать тест "egal" (соответствующий функции === в Julia) для сравнения двух неизвестных объектов на эквивалентность:

int jl_egal(jl_value_t *a, jl_value_t *b);

Эта оптимизация должна быть относительно прозрачной для API, поскольку объект будет "упакован" по мере необходимости, всякий раз, когда требуется указатель jl_value_t.

Обратите внимание, что изменение указателя jl_value_t в памяти разрешено только в том случае, если объект изменяемый. В противном случае изменение значения может повредить программу, и результат будет неопределенным. Свойство изменяемости значения можно запросить с помощью:

int jl_is_mutable(jl_value_t *v);

Если хранимый объект является jl_value_t, сборщик мусора Julia также должен быть уведомлен:

void jl_gc_wb(jl_value_t *parent, jl_value_t *ptr);

Однако раздел Embedding Julia руководства также является обязательным для чтения на данном этапе, так как он охватывает другие детали упаковки и распаковки различных типов, а также понимание взаимодействий с сборщиком мусора.

Зеркальные структуры для некоторых встроенных типов - это defined in julia.h. Соответствующие глобальные объекты jl_datatype_t создаются с помощью jl_init_types in jltypes.c.

Garbage collector mark bits

Сборщик мусора использует несколько битов из метаданных jl_typetag_t для отслеживания каждого объекта в системе. Дополнительные сведения об этом алгоритме можно найти в комментариях к garbage collector implementation in gc.c.

Object allocation

Большинство новых объектов выделяются с помощью jl_new_structv():

jl_value_t *jl_new_struct(jl_datatype_t *type, ...);
jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, uint32_t na);

Хотя объекты isbits также могут быть созданы непосредственно из памяти:

jl_value_t *jl_new_bits(jl_value_t *bt, void *data)

И некоторые объекты имеют специальные конструкторы, которые должны использоваться вместо вышеуказанных функций:

Типы:

jl_datatype_t *jl_apply_type(jl_datatype_t *tc, jl_tuple_t *params);
jl_datatype_t *jl_apply_array_type(jl_datatype_t *type, size_t dim);

Хотя это самые часто используемые опции, есть и более низкоуровневые конструкторы, которые вы можете найти, объявленные в julia.h. Они используются в jl_init_types(), чтобы создать начальные типы, необходимые для загрузки создания образа системы Julia.

Кортежи:

jl_tuple_t *jl_tuple(size_t n, ...);
jl_tuple_t *jl_tuplev(size_t n, jl_value_t **v);
jl_tuple_t *jl_alloc_tuple(size_t n);

Представление кортежей является весьма уникальным в экосистеме представления объектов Julia. В некоторых случаях объект Base.tuple() может быть массивом указателей на объекты, содержащиеся в кортеже, эквивалентном:

typedef struct {
    size_t length;
    jl_value_t *data[length];
} jl_tuple_t;

Однако в других случаях кортеж может быть преобразован в анонимный isbits тип и храниться без упаковки, или он может вообще не храниться (если он не используется в обобщенном контексте как jl_value_t*).

Символы:

jl_sym_t *jl_symbol(const char *str);

Функции и MethodInstance:

jl_function_t *jl_new_generic_function(jl_sym_t *name);
jl_method_instance_t *jl_new_method_instance(jl_value_t *ast, jl_tuple_t *sparams);

Массивы:

jl_array_t *jl_new_array(jl_value_t *atype, jl_tuple_t *dims);
jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr);
jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims);

Обратите внимание, что многие из них имеют альтернативные функции распределения для различных специальных целей. Список здесь отражает более распространенные использования, но более полный список можно найти, прочитав julia.h header file.

Внутри Julia память обычно выделяется с помощью newstruct() (или newobj() для специальных типов):

jl_value_t *newstruct(jl_value_t *type);
jl_value_t *newobj(jl_value_t *type, size_t nfields);

И на самом низком уровне память выделяется вызовом сборщика мусора (в gc.c), а затем помечается своим типом:

jl_value_t *jl_gc_allocobj(size_t nbytes);
void jl_set_typeof(jl_value_t *v, jl_datatype_t *type);
Out of date Warning

Документация и использование функции jl_gc_allocobj могут быть устаревшими.

Обратите внимание, что все объекты выделяются кратно 4 байтам и выравниваются по размеру указателя платформы. Память выделяется из пула для мелких объектов или напрямую с помощью malloc() для крупных объектов.

Singleton Types

Типы-синглетоны имеют только один экземпляр и не содержат полей данных. Экземпляры синглетонов имеют размер 0 байт и состоят только из своей метаданных. Например, nothing::Nothing.

Смотрите Singleton Types и Nothingness and missing values