قبل تنفيذ الشفرة المكتوبة من قبلنا ، فإنها تقطع شوطا طويلا إلى حد ما.
قام Andrey Melikhov في تقريره عن RIT ++ 2018 بفحص كل خطوة على هذا المسار باستخدام مثال محرك V8. تعال تحت القطة لمعرفة ما يمنحنا فهمًا عميقًا لمبادئ المترجم وكيفية جعل تعليمات JavaScript أكثر إنتاجية.

سنكتشف ما إذا كانت WASM رمزًا فضيًا لتحسين أداء الشفرة ، وما إذا كانت التحسينات مبررة دائمًا.
المفسد: "التحسين المبكر هو أصل جميع العلل" دونالد كنوث.
حول المتحدث: يعمل Andrei Melikhov في Yandex.Money ، ويكتب بنشاط على Node.js ، وأقل في المتصفح ، لذا فإن خادم JavaScript أقرب إليه. أندرو يدعم ويطور مجتمع devShacht ، لذا تحقق من
GitHub أو
Medium .
الدافع والمصطلحات
اليوم سنتحدث عن تجميع JIT. أعتقد أن هذا مثير للاهتمام بالنسبة لك ، لأنك تقرأ هذا. ومع ذلك ، دعنا نوضح لماذا تحتاج إلى معرفة ما هو JIT وكيف يعمل V8 ، ولماذا لا تكفي كتابة React في متصفح.
- يسمح لك بكتابة كود أكثر كفاءة ، لأن لغتنا محددة.
- ويكشف عن ألغاز لماذا تتم كتابة الشفرة في مكتبات الأشخاص الآخرين بهذه الطريقة ، وليس بطريقة أخرى. في بعض الأحيان نأتي عبر مكتبات قديمة ونرى ما هو مكتوب هناك بطريقة ما غريبة ، ولكن إذا كان ذلك ضروريًا ، فهو ليس ضروريًا - ليس واضحًا. عندما تعرف كيف يعمل ، فأنت تفهم لماذا تم ذلك.
- هذا مثير للاهتمام . بالإضافة إلى ذلك ، فإنه يسمح لنا بفهم ما يتواصل مع أكسل Rauschmeier و Benedict Moyrer و Dan Abramov على Twitter.

تقول ويكيبيديا أن JavaScript هي لغة برمجة عالية المستوى مع كتابة ديناميكية. سنتعامل مع هذه الشروط.
تجميع وتفسيرالتجميع - عندما يتم تسليم البرنامج في رمز ثنائي ، ويتم تحسينه في البداية للبيئة التي سيعمل فيها.
التفسير - عندما نقدم الرمز كما هو.
يتم تسليم جافا سكريبت كما هي - إنها لغة مترجمة ، كما هو مكتوب على ويكيبيديا.
كتابة ديناميكية وثابتةغالبًا ما يتم الخلط بين الكتابة الثابتة والديناميكية مع الكتابة الضعيفة والقوية. على سبيل المثال ، C هي لغة ذات كتابة ضعيفة ثابتة. جافا سكريبت لديها كتابة ديناميكية ضعيفة.
أيهما أفضل؟ إذا تم تجميع البرنامج ، فهو موجه نحو البيئة التي سيتم تنفيذه فيها ، مما يعني أنه سيعمل بشكل أفضل. الكتابة الثابتة تجعل هذا الرمز أكثر كفاءة. في JavaScript ، العكس هو الصحيح.
ولكن في الوقت نفسه ، أصبح تطبيقنا أكثر تعقيدًا: على كل من العميل والخادم ، تظهر مجموعات ضخمة على Node.js ، والتي تعمل بشكل جيد وتأتي لتحل محل تطبيقات Java.
ولكن كيف يعمل كل شيء إذا بدا أنه خاسر في البداية.
سوف JIT التوفيق بين الجميع! أو على الأقل حاول.
لدينا JIT (تجميع في الوقت المناسب) يحدث في وقت التشغيل. سنتحدث عنها.
محركات شبيبة
- شقرا غير محبوب ، الموجود في Internet Explorer. إنه لا يعمل حتى مع JavaScript ، ولكن مع Jscript - فهناك مجموعة فرعية من هذا القبيل.
- شقرا الحديثة و ChakraCore التي تعمل في Edge ؛
- SpiderMonkey في FireFox ؛
- JavaScriptCore في WebKit. كما أنها تستخدم في React Native. إذا كان لديك تطبيق RN لنظام Android ، فسيتم تشغيله أيضًا على JavaScriptCore - يأتي المحرك مرفقًا بالتطبيق.
- V8 هو المفضل لدي. إنه ليس الأفضل ، أنا أعمل فقط مع Node.js ، حيث هو المحرك الرئيسي ، كما هو الحال في جميع المتصفحات القائمة على Chrome.
- الكركدن وناشورن هي المحركات المستخدمة في جافا. بمساعدتهم ، يمكنك أيضًا تنفيذ JavaScript هناك.
- JerryScript - للأجهزة المدمجة ؛
- وآخرون ...
يمكنك كتابة محركك الخاص ، ولكن إذا انتقلت نحو التنفيذ الفعال ، فستصل إلى نفس المخطط تقريبًا ، والذي سأعرضه لاحقًا.
اليوم سنتحدث عن محرك V8 ، ونعم ، تم تسميته على اسم محرك 8 أسطوانات.
نحن نتسلق تحت غطاء المحرك
كيف يتم تنفيذ جافا سكريبت؟
- يوجد كود مكتوب بلغة JavaScript التي يتم توفيرها.
- هو يجرؤ.
- قيد التنفيذ ؛
- يتم الحصول على النتيجة.

يؤدي التحليل إلى تحويل التعليمات البرمجية إلى
شجرة بناء جملة مجردة . AST هو عرض البنية النحوية للتعليمة البرمجية على شكل شجرة. هذا في الواقع ملائم للبرنامج ، على الرغم من صعوبة قراءته.

يتم تمثيل الحصول على عنصر صفيف باستخدام الفهرس 1 في شكل شجرة كعامل وعاملين: تحميل الخاصية بواسطة المفتاح وهذه المفاتيح.
أين يتم استخدام AST؟
AST ليس فقط في المحركات. باستخدام AST ، يكتب العديد من الأدوات المساعدة ملحقات ، بما في ذلك:
- ESLint ؛
- بابل
- أجمل
- Jscodeshift.
على سبيل المثال ، الشيء الرائع لـ Jscodeshift ، والذي لا يعرف عنه الجميع حتى الآن ، يسمح لك بكتابة التحولات. إذا قمت بتغيير واجهة برمجة التطبيقات (API) لدالة ، يمكنك تعيين هذه التحويلات عليها وإجراء تغييرات في المشروع بأكمله.

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

لذلك ، في حين أن المتصفحات لديها القليل من جافا سكريبت - تمييز الخط ، وفتح شيء ما ، وإغلاقه. ولكن لدينا الآن تطبيقات - SPA ، Node.js ، وأصبح
المترجم عنق الزجاجة .
تحسين مترجم JIT
بدلاً من المترجم ، يظهر مترجم JIT الأمثل ، وهو مترجم فقط في الوقت المناسب. يعمل المترجمون قبل الوقت قبل تنفيذ التطبيق ، و JIT - أثناء. فيما يتعلق بمسألة التحسين ، يحاول المترجم JIT تخمين كيفية تنفيذ الشفرة ، والأنواع التي سيتم استخدامها ، وتحسين الشفرة بحيث تعمل بشكل أفضل.
يسمى هذا التحسين
المضاربة ، لأنه يتكهن بمعرفة ما حدث للرمز من قبل. أي أنه إذا تم استدعاء شيء من نوع الرقم 10 مرات ، فإن المترجم يعتقد أن هذا سيحدث طوال الوقت ويحسن هذا النوع.
بطبيعة الحال ، إذا دخل منطقي الإدخال ، يحدث انحراف. خذ بعين الاعتبار وظيفة تضيف أرقامًا.
const foo=(a, b) => a + b;
foo (1, 2);
foo (2, 3);
مطوية مرة واحدة ، المرة الثانية. يقوم المترجم ببناء التنبؤ: "هذه أرقام ، لدي حل رائع لإضافة أرقام!" وأنت تكتب
foo('WTF', 'JS')
، وتمرير الأسطر إلى الوظيفة - لدينا JavaScript ، يمكننا إضافة سطر برقم.
عند هذه النقطة ، يحدث انحراف.

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

كان ذلك حتى العام الماضي. في العام الماضي ، كنت قد سمعت الكثير من التقارير من Google أنها أطلقت خط أنابيب جديد مع TurboFan والآن يبدو المخطط أكثر بساطة.

ومن المثير للاهتمام ، ظهر مترجم هنا.

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

في 2013-2014 ، بدأ الناس في استخدام الأجهزة المحمولة للوصول إلى الإنترنت أكثر من سطح المكتب. في الأساس ، هذا ليس جهاز iPhone ، ولكن من أجهزة أبسط - لديهم ذاكرة صغيرة ومعالج ضعيف.

أعلاه هو رسم بياني للتحليل الأولي لـ 1 ميغابايت من التعليمات البرمجية قبل بدء المترجم. يمكن ملاحظة أن سطح المكتب يفوز كثيرًا. iPhone ليس سيئًا أيضًا ، ولكنه يحتوي على محرك مختلف ، ونحن نتحدث عن V8 ، الذي يعمل في Chrome.
هل تعلم أنه إذا قمت بتثبيت Chrome على iPhone ، فسيظل يعمل على JavaScriptCore؟
وبالتالي ، يضيع الوقت - وهذا مجرد تحليل ، وليس تنفيذ - تم تحميل ملفك ، وهو يحاول فهم ما هو مكتوب فيه.

عند حدوث الغموض ، تحتاج إلى أخذ شفرة المصدر مرة أخرى ، أي يجب تخزينه في مكان ما. استغرق الأمر الكثير من الذاكرة.
وهكذا ، كان للمترجم مهمتان:
- تقليل تحليل النفقات العامة ؛
- تقليل استهلاك الذاكرة.
تم حل المهام بالتبديل إلى مترجم الشفرة الثانوية.
Bytecode في Chrome عبارة عن آلة تسجيل مع بطارية . يحتوي SpiderMonkey على آلة مكدسة ، توجد جميع البيانات على المكدس ، ولكن لا توجد سجلات. ها هم.
لن نقوم بتحليل كامل لكيفية عمل ذلك ، ما عليك سوى إلقاء نظرة على جزء التعليمات البرمجية.

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

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

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

لدينا مُنشئ يقوم بإنشاء كائن جديد من النوع Point.
- قم بإنشاء كائن.
- قم بربط فئة مخفية بها ، والتي تقول أنها كائن من النوع Point.
- أضفنا الحقل x - فئة مخفية جديدة تقول أنها كائن من النوع Point ، حيث تأتي قيمة x أولاً.
- تمت إضافة y - الطبقات المخفية الجديدة ، حيث x ، ثم y.
- إنشاء كائن آخر - يحدث نفس الشيء. أي أنه يربط أيضًا ما تم إنشاؤه بالفعل. في هذه اللحظة ، يكون هذان الجسمان من نفس النوع (عبر الفئات المخفية).
- عند إضافة حقل جديد إلى الكائن الثاني ، تظهر فئات مخفية جديدة في الكائن. الآن بالنسبة للمحرك p1 و p2 ، هذه كائنات من فئات مختلفة ، لأن لديهم هياكل مختلفة
- إذا قمت بنقل الكائن الأول في مكان ما ، فعندما تقوم بنقل الكائن الثاني هناك ، سيحدث الغموض. يشير الأول إلى فئة مخفية ، والثاني إلى فئة أخرى.
كيف يمكنني التحقق من الفصول المخفية؟في Node.js ، يمكنك تشغيل العقدة —السماح -الأصليين- بناء الجملة. ثم ستتاح لك الفرصة لكتابة أوامر في بناء جملة خاص ، والذي ، بالطبع ، لا يمكن استخدامه في الإنتاج. يبدو هذا:
%HaveSameMap({'a':1}, {'b':1})
لا أحد يضمن أن هذه الأوامر ستعمل غدًا ، فهي ليست في مواصفات ECMAScript ، كل هذا من أجل تصحيح الأخطاء.
ما رأيك سيكون نتيجة استدعاء دالة٪ HaveSameMap لكائنين. الإجابة الصحيحة خاطئة ، لأن أحدهما يحتوي على حقل ، والآخر يحتوي على
b . هذه أشياء مختلفة. يمكن استخدام هذه المعرفة لتقنية التخزين المؤقت المضمن.
مخابئ مضمنة
نحن نسمي وظيفة بسيطة للغاية تقوم بإرجاع حقل من كائن. تبدو إعادة الوحدة بسيطة للغاية. ولكن إذا نظرت إلى مواصفات ECMAScript ، فسترى أن هناك قائمة ضخمة بما عليك القيام به للحصول على الحقل من الكائن. لأنه ، إذا لم يكن الحقل في الكائن ، فمن الممكن أن يكون في النموذج الأولي الخاص به. ربما يكون محدد ، معتدل وما إلى ذلك. كل هذا يحتاج إلى التحقق.

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

- إذا قمنا باستدعاء الوظيفة لأول مرة ، فكل شيء على ما يرام ، فقد قام المترجم بالتحسين
- بالنسبة للمكالمة الثانية ، يتم حفظ حالة أحادية الشكل.
- أسمي الوظيفة مرة ثالثة ، وأمر كائنًا مختلفًا قليلاً {x: 3، y: 1}. يحدث الغموض ، إذا ظهر ، نذهب إلى حالة متعددة الأشكال. الآن يعرف الرمز الذي ينفذ هذه الوظيفة أن هناك نوعين مختلفين من الكائنات يمكن أن تطير إليها.
- إذا مررنا كائنات مختلفة عدة مرات ، فإنها تظل في حالة متعددة الأشكال ، مضيفين رموزًا جديدة. ولكن في مرحلة ما يستسلم ويذهب إلى حالة ضخمة ، أي متى: "يصل عدد كبير جدًا من الأنواع المختلفة عند المدخل - لا أعرف كيفية تحسينه!"
يبدو أنه يُسمح الآن بأربع حالات متعددة الأشكال ، ولكن قد يكون هناك غدًا 8. ويقرر ذلك مطورو المحرك. من الأفضل أن نبقى في حالة أحادية الشكل ، في الحالات القصوى ، حالة متعددة الأشكال. يعد الانتقال بين الحالات أحادية الشكل ومتعددة الأشكال مكلفًا ، لأنك ستحتاج إلى الذهاب إلى المترجم ، والحصول على الرمز مرة أخرى وتحسينه مرة أخرى.
المصفوفات
في JavaScript ، بصرف النظر عن المصفوفات المكتوبة المحددة ، هناك نوع واحد
صفيف. هناك 6 منهم في محرك V8:
1. [1 ، 2 ، 3 ، 4] // PACKED_SMI_ELEMENTS - فقط مجموعة معبأة من عدد صحيح صغير. هناك تحسينات له.
2. [1.2 ، 2.3 ، 3.4 ، 4.6] // PACKED_DOUBLE_ELEMENTS - مجموعة مليئة بالعناصر المزدوجة ، وهناك أيضًا تحسينات لها ، ولكن أبطأ.
3. [1 ، 2 ، 3 ، 4 ، "X"] // PACKED_ELEMENTS - صفيف معبأ يوجد به أشياء وسلاسل وكل شيء آخر. بالنسبة له أيضًا ، هناك تحسينات.
الأنواع الثلاثة التالية هي صفائف من نفس النوع مثل الأنواع الثلاثة الأولى ، ولكن مع ثقوب:
4. [1، / * hole * /، 2، / * hole * /، 3، 4] // HOLEY_SMI_ELEMENTS
5. [1.2، / * hole * /، 2، / * hole * /، 3،4] // HOLEY_DOUBLE_ELEMENTS
6. [1، / * hole * /، 'X'] // HOLEY_ELEMENTS
عندما تظهر ثقوب في المصفوفات الخاصة بك ، تصبح التحسينات أقل كفاءة. يبدأون في العمل بشكل سيئ ، لأنه من المستحيل المرور عبر هذه المجموعة على التوالي ، والفرز من خلال التكرارات. كل نوع لاحق أقل أمثل

في الرسم البياني ، يتم تحسين كل شيء أعلاه بشكل أسرع. أي أن جميع طرقك الأصلية - الخريطة ، التصغير ، الفرز - بالداخل محسنة بشكل جيد. ولكن مع كل نوع ، يصبح التحسين أسوأ.
على سبيل المثال ، جاء صفيف بسيط [
1 ،
2 ،
3 ] إلى الإدخال (عدد صحيح صغير معبأ بالنوع). قمنا بتغيير هذا المصفوفة بشكل طفيف بإضافة مضاعفة إليه - انتقلنا إلى حالة PACKED_DOUBLE_ELEMENTS. أضف كائنًا إليه - انتقل إلى الحالة التالية ، المستطيل الأخضر PACKED_ELEMENTS. أضف ثقوبًا إليها - انتقل إلى حالة HOLEY_ELEMENTS. نريد استعادتها إلى حالتها السابقة حتى تصبح "جيدة" مرة أخرى - نحذف كل ما كتبناه ونظل في نفس الحالة ... مع ثقوب! أي ، HOLEY_ELEMENTS في الجزء السفلي الأيمن من الرسم التخطيطي. مرة أخرى هذا لا يعمل. يمكن أن تصبح صفائفك أسوأ فقط ، ولكن ليس العكس.
كائن يشبه المصفوفة
غالبًا ما تصادف كائنات تشبه المصفوفة - هذه كائنات تشبه المصفوفات لأنها تحتوي على علامة طول. في الواقع ، هم مثل قطة قرصان ، أي أنها تبدو متشابهة ، ولكن في كفاءة استهلاك الروم ، ستكون القطة أسوأ من القراصنة. وبالمثل ، فإن الكائن الذي يشبه الصفيف يشبه المصفوفة ، ولكنه ليس فعالًا.

لدينا كائنين شبيهين بالصفيف هما الحجج والوثائق. QuerySelectorAII. هناك أشياء وظيفية جميلة.

حصلنا على خريطة - قمنا بنزعها من النموذج الأولي ويبدو أننا يمكن استخدامها. ولكن إذا لم تكن هناك مجموعة من المدخلات ، فلن يكون هناك تحسين. محركنا غير قادر على القيام بتحسين على الأشياء.
ما الذي يجب فعله؟
- خيار المدرسة القديمة - من خلال slice.call () يتحول إلى مصفوفة حقيقية.
- الخيار الحديث أفضل: اكتب (... الباقي) ، واحصل على مجموعة نظيفة - وليس الحجج - كل شيء على ما يرام!
مع querySelectorAll نفس الشيء - بسبب الانتشار ، يمكننا تحويله إلى مصفوفة كاملة والعمل مع جميع التحسينات.
صفائف كبيرة
Riddle: صفيف جديد (1000) مقابل صفيف = []
أي خيار أفضل: قم بإنشاء مصفوفة كبيرة فورًا واملأها بـ 1000 كائن في حلقة ، أو قم بإنشاء مجموعة فارغة واملأها تدريجيًا؟
الجواب الصحيح: يعتمد على.
ما الفرق؟
- عندما نقوم بإنشاء مصفوفة في الطريقة الأولى وملء 1000 عنصر ، نقوم بإنشاء 1000 حفرة. لن يتم تحسين هذا الصفيف. لكنه سيكتب بسرعة.
- إنشاء صفيف وفقًا للمتغير الثاني ، يتم تخصيص القليل من الذاكرة ، نكتب ، على سبيل المثال ، 60 عنصرًا ، يتم تخصيص المزيد من الذاكرة قليلاً ، إلخ.
أي في الحالة الأولى نكتب بسرعة - نحن نعمل ببطء ؛ في الثانية نكتب ببطء - نعمل بسرعة.
جامع القمامة
جامع القمامة يأكل أيضا القليل من الوقت والموارد. بدون الغوص بعمق ، سأعطيك القاعدة الأكثر شيوعًا.

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

هذه هي الطريقة التي يعمل بها جامع القمامة التلقائي - فهو ينظف الأشياء على أساس عدم وجود روابط لها. هذان خوارزميتان مختلفتان.
- Scavenge سريع ولكنه غير فعال.
- Mark-Sweep بطيء ولكنه فعال.
إذا بدأت في تحديد خصائص استهلاك الذاكرة في Node.js ، فستحصل على شيء مثل هذا.

في البداية ، تنمو بشكل مفاجئ - هذا هو عمل خوارزمية Scavenge. ثم يحدث انخفاض حاد - جمعت خوارزمية Mark-Sweep القمامة في مساحة الأشياء القديمة. في هذه اللحظة ، يبدأ كل شيء في التباطؤ قليلاً.
لا يمكنك التحكم فيه ، لأنك لا تعرف متى سيحدث. يمكنك ضبط الأحجام فقط.
لذلك ، يحتوي خط الأنابيب على مرحلة جمع القمامة التي تستهلك الوقت.

أسرع؟
دعونا ننظر إلى المستقبل. ماذا تفعل بعد ذلك ، كيف تكون أسرع؟

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

تكمن المشكلة في أن الرمز الثانوي مختلف الآن. كما قلت: في Safari ، في FireFox آخر ، في Chrome الثالث. ومع ذلك ، قدم مطورو Mozilla و Bloomberg و Facebook مثل هذا
الاقتراح ، ولكن هذا هو المستقبل.
هناك مشكلة أخرى - التجميع والتحسين وإعادة التحسين ، إذا لم يخمن المترجم. تخيل أن هناك لغة مكتوبة بشكل ثابت في المدخلات التي تنتج تعليمات برمجية فعالة ، مما يعني أن إعادة التحسين لم تعد ضرورية ، لأن ما حصلنا عليه فعال بالفعل. لا يمكن تجميع مثل هذا الإدخال وتحسينه إلا مرة واحدة. ستكون الشفرة الناتجة أكثر كفاءة وسيتم تنفيذها بشكل أسرع.
ما الذي يمكن فعله أيضًا؟ تخيل أن هذه اللغة لديها إدارة ذاكرة يدوية. ثم لا تحتاج إلى جامع القمامة. أصبح الخط أقصر وأسرع.

خمن كيف يبدو؟
WebAssembly تقريبًا
هذه هي الطريقة التي تعمل بها: إدارة الذاكرة اليدوية ، مكتوبة بشكل ثابت
اللغات والتنفيذ السريع.

هل WebAssembly رمز نقطي فضي؟

لا ، لأنها تعني JavaScript. WASM لا تستطيع أن تفعل أي شيء حتى الآن. ليس لديه حق الوصول إلى DOM API. إنه داخل محرك JavaScript - داخل نفس المحرك! يفعل كل شيء من خلال JavaScript ، لذلك
لن تسرع WASM التعليمات البرمجية الخاصة بك . يمكن أن يسرع الحسابات الفردية ، ولكن التبادل بين JavaScript و WASM سيكون عنق الزجاجة.
لذلك ، في حين أن لغتنا هي جافا سكريبت وفقط ، وبعض المساعدة من الصندوق الأسود.
المجموع
يمكن تمييز ثلاثة أنواع من التحسين.
●
تحسينات خوارزميةهناك مقال "
ربما لا تحتاج إلى Rust لتسريع شبيبة JS " بقلم فياتشيسلاف إيغوروف ، الذي طور V8 ذات مرة ويطور الآن Dart. إعادة سرد قصتها لفترة وجيزة.
كانت هناك مكتبة جافا سكريبت لا تعمل بسرعة كبيرة. قام بعض الأشخاص بإعادة كتابته في Rust ، وتم تجميعه وحصلوا على WebAssembly ، وبدأ التطبيق في العمل بشكل أسرع. قرر Vyacheslav Egorov كمطور JS من ذوي الخبرة الإجابة عليها. قام بتطبيق تحسينات خوارزمية ، وأصبح حل JavaScript أسرع بكثير من حل Rust. في المقابل ، رأى هؤلاء الرجال هذا ، وأجروا نفس التحسينات ، وفازوا مرة أخرى ، ولكن ليس كثيرًا - يعتمد ذلك على المحرك: في موزيلا فازوا ، في Chrome لم يفعلوا.
لم نتحدث اليوم عن تحسينات خوارزمية ، ولا تتحدث الواجهة الأمامية عادةً عنها. هذا أمر سيئ للغاية ، لأن
الخوارزميات تسمح أيضًا بتشغيل الشفرة بشكل أسرع . يمكنك ببساطة إزالة الدورات التي لا تحتاج إليها.
●
تحسينات خاصة باللغةهذا ما تحدثنا عنه اليوم: يتم تفسير لغتنا بشكل ديناميكي.
يتيح لك فهم كيفية عمل المصفوفات والأشياء وموحد الشكل كتابة تعليمات
برمجية فعالة . هذا يجب أن يعرف ويكتب بشكل صحيح.
●
تحسينات خاصة بالمحركهذه هي التحسينات الأكثر خطورة. إذا كان مطورك الذكي جدًا ، ولكن غير الاجتماعي ، الذي طبق الكثير من هذه التحسينات ولم يخبر أي شخص عنها ، لم يكتب الوثائق ، ثم إذا فتحت الشفرة ، فلن ترى JavaScript ، ولكن ، على سبيل المثال ، Crankshaft Script. أي أن جافا سكريبت مكتوبة بفهم عميق لكيفية عمل محرك العمود المرفقي قبل عامين. يعمل كل شيء ، ولكن الآن لم تعد هناك حاجة.
لذلك ، يجب توثيق هذه التحسينات بالضرورة ، وتغطيتها باختبارات تثبت فعاليتها في الوقت الحالي. يجب مراقبتها. تحتاج إلى الذهاب إليهم فقط في الوقت الذي تباطأ فيه حقًا في مكان ما - لا يمكنك الاستغناء عن هذه الأجهزة العميقة. لذلك ، تبدو العبارة الشهيرة دونالد كنوث منطقية.

لا حاجة لمحاولة تنفيذ أي نوع من التحسينات الصعبة فقط لأنك تقرأ مراجعات إيجابية عنها.
يجب على المرء أن يخاف من هذه التحسينات ، تأكد من توثيق وترك المقاييس. بشكل عام ، اجمع المقاييس دائمًا.
المقاييس مهمة!روابط مفيدة:Frontend Conf Moscow 4 5 . 15 , , :
- (KeepSolid) , Offline First Persistent Storage
- (TradingView) WebGL WebAssembly , , API .
- , Google Docs.