Memory layout of Julia Objects

Object layout (jl_value_t)

هيكل jl_value_t هو الاسم لكتلة من الذاكرة مملوكة لجمع القمامة في جوليا، تمثل البيانات المرتبطة بكائن جوليا في الذاكرة. في غياب أي معلومات نوع، هو ببساطة مؤشر غير شفاف:

typedef struct jl_value_t* jl_pvalue_t;

كل بنية jl_value_t موجودة داخل بنية jl_typetag_t التي تحتوي على معلومات وصفية حول كائن جوليا، مثل نوعه وقابلية الوصول لجمع القمامة (gc):

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

نوع أي كائن في جوليا هو مثيل لكائن ورقة 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 يشير إلى جزء البيانات، وليس إلى البيانات الوصفية في أعلى الهيكل.

قد يتم تخزين قيمة "غير مغلقة" في العديد من الظروف (فقط البيانات، دون البيانات الوصفية، وربما لا يتم تخزينها حتى ولكن يتم الاحتفاظ بها في السجلات)، لذا من غير الآمن افتراض أن عنوان صندوق هو معرف فريد. يجب استخدام اختبار "الإيغال" (الذي يتوافق مع دالة === في جوليا) بدلاً من ذلك لمقارنة كائنين غير معروفين من أجل التماثل:

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

يجب أن تكون هذه التحسينات شفافة نسبيًا لواجهة برمجة التطبيقات، حيث سيتم "تغليف" الكائن عند الطلب، كلما كان هناك حاجة إلى مؤشر jl_value_t.

لاحظ أن تعديل مؤشر jl_value_t في الذاكرة مسموح فقط إذا كان الكائن قابلاً للتغيير. خلاف ذلك، قد يؤدي تعديل القيمة إلى فساد البرنامج والنتيجة ستكون غير محددة. يمكن الاستعلام عن خاصية القابلية للتغيير لقيمة ما باستخدام:

int jl_is_mutable(jl_value_t *v);

إذا كان الكائن المخزن هو jl_value_t، يجب إبلاغ جامع القمامة في جوليا أيضًا:

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

ومع ذلك، فإن قسم Embedding Julia من الدليل هو أيضًا قراءة مطلوبة في هذه المرحلة، لتغطية تفاصيل أخرى حول التغليف وفك التغليف لمختلف الأنواع، وفهم تفاعلات gc.

تراكيب المرآة لبعض الأنواع المدمجة هي 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() لإنشاء الأنواع الأولية اللازمة لبدء إنشاء صورة نظام جوليا.

الأزواج:

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);

تمثيل التوابل فريد للغاية في نظام تمثيل كائنات جوليا. في بعض الحالات، قد يكون كائن 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.

داخليًا في جوليا، يتم عادةً تخصيص التخزين بواسطة 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