Ahead of Time Compilation
هذا المستند يصف تصميم وبنية نظام التجميع المسبق (AOT) في جوليا. يُستخدم هذا النظام عند إنشاء صور النظام وصور الحزم. تقع معظم التنفيذات الموصوفة هنا في aotcompile.cpp
و staticdata.c
و processor.cpp
Introduction
على الرغم من أن جوليا عادةً ما تقوم بترجمة الشيفرة في الوقت الفعلي (JIT)، إلا أنه من الممكن ترجمة الشيفرة مسبقًا وحفظ الشيفرة الناتجة في ملف. يمكن أن يكون هذا مفيدًا لعدد من الأسباب:
- لتقليل الوقت الذي يستغرقه بدء عملية جوليا.
- لتقليل الوقت المستغرق في مترجم JIT بدلاً من تنفيذ الشيفرة (الوقت حتى التنفيذ الأول، TTFX).
- لتقليل كمية الذاكرة المستخدمة بواسطة مترجم JIT.
High-Level Overview
تصف الوصف التالي لمحة عن تفاصيل التنفيذ الحالية لخط الأنابيب من النهاية إلى النهاية الذي يحدث داخليًا عندما يقوم المستخدم بتجميع وحدة AOT جديدة، كما يحدث عندما يكتب using Foo
. من المحتمل أن تتغير هذه التفاصيل بمرور الوقت مع تنفيذ طرق أفضل للتعامل معها، لذا قد لا تتطابق التنفيذات الحالية تمامًا مع تدفق البيانات والوظائف الموصوفة أدناه.
Compiling Code Images
أولاً، يجب تحديد الطرق التي تحتاج إلى التجميع إلى كود أصلي. لا يمكن القيام بذلك إلا من خلال تنفيذ الكود الذي سيتم تجميعه، حيث أن مجموعة الطرق التي تحتاج إلى التجميع تعتمد على أنواع المعاملات المرسلة إلى الطرق، وقد لا تكون استدعاءات الطرق مع مجموعات معينة من الأنواع معروفة حتى وقت التشغيل. خلال هذه العملية، يتم تتبع الطرق الدقيقة التي تراها المترجم للتجميع لاحقًا، مما ينتج عنه تتبع التجميع.
حاليًا، عند تجميع الصور، تقوم جوليا بتشغيل توليد التتبع في عملية مختلفة عن العملية التي تقوم بإجراء تجميع AOT. يمكن أن يكون لهذا تأثيرات عند محاولة استخدام مصحح الأخطاء أثناء ما قبل التجميع. أفضل طريقة لتصحيح ما قبل التجميع باستخدام مصحح الأخطاء هي استخدام مصحح الأخطاء rr، تسجيل شجرة العمليات بالكامل، استخدام rr ps
لتحديد العملية الفاشلة ذات الصلة، ثم استخدام rr replay -p PID
لإعادة تشغيل العملية الفاشلة فقط.
بمجرد تحديد الطرق التي سيتم تجميعها، يتم تمريرها إلى دالة jl_create_system_image
. تقوم هذه الدالة بإعداد عدد من الهياكل البيانية التي ستستخدم عند تسلسل الشيفرة الأصلية إلى ملف، ثم تستدعي jl_create_native
مع مصفوفة الطرق. تقوم jl_create_native
بتشغيل توليد الشيفرة على الطرق وتنتج وحدة أو أكثر من وحدات LLVM. ثم تسجل jl_create_system_image
بعض المعلومات المفيدة حول ما أنتجته توليد الشيفرة من الوحدة (الوحدات).
يتم تمرير الوحدة (الوحدات) بعد ذلك إلى jl_dump_native
، جنبًا إلى جنب مع المعلومات المسجلة بواسطة jl_create_system_image
. يحتوي jl_dump_native
على الكود اللازم لتسلسل الوحدة (الوحدات) إلى كود بت، أو كائن، أو ملفات تجميع حسب خيارات سطر الأوامر المرسلة إلى جوليا. ثم يتم كتابة الكود المتسلسل والمعلومات إلى ملف كأرشيف.
الخطوة النهائية هي تشغيل رابط النظام على ملفات الكائن في الأرشيف الذي تم إنتاجه بواسطة jl_dump_native
. بمجرد اكتمال هذه الخطوة، يتم إنتاج مكتبة مشتركة تحتوي على الكود المترجم.
Loading Code Images
عند تحميل صورة الشيفرة، يتم تحميل المكتبة المشتركة التي ينتجها الرابط في الذاكرة. ثم يتم تحميل بيانات صورة النظام من المكتبة المشتركة. تحتوي هذه البيانات على معلومات حول الأنواع، والأساليب، وحالات الشيفرة التي تم تجميعها في المكتبة المشتركة. تُستخدم هذه البيانات لاستعادة حالة وقت التشغيل إلى ما كانت عليه عندما تم تجميع صورة الشيفرة.
إذا تم تجميع صورة الشيفرة باستخدام تعدد الإصدارات، سيقوم المحمل باختيار الإصدار المناسب من كل دالة للاستخدام بناءً على ميزات وحدة المعالجة المركزية المتاحة على الجهاز الحالي.
بالنسبة لصور النظام، نظرًا لأنه لم يتم تحميل أي كود آخر، فإن حالة وقت التشغيل الآن هي نفسها كما كانت عندما تم تجميع صورة الكود. بالنسبة لصور الحزمة، قد يكون قد تغيرت البيئة مقارنةً عندما تم تجميع الكود، لذا يجب التحقق من كل طريقة مقابل جدول الطرق العالمي لتحديد ما إذا كانت لا تزال كودًا صالحًا.
Compiling Methods
Tracing Compiled Methods
تحتوي جوليا على علامة سطر الأوامر لتسجيل جميع الطرق التي يتم تجميعها بواسطة مترجم JIT، --trace-compile=filename
. عندما يتم تجميع دالة ولديها هذه العلامة مع اسم ملف، ستقوم جوليا بطباعة بيان ما قبل التجميع إلى ذلك الملف مع الطريقة وأنواع المعاملات التي تم استدعاؤها بها. وبالتالي، فإن هذا يولد نصًا برمجيًا مسبق التجميع يمكن استخدامه لاحقًا في عملية التجميع AOT. تحتوي حزمة PrecompileTools على أدوات يمكن أن تجعل الاستفادة من هذه الوظيفة أسهل لمطوري الحزم.
jl_create_system_image
jl_create_system_image
يحفظ كل البيانات الوصفية الخاصة بـ Julia اللازمة لاستعادة حالة وقت التشغيل لاحقًا. يتضمن ذلك بيانات مثل حالات الشيفرة، حالات الطرق، جداول الطرق، ومعلومات النوع. تقوم هذه الوظيفة أيضًا بإعداد الهياكل البيانية اللازمة لتسلسل الشيفرة الأصلية إلى ملف. أخيرًا، تستدعي jl_create_native
لإنشاء وحدة أو أكثر من وحدات LLVM تحتوي على الشيفرة الأصلية للطرق المرسلة إليها. jl_create_native
مسؤولة عن تشغيل توليد الشيفرة على الطرق المرسلة إليها.
jl_dump_native
jl_dump_native
مسؤول عن تسلسل وحدة LLVM التي تحتوي على الشيفرة الأصلية إلى ملف. بالإضافة إلى الوحدة، يتم تجميع بيانات صورة النظام التي تنتجها jl_create_system_image
كمتغير عالمي. ناتج هذه الطريقة هو كود بت، كائن، و/أو أرشيفات تجميع تحتوي على الشيفرة وبيانات صورة النظام.
jl_dump_native
هو عادةً أحد أكبر مصادر الوقت عند إصدار الشيفرة الأصلية، حيث يقضي الكثير من الوقت في تحسين LLVM IR وإصدار الشيفرة الآلية. لذلك، فإن هذه الدالة قادرة على استخدام تعدد الخيوط في خطوات تحسين الشيفرة وإصدار الشيفرة الآلية. يتم تحديد هذا التعدد بناءً على حجم الوحدة، ولكن يمكن تجاوزه صراحةً عن طريق تعيين متغير البيئة JULIA_IMAGE_THREADS
. العدد الأقصى الافتراضي للخيوط هو نصف عدد الخيوط المتاحة، ولكن يمكن أن يؤدي تقليله إلى تقليل استخدام الذاكرة القصوى أثناء التجميع.
jl_dump_native
يمكنه أيضًا إنتاج كود أصلي مُحسّن لعدة معماريات، عند دمجه مع محمل جوليا. يتم تفعيل ذلك عن طريق تعيين متغير البيئة JULIA_CPU_TARGET
ويتم التوسط فيه بواسطة تمرير تعدد الإصدارات في خط أنابيب التحسين. لجعل هذا يعمل مع تعدد الخيوط، تتم إضافة خطوة توضيحية قبل تقسيم الوحدة إلى وحدات فرعية يتم إصدارها في خيوطها الخاصة، وتستخدم هذه الخطوة التوضيحية المعلومات المتاحة في جميع أنحاء الوحدة بأكملها لتحديد الوظائف التي يتم استنساخها لمعماريات مختلفة. بمجرد حدوث التوضيح، يمكن للخيوط الفردية إصدار كود لمعماريات مختلفة بالتوازي، مع العلم أن وحدة فرعية مختلفة مضمونة لإنتاج الوظائف اللازمة التي سيتم استدعاؤها بواسطة وظيفة مستنسخة.
بعض البيانات الوصفية الأخرى حول كيفية تسلسل الوحدة مخزنة أيضًا في الأرشيف، مثل عدد الخيوط المستخدمة لتسلسل الوحدة وعدد الدوال التي تم تجميعها.
Static Linking
الخطوة النهائية في عملية تجميع AOT هي تشغيل رابط على ملفات الكائن في الأرشيف الذي تم إنتاجه بواسطة jl_dump_native
. ينتج عن ذلك مكتبة مشتركة تحتوي على الكود المترجم. يمكن بعد ذلك تحميل هذه المكتبة المشتركة بواسطة جوليا لاستعادة حالة وقت التشغيل. عند تجميع صورة نظام، يتم استخدام الرابط الأصلي الذي يستخدمه مترجم C لإنتاج المكتبة المشتركة النهائية. بالنسبة لصور الحزم، يتم استخدام رابط LLVM LLD لتوفير واجهة ربط أكثر اتساقًا.
Loading Code Images
Loading the Shared Library
الخطوة الأولى في تحميل صورة الشيفرة هي تحميل المكتبة المشتركة التي ينتجها الرابط. يتم ذلك عن طريق استدعاء jl_dlopen
على المسار إلى المكتبة المشتركة. هذه الوظيفة مسؤولة عن تحميل المكتبة المشتركة وحل جميع الرموز في المكتبة.
Loading Native Code
يحتاج المحمل أولاً إلى تحديد ما إذا كان الكود الأصلي الذي تم تجميعه صالحًا للمعمارية التي يعمل عليها المحمل. هذا ضروري لتجنب تنفيذ التعليمات التي لا تتعرف عليها وحدات المعالجة المركزية الأقدم. يتم ذلك من خلال التحقق من ميزات وحدة المعالجة المركزية المتاحة على الجهاز الحالي مقابل ميزات وحدة المعالجة المركزية التي تم تجميع الكود من أجلها. عندما يتم تمكين النسخ المتعددة، سيختار المحمل النسخة المناسبة من كل دالة للاستخدام بناءً على ميزات وحدة المعالجة المركزية المتاحة على الجهاز الحالي. إذا لم تكن هناك أي مجموعات ميزات تم نسخها بشكل متعدد، فسيرمي المحمل خطأ.
جزء من عملية النسخ المتعددة ينشئ عددًا من المصفوفات العالمية لجميع الوظائف في الوحدة. عندما تكون هذه العملية متعددة الخيوط، يتم إنشاء مصفوفة من المصفوفات، والتي يعيد تنظيمها المحمل إلى مصفوفة كبيرة واحدة تحتوي على جميع الوظائف التي تم تجميعها لهذه البنية. تحدث عملية مماثلة للمتغيرات العالمية في الوحدة.
Setting Up Julia State
يستخدم المحمل بعد ذلك المتغيرات العالمية والدوال الناتجة عن تحميل الكود الأصلي لإعداد هياكل بيانات نواة جوليا في العملية الحالية. تتضمن هذه الإعدادات إضافة الأنواع والأساليب إلى وقت تشغيل جوليا، وجعل الكود الأصلي المخزن متاحًا للاستخدام من قبل دوال جوليا الأخرى والمترجم. بالنسبة لصور الحزم، يجب التحقق من صحة كل طريقة، حيث يجب أن تتطابق حالة جدول الطرق العالمية مع الحالة التي تم تجميع صورة الحزمة من أجلها. على وجه الخصوص، إذا كانت مجموعة مختلفة من الطرق موجودة في وقت التحميل مقارنة بوقت التجميع لصورة الحزمة، يجب إبطال الطريقة وإعادة تجميعها عند الاستخدام الأول. هذا ضروري لضمان أن تظل دلالات التنفيذ كما هي بغض النظر عما إذا كانت الحزمة قد تم تجميعها مسبقًا أو إذا تم تنفيذ الكود مباشرة. لا تحتاج صور النظام إلى إجراء هذا التحقق، حيث إن جدول الطرق العالمية فارغ في وقت التحميل. وبالتالي، فإن صور النظام لديها أوقات تحميل أسرع من صور الحزم.