مرحبا بالجميع. يتبقى وقت أقل وأقل حتى بدء الدورة التدريبية
"أمان نظم المعلومات" ، لذلك نواصل اليوم مشاركة المنشورات المخصصة لإطلاق هذه الدورة. بالمناسبة ، يعد هذا المنشور امتدادًا لهذين المادتين:
"أساسيات محركات JavaScript: النماذج العامة والتخزين المؤقت المضمّن. الجزء 1 " ،
" أساسيات محركات جافا سكريبت: النماذج العامة والتخزين المؤقت المضمّن. الجزء 2 " .
المقالة تصف الأساسيات. إنها مشتركة بين جميع محركات جافا سكريبت ، وليس فقط على
محركات V8 التي يعمل عليها المؤلفون (
بنديكت وماثياس ). كمطور جافا سكريبت ، أستطيع أن أقول إن الفهم العميق لكيفية عمل محرك جافا سكريبت سوف يساعدك على معرفة كيفية كتابة التعليمات البرمجية الفعالة.

في
مقالة سابقة ، ناقشنا كيفية تحسين محركات JavaScript الوصول إلى الكائنات والصفائف باستخدام النماذج وذاكرة التخزين المؤقت المضمّنة. في هذه المقالة ، سننظر في تحسين حلول خط الأنابيب وتسريع الوصول إلى خصائص النموذج الأولي.
ملاحظة: إذا كنت تفضل مشاهدة العروض التقديمية من قراءة المقالات ، فقم بمشاهدة هذا الفيديو . إذا لم يكن كذلك ، ثم تخطي ذلك وقراءة.
مستويات التحسين والمقايضاتآخر مرة اكتشفنا أن جميع محركات جافا سكريبت الحديثة ، في الواقع ، لديها نفس خط الأنابيب:

لقد أدركنا أيضًا أنه على الرغم من تشابه خطوط الأنابيب عالية المستوى من المحرك إلى المحرك ، إلا أن هناك اختلافًا في خط أنابيب التحسين. لماذا هذا هكذا؟ لماذا تحتوي بعض المحركات على مستويات تحسين أكثر من غيرها؟ الشيء هو جعل حل وسط بين الانتقال السريع إلى مرحلة تنفيذ التعليمات البرمجية أو قضاء المزيد من الوقت لتنفيذ التعليمات البرمجية مع الأداء الأمثل.

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

المفاضلة بين تأخير بدء التشغيل وسرعة التنفيذ هي السبب في أن بعض محركات JavaScript تفضل إضافة مستويات تحسين إضافية بين الخطوات. على سبيل المثال ، يضيف SpiderMonkey طبقة Baseline بين مترجمها ومترجم IonMonkey الأمثل الكامل:

يولد المترجم الشفري بسرعة الرمز الثنائي ، لكن الرمز الثنائي نفسه بطيء نسبياً. ينشئ الخط الأساسي رمزًا لفترة أطول قليلاً ، ولكنه يوفر أداءً محسّنًا في وقت التشغيل. أخيرًا ، يقضي برنامج التحويل البرمجي المحسن IonMonkey معظم كود توليد الجهاز ، لكن هذا الكود فعال للغاية.
دعونا نلقي نظرة على مثال محدد ونرى كيف تتعامل خطوط أنابيب المحركات المختلفة مع هذه المشكلة. هنا في الحلقة الساخنة ، يتم تكرار نفس الرمز في كثير من الأحيان.
let result = 0; for (let i = 0; i < 4242424242; ++i) { result += i; } console.log(result);
يبدأ V8 ببدء تشغيل bytecode في مترجم الإشعال. في مرحلة ما ، يحدد المشغل أن الكود ساخن ويطلق واجهة TurboFan ، التي تدمج بيانات التوصيف وتضع تمثيلًا أساسيًا للآلة في الكود. ثم يتم إرسالها إلى محسن TurboFan في موضوع آخر لمزيد من التحسين.

أثناء استمرار التحسين ، يواصل V8 تنفيذ التعليمات البرمجية في Ignition. في مرحلة ما ، عندما ينتهي المحسن وتلقينا رمز الجهاز القابل للتنفيذ ، ينتقل مباشرة إلى مرحلة التنفيذ.
SpyderMonkey يبدأ أيضًا تنفيذ bytecode في المترجم. ولكن يحتوي على مستوى أساسي إضافي ، مما يعني أنه يتم إرسال الرمز الساخن إلى هناك أولاً. يقوم مترجم Baseline بإنشاء رمز Baseline في الخيط الرئيسي ويستمر في التنفيذ في نهاية إنشاءه.

إذا تم تشغيل رمز Baseline لبعض الوقت ، فسوف يقوم SpiderMonkey في النهاية بتشغيل واجهة IonMonkey (واجهة IonMonkey الأمامية) وتشغيل المحسن ، وهذه العملية تشبه إلى حد بعيد V8. كل هذا يستمر في العمل في نفس الوقت في Baseline ، في حين تعمل IonMonkey في التحسين. أخيرًا ، عندما ينتهي المُحسِّن من عمله ، يتم تنفيذ التعليمات البرمجية المُحسّنة بدلاً من رمز الأساس.
تشبه بنية Chakra SpiderMonkey تمامًا ، لكن Chakra يحاول تشغيل المزيد من العمليات في نفس الوقت لتجنب حظر الخيط الرئيسي. بدلاً من تشغيل أي جزء من برنامج التحويل البرمجي في مؤشر الترابط الرئيسي ، تقوم Chakra بنسخ رمز التوصيف الجانبي وبيانات ملفات التعريف التي يحتاجها برنامج التحويل البرمجي ويرسلها إلى عملية برنامج التحويل البرمجي المخصص.

عندما يكون الرمز الذي تم إنشاؤه جاهزًا ، ينفذ المشغل رمز SimpleJIT هذا بدلاً من الرمز الثانوي. يحدث الشيء نفسه مع FullJIT. تتمثل ميزة هذا الأسلوب في أن الإيقاف المؤقت الذي يحدث أثناء النسخ يكون عادة أقصر بكثير من بدء تشغيل برنامج التحويل البرمجي الكامل (الواجهة الأمامية). من ناحية أخرى ، فإن هذا النهج له عيب. يكمن في حقيقة أن نسخ مجريات الأمور يمكن أن يتخطى بعض المعلومات التي ستكون مطلوبة لتحقيق التحسين ، لذلك يمكننا القول أنه يتم التضحية بجودة الرمز إلى حد ما من أجل تسريع العمل.
في JavaScriptCore ، تعمل جميع برامج التحويل البرمجي المحسنة بالكامل بالتوازي مع التنفيذ الأساسي لـ JavaScript. ليس هناك مرحلة النسخ. بدلاً من ذلك ، يبدأ مؤشر الترابط الرئيسي التحويل البرمجي في مؤشر ترابط آخر. يستخدم المترجمون ثم نظام تأمين معقد للوصول إلى بيانات ملفات التعريف من الخيط الرئيسي.

تتمثل ميزة هذا الأسلوب في أنه يقلل من كمية البيانات المهملة التي تظهر بعد التحسين في السلسلة الرئيسية. عيب هذا النهج هو أنه يتطلب حل مشاكل multithreading المعقدة وبعض حجب التكاليف لمختلف العمليات.
تحدثنا عن المفاضلة بين إنشاء الشفرة السريعة أثناء تشغيل المترجم الشفري وتوليد الشفرة السريعة باستخدام برنامج التحويل البرمجي الأمثل. ولكن هناك حل وسط آخر ، وهو يتعلق باستخدام الذاكرة. لتوضيح ذلك ، كتبت برنامج جافا سكريبت بسيط يضيف رقمين.
function add(x, y) { return x + y; } add(1, 2);
انظر إلى bytecode الذي تم إنشاؤه لوظيفة الإضافة بواسطة مترجم الإشعال في V8.
StackCheck Ldar a1 Add a0, [0] Return
لا تقلق بشأن الرمز الفرعي ، فلا يجب أن تكون قادرًا على قراءته. هنا من الضروري الانتباه إلى حقيقة أنه يحتوي على
4 تعليمات فقط .
عندما يصبح الرمز ساخنًا ، يولد TurboFan رمز الجهاز المحسن للغاية ، والذي يرد أدناه:
leaq rcx,[rip+0x0] movq rcx,[rcx-0x37] testb [rcx+0xf],0x1 jnz CompileLazyDeoptimizedCode push rbp movq rbp,rsp push rsi push rdi cmpq rsp,[r13+0xe88] jna StackOverflow movq rax,[rbp+0x18] test al,0x1 jnz Deoptimize movq rbx,[rbp+0x10] testb rbx,0x1 jnz Deoptimize movq rdx,rbx shrq rdx, 32 movq rcx,rax shrq rcx, 32 addl rdx,rcx jo Deoptimize shlq rdx, 32 movq rax,rdx movq rsp,rbp pop rbp ret 0x18
يوجد بالفعل الكثير من الفرق هنا ، خاصةً مقارنة بالفرق الأربعة التي رأيناها في الرمز الثانوي. بشكل عام ، يعتبر bytecode أكثر رحابة من رمز الجهاز ، ولا سيما رمز الآلة المحسن. Bytecode ، من ناحية أخرى ، يتم تنفيذه من قبل المترجم ، في حين يمكن تنفيذ التعليمات البرمجية الأمثل مباشرة من قبل المعالج.
هذا أحد الأسباب وراء عدم قيام محركات JavaScript "بتحسين كل شيء". كما رأينا سابقًا ، يستغرق إنشاء رمز الجهاز المُحسّن كثيرًا من الوقت ، وبالتالي يتطلب المزيد من الذاكرة.
للتلخيص: السبب في أن محركات جافا سكريبت لديها مستويات مختلفة من التحسين هو إيجاد حل وسط بين توليد الكود السريع باستخدام المترجم وتوليد الكود السريع باستخدام المحول البرمجي الأمثل. تتيح لك إضافة المزيد من مستويات التحسين اتخاذ قرارات أكثر استنارة ، استنادًا إلى تكلفة التعقيد الإضافي والنفقات العامة أثناء التنفيذ. بالإضافة إلى ذلك ، هناك مفاضلة بين مستوى التحسين واستخدام الذاكرة. لهذا السبب تحاول محركات JavaScript تحسين الوظائف الساخنة فقط.
تحسين الوصول إلى خصائص النموذج الأوليتحدثنا في
آخر مرة عن كيفية تحسين محركات JavaScript لتحميل خصائص الكائن باستخدام النماذج وذاكرة التخزين المؤقت المضمنة. تذكر أن المحركات تخزن أشكال الكائنات بشكل منفصل عن قيم الكائن.

تتيح لك النماذج استخدام التحسين باستخدام ذاكرة التخزين المؤقت المضمنة أو المرحلية لفترة قصيرة. عند العمل معًا ، يمكن للنماذج وأجهزة الشهادات المتكاملة تسريع الوصول المتكرر إلى الخصائص من نفس المكان في الكود.

وهكذا انتهى الجزء الأول من المنشور ، ويمكن العثور على الفصول الدراسية وبرمجة النموذج الأولي في
الجزء الثاني . تقليديًا ، ننتظر تعليقاتك ومناقشاتك العاصفة ، كما ندعوك إلى
يوم مفتوح في الدورة التدريبية "أمان نظم المعلومات".