Расположение объектов Julia в памяти
Структура объекта (jl_value_t
)
Структура jl_value_t
— это имя блока памяти, принадлежащего сборщику мусора Julia и представляющего данные, связанные с объектом Julia в памяти. При отсутствии информации о типе это просто непрозрачный указатель.
typedef struct jl_value_t* jl_pvalue_t;
Каждая структура jl_value_t
содержится в структуре jl_typetag_t
, которая содержит метаданные об объекте Julia, такие как его тип и доступность сборщику мусора.
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);
Структура объекта зависит от его типа. Для ее проверки можно использовать методы отражения. Доступ к полю можно получить, вызвав один из методов get-field.
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);
Раздел руководства Внедрение кода Julia является обязательным для прочтения на данном этапе, поскольку в нем рассматриваются другие детали упаковки и распаковки различных типов, а также понимание взаимодействия в рамках сборки мусора.
Зеркальные структуры для некоторых встроенных типов определены в julia.h
. Соответствующие глобальные объекты jl_datatype_t
создаются с помощью jl_init_types
в jltypes.c
.
Биты метки сборщика мусора
Сборщик мусора использует несколько битов из части метаданных структуры jl_typetag_t
для отслеживания каждого объекта в системе. Более подробную информацию об этом алгоритме можно найти в комментариях к реализации сборщика мусора в gc.c
.
Выделение объектов
Большинство новых объектов выделяется с помощью 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_new_arrayv(jl_value_t *atype, ...);
jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr);
jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, size_t nc);
jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, size_t nc, size_t z);
jl_array_t *jl_alloc_vec_any(size_t n);
Обратите внимание, что у многих из них есть альтернативные функции выделения для различных особых целей. В приведенном здесь списке указаны самые распространенные варианты использования. Более полный список можно найти, изучив файл заголовка julia.h
.
В 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);
Обратите внимание, что все объекты выделяются кратно 4 байт и согласуются с размером указателя платформы. Память выделяется из пула для небольших объектов или напрямую с помощью функции malloc()
для крупных объектов.
Singleton Types
Одинарные типы имеют только один экземпляр и не имеют полей данных. Отдельные экземпляры имеют размер 0 байт и состоят только из своих метаданных. Например, |