ستناقش المادة ، الجزء الأول من الترجمة التي ننشرها اليوم ، كيف يختار محرك V8 JavaScript أفضل الطرق لتمثيل قيم JS المختلفة في الذاكرة ، وكيف يؤثر ذلك على الآليات الداخلية لـ V8 فيما يتعلق بالعمل مع ما يسمى
النماذج كائنات (الشكل). كل هذا سيساعدنا في تحديد جوهر
مشكلة أداء React الأخيرة.

أنواع بيانات JavaScript
يمكن أن تحتوي كل قيمة JavaScript على نوع واحد فقط من أنواع البيانات الثمانية الموجودة:
Number
،
String
،
Symbol
،
BigInt
،
Boolean
،
Undefined
،
Null
Object
.
أنواع بيانات JavaScriptيمكن تحديد نوع القيمة باستخدام معامل
typeof
، ولكن هناك استثناء واحد مهم:
typeof 42;
كما ترى ، فإن الأمر
typeof null
يُرجع
'object'
، وليس
'null'
، على الرغم من حقيقة أن
null
له نوعه الخاص -
Null
. لفهم سبب هذا النوع من السلوك ، نأخذ في الاعتبار حقيقة أن مجموعة جميع أنواع JavaScript يمكن تقسيمها إلى مجموعتين:
- كائنات (أي ، اكتب
Object
). - القيم البدائية (أي ، أي قيم غير موضوعية).
في ضوء هذه المعرفة ، اتضح أن القيمة
null
"لا قيمة لها" ، في حين أن "
undefined
" يعني "لا قيمة".
القيم البدائية ، الكائنات ، لاغية وغير محددةباتباع هذه الانعكاسات بروح Java ، قام Brendan Eich بتصميم JavaScript بحيث يقوم مشغل
typeof
بإرجاع
'object'
لقيم تلك الأنواع الموجودة في الشكل السابق إلى اليمين. جميع القيم وجوه و
null
get هنا. هذا هو السبب في أن التعبير
typeof null === 'object'
صحيح ، على الرغم من وجود نوع منفصل
Null
في مواصفات اللغة.
التعبير typeof v === 'object' صحيحتمثيل القيم
يجب أن تكون محركات JavaScript قادرة على تمثيل أي قيم JavaScript في الذاكرة. ومع ذلك ، من المهم ملاحظة أن أنواع القيم في JavaScript منفصلة عن الطريقة التي تمثل بها محركات JS في الذاكرة.
على سبيل المثال ، قيمة 42 في JavaScript هي من النوع type.
typeof 42;
هناك عدة طرق لتمثيل أعداد صحيحة مثل 42 في الذاكرة:
وفقًا لمعيار ECMAScript ، فإن الأرقام هي قيم الفاصلة العائمة 64 بت ، والمعروفة باسم أرقام الفاصلة العائمة المزدوجة الدقة (Float64). ومع ذلك ، هذا لا يعني أن محركات JavaScript تقوم دائمًا بتخزين الأرقام في طريقة عرض Float64. سيكون ذلك غير فعال للغاية! يمكن للمحركات استخدام تمثيلات داخلية أخرى للأرقام - طالما يتطابق سلوك القيم تمامًا مع سلوك أرقام Float64.
معظم الأرقام في تطبيقات JS الحقيقية ، كما اتضح فيما بعد ، هي
فهارس صفيف ECMAScript صالحة. هذا هو - أعداد صحيحة في النطاق من 0 إلى 2
32 -2.
array[0];
يمكن لمحركات JavaScript اختيار التنسيق الأمثل لتمثيل هذه القيم في الذاكرة. يتم ذلك من أجل تحسين الكود الذي يعمل مع عناصر الصفيف باستخدام الفهارس. يحتاج المعالج الذي يقوم بعمليات الوصول إلى الذاكرة إلى توفر مؤشرات الصفيف كأرقام مخزنة في طريقة عرض مع
إضافة اثنين . إذا قمنا بدلاً من ذلك بتمثيل فهارس المصفوفات في شكل قيم Float64 ، فإن هذا سيعني إهدار موارد النظام ، حيث إن المحرك سيحتاج بعد ذلك إلى تحويل أرقام Float64 إلى تنسيق مع إضافة اثنين والعكس بالعكس كلما قام شخص ما بالوصول إلى عنصر صفيف.
يعد تمثيل الأرقام 32 بت مع إضافة ما يصل إلى رقمين مفيدًا ليس فقط لتحسين العمل مع المصفوفات. بشكل عام ، يمكن الإشارة إلى أن المعالج ينفذ عمليات عدد صحيح أسرع بكثير من العمليات التي تستخدم قيم الفاصلة العائمة. هذا هو السبب في المثال التالي ، تكون الدورة الأولى دون مشاكل أسرع مرتين مقارنة بالدورة الثانية.
for (let i = 0; i < 1000; ++i) {
الأمر نفسه ينطبق على العمليات الحسابية باستخدام العوامل الرياضية.
على سبيل المثال ، يعتمد أداء المشغل لأخذ الجزء المتبقي من القسمة من جزء الكود التالي على الأرقام المشاركة في العمليات الحسابية.
const remainder = value % divisor;
إذا تم تمثيل كلتا المعاملتين بأعداد صحيحة ، فإن المعالج يمكن أن يحسب النتيجة بكفاءة عالية. يوجد تحسين إضافي في V8 للحالات التي يتم فيها تمثيل المعامل divisor بواسطة رقم يمثل قوة اثنين. بالنسبة للقيم الممثلة كأرقام الفاصلة العائمة ، تكون الحسابات أكثر تعقيدًا وتستغرق وقتًا أطول.
نظرًا لأن عمليات الأعداد الصحيحة عادة ما يتم تنفيذها بشكل أسرع بكثير من العمليات على قيم الفاصلة العائمة ، فقد يبدو أن المحركات يمكنها دائمًا تخزين جميع الأعداد الصحيحة وجميع نتائج عمليات الأعداد الصحيحة بتنسيق مع إضافة اثنين. لسوء الحظ ، فإن مثل هذا النهج ينتهك مواصفات ECMAScript. كما ذكرنا سابقًا ، يوفر المعيار تمثيل الأرقام بتنسيق Float64 ، ويمكن أن تؤدي بعض العمليات ذات الأعداد الصحيحة إلى ظهور النتائج في شكل أرقام الفاصلة العائمة. من المهم أن تحقق محركات JS نتائج صحيحة في مثل هذه الحالات.
على الرغم من أن جميع الأرقام الموجودة على الجانب الأيسر من التعبيرات في المثال السابق عبارة عن أعداد صحيحة ، فإن جميع الأرقام الموجودة على الجانب الأيمن من التعبيرات هي قيم نقاط عائمة. هذا هو السبب في أنه لا يمكن تنفيذ أي من العمليات السابقة بشكل صحيح باستخدام تنسيق 32 بت مع إضافة ما يصل إلى اثنين. يجب أن تولي محركات جافا سكريبت اهتمامًا خاصًا لضمان حصولك على النتائج الصحيحة (على الرغم من قدرتها على المظهر غير العادي - في المثال السابق) عند إجراء عمليات عدد صحيح.
في حالة الأعداد الصحيحة الصغيرة التي تقع في نطاق التمثيل 31 بت للأعداد الصحيحة الموقعة ، يستخدم V8 تمثيل خاص يسمى
Smi
. يتم تمثيل كل ما لا يمثل قيمة
HeapObject
كقيمة
HeapObject
، وهو عنوان بعض الكيانات في الذاكرة. بالنسبة للأرقام التي لا تدخل في نطاق
Smi
، لدينا نوع خاص من
HeapObject
- ما يسمى
HeapNumber
.
-Infinity
كما ترى من المثال السابق ، يتم تمثيل بعض أرقام JS كـ
HeapNumber
، وبعضها مثل
HeapNumber
. تم تحسين محرك V8 من حيث معالجة أرقام
Smi
. الحقيقة هي أن الأعداد الصحيحة الصغيرة شائعة جدًا في برامج JS الحقيقية. عند العمل بقيم
Smi
، ليس من الضروري تخصيص ذاكرة للكيانات الفردية. استخدامها ، بالإضافة إلى ذلك ، يسمح لك بإجراء عمليات سريعة مع أعداد صحيحة.
مقارنة بين Smi و HeapNumber و MutableHeapNumber
دعنا نتحدث عن الشكل الداخلي لهذه الآليات. لنفترض أن لدينا الكائن التالي:
const o = { x: 42,
يتم تشفير القيمة 42 من خاصية الكائن
x
كـ
Smi
. هذا يعني أنه يمكن تخزينها داخل الكائن نفسه. لتخزين القيمة 4.2 ، من ناحية أخرى ، سوف تحتاج إلى إنشاء كيان منفصل. في الكائن ، سيكون هناك رابط لهذا الكيان.
تخزين القيم المختلفةلنفترض أننا ننفذ الجزء التالي من شفرة JavaScript:
ox += 10;
في هذه الحالة ، يمكن تحديث قيمة الخاصية
x
في موقع التخزين الخاص بها. الحقيقة هي أن القيمة الجديدة لـ
x
هي 52 ، وهذا الرقم يقع في نطاق
Smi
.
يتم تخزين القيمة الجديدة للخاصية x حيث تم تخزين القيمة السابقة.ومع ذلك ، فإن القيمة الجديدة لـ
y
، 5.2 ، لا تنسجم مع نطاق
Smi
، وتختلف ، بالإضافة إلى ذلك ، عن القيمة السابقة لـ y - 4.2. نتيجةً لذلك ، يتعين على V8 تخصيص ذاكرة لكيان
HeapNumber
الجديد والإشارة إليها من الكائن بالفعل.
كيان جديد HeapNumber لتخزين قيمة y الجديدةكيانات
HeapNumber
غير قابلة للتغيير. هذا يسمح لك بتنفيذ بعض التحسينات. افترض أننا نريد تعيين خاصية الكائن
x
قيمة الخاصية
y
:
ox = oy;
عند إجراء هذه العملية ، يمكننا ببساطة الرجوع إلى نفس كيان
HeapNumber
، وعدم تخصيص ذاكرة إضافية لتخزين نفس القيمة.
أحد عيوب حصانة كيانات HeapNuber هو أن التحديث المتكرر للحقول ذات القيم خارج نطاق
Smi
يكون بطيئًا. هذا موضح في المثال التالي:
عند معالجة السطر الأول ، يتم إنشاء مثيل
HeapNumber
، تكون القيمة الأولية له هي 0.1. في نص الدورة ، تتغير هذه القيمة إلى 1.1 ، 2.1 ، 3.1 ، 4.1 ، وأخيراً إلى 5.1. نتيجة لذلك ، في عملية تنفيذ هذا الرمز ،
HeapNumber
6 حالات من
HeapNumber
خمس منها لعمليات جمع القمامة بعد الانتهاء من الحلقة.
الكيانات كومةلتجنب هذه المشكلة ، يتوفر V8 على التحسين ، وهي آلية لتحديث الحقول العددية التي لا تتناسب قيمها مع نطاق
Smi
في نفس الأماكن التي تم تخزينها فيها بالفعل. إذا قام حقل رقمي بتخزين القيم التي لا
Smi
كيان
Smi
مناسبًا للتخزين ، فإن V8 ، في شكل كائن ، يقوم
MutableHeapNumber
هذا الحقل على أنه
Double
ويخصص ذاكرة لكيان
MutableHeapNumber
، الذي يخزن القيمة الحقيقية الممثلة بتنسيق Float64.
باستخدام MutableHeapNumber الكياناتنتيجة لذلك ، بعد تغير قيمة الحقل ، لم تعد V8 بحاجة إلى تخصيص ذاكرة لكيان
HeapNumber
الجديد. بدلاً من ذلك ، فقط اكتب القيمة الجديدة في كيان
MutableHeapNumber
موجود.
كتابة قيمة جديدة إلى MutableHeapNumberومع ذلك ، فإن هذا النهج له عيوبه. أي ، بما أن قيم
MutableHeapNumber
يمكن أن تتغير ، فمن المهم التأكد من أن النظام يعمل بطريقة تتصرف بها هذه القيم على النحو المنصوص عليه في مواصفات اللغة.
عيوب MutableHeapNumberعلى سبيل المثال ، إذا قمت بتعيين قيمة
ox
لبعض المتغيرات الأخرى
y
، فأنت بحاجة إلى التأكد من أن قيمة
y
لا تتغير مع تغيير لاحق في
ox
. سيكون ذلك انتهاكًا لمواصفات JavaScript! نتيجة لذلك ، عند الوصول إلى
ox
، يجب إعادة تحزيم الرقم إلى قيمة
HeapNumber
المعتادة قبل تعيين
y
.
في حالة أرقام الفاصلة العائمة ، تقوم V8 بعمليات التعبئة أعلاه باستخدام آلياتها الداخلية. ولكن في حالة الأعداد الصحيحة الصغيرة ، فإن استخدام
MutableHeapNumber
سيكون مضيعة للوقت لأن
Smi
طريقة أكثر فاعلية لتمثيل هذه الأرقام.
const object = { x: 1 };
من أجل تجنب الاستخدام غير الفعال لموارد النظام ، كل ما نحتاج إلى عمله للعمل مع الأعداد الصحيحة الصغيرة هو وضع علامة على الحقول المقابلة في أشكال الكائنات باسم
Smi
. نتيجة لذلك ، يمكن تحديث قيم هذه الحقول ، طالما أنها تتوافق مع نطاق
Smi
، مباشرة داخل الكائنات.
العمل مع الأعداد الصحيحة التي تقع قيمها في نطاق Smiأن تستمر ...
أعزائي القراء! هل واجهت مشكلات في أداء جافا سكريبت بسبب ميزات محرك JS؟
