تاريخ النشر: 30 كانون الثاني (يناير) 2025
تستفيد العديد من تطبيقات WebAssembly على الويب من ميزة "توفُّر عدّة مؤشرات تسلسل"، بالطريقة نفسها التي تستفيد بها التطبيقات المُنشأة على الأجهزة. تتيح سلاسل المهام المتعددة تنفيذ المزيد من المهام في الموازاة، ونقل المهام الثقيلة خارج سلسلة المهام الرئيسية لتجنُّب مشاكل وقت الاستجابة. حتى وقت قريب، كانت هناك بعض المشاكل الشائعة التي يمكن أن تحدث مع التطبيقات المتعدّدة المواضيع، والتي تتعلّق بعمليات التوزيع وعمليات الإدخال/الإخراج. لحسن الحظ، يمكن أن تساعدك ميزات Emscripten الحديثة في حلّ هذه المشاكل. يوضّح هذا الدليل كيف يمكن أن تؤدي هذه الميزات إلى تحسين السرعة بمقدار 10 مرات أو أكثر في بعض الحالات.
التحجيم
يعرض الرسم البياني التالي توسيعًا فعّالاً للمهام المتعدّدة المواضيع في ملف حمولة حسابية مطلقة (من المعيار الذي سنستخدمه في هذه المقالة):
يقيس هذا المقياس العمليات الحسابية البحتة، وهي العمليات التي يمكن لكل نواة وحدة معالجة مركزية تنفيذها بمفردها، لذا يتحسن الأداء مع زيادة عدد النوى. إنّ هذا الخط المتنازلي للأداء الأسرع هو بالضبط ما يمثّل التوسّع الجيد. ويُظهر ذلك أنّ منصة الويب يمكنها تنفيذ الرموز البرمجية الأصلية المتعدّدة المواضيع بشكلٍ جيد جدًا، على الرغم من استخدام web workers كأساس للتوازي، واستخدام Wasm بدلاً من الرموز البرمجية الأصلية الحقيقية، وتفاصيل أخرى قد تبدو أقلّ فعالية.
إدارة الحِزم: malloc
/free
malloc
وfree
هما وظيفتان مهمتان في المكتبة العادية في جميع برمجيات
الذاكرة الخطية (مثل C وC++ وRust وZig) التي يُعتمد عليها لإدارة كل
الذاكرة التي ليست ثابتة بالكامل أو في الحزمة. يستخدم Emscripten dlmalloc
بشكلٍ تلقائي، وهو تطبيق مكثّف ولكنّه فعّال (وهو يتيح أيضًا استخدام
emmalloc
، وهو
أكثر كثافة ولكنّه أبطأ في بعض الحالات). ومع ذلك، فإنّ الأداء المتعدد المهام لdlmalloc
محدود لأنّه يأخذ قفلًا على كل
malloc
/free
(لأنّ هناك عامل توزيع شامل واحد). لذلك،
قد تواجه تعارضًا وبطءًا إذا كان لديك العديد من عمليات التوزيع في العديد من
المهام في آنٍ واحد. إليك ما يحدث عند إجراء اختبار أداء malloc
-شديد الصعوبة:
لا يتحسن الأداء فقط مع زيادة عدد النوى، بل يزداد سوءًا
سوءًا، لأنّ كل سلسلة محادثات تنتهي بالانتظار لفترات طويلة للحصول على malloc
القفل. هذا هو أسوأ الحالات الممكنة، ولكن يمكن أن يحدث ذلك في أعباء العمل الفعلية
إذا كانت هناك عمليات تخصيص كافية.
mimalloc
تتوفّر إصدارات محسّنة لمعالجة المهام المتعددة من dlmalloc
، مثل ptmalloc3
، الذي
ينفّذ مثيلًا من الموزّع المنفصل لكل سلسلة مهام، ما يتجنّب الصراع.
تتوفّر العديد من أدوات التوزيع الأخرى التي تتضمّن تحسينات متعددة لخيوط المعالجة، مثل
jemalloc
وtcmalloc
. قرّر فريق Emscripten التركيز على مشروع
mimalloc
الحديث، وهو عبارة عن موزّع مُصمّم بشكل جيد من Microsoft يتمتع بقابلية نقل وأداء جيدَين للغاية. استخدِم هذه الميزة على النحو التالي:
emcc -sMALLOC=mimalloc
في ما يلي نتائج مقياس الأداء malloc
باستخدام mimalloc
:
ممتاز. وأصبح الأداء الآن يتزايد بكفاءة، ويصبح أسرع وأسرع مع كل قلب.
إذا اطّلعت بعناية على بيانات أداء النواة الواحدة في آخر
رسمَي بيان، ستلاحظ أنّ dlmalloc
استغرق 2660 ملي ثانية وmimalloc
1466 فقط، ما يمثّل تحسنًا في السرعة بمقدار مرة تقريبًا. يشير ذلك إلى أنّه حتى في التطبيقات التي تستخدم خيوطًا واحدة، قد تستفيد من تحسينات mimalloc
الأكثر تعقيدًا، مع العلم أنّ ذلك يتطلّب تكلفة في حجم الرمز واستخدام الذاكرة (لهذا السبب، يظل dlmalloc
هو الإعداد التلقائي).
الملفات ووحدات الإدخال والإخراج
تحتاج العديد من التطبيقات إلى استخدام الملفات لأسباب مختلفة. على سبيل المثال، لتحميل
المستويات في لعبة أو الخطوط في محرِّر صور. حتى عملية مثل printf
تستخدم نظام الملفات بشكل أساسي، لأنّها تُطبع من خلال كتابة البيانات في
stdout
.
في التطبيقات التي تستخدم سلسلة مهام واحدة، لا يشكّل ذلك عادةً مشكلة، وسيتجنّب Emscripten
تلقائيًا ربط دعم نظام الملفات بالكامل إذا كان كل ما تحتاجه هو
printf
. ومع ذلك، إذا كنت تستخدم الملفات، يكون الوصول إلى نظام الملفات المتعدّد المواضيع
معقدًا لأنّه يجب مزامنة الوصول إلى الملفات بين المواضيع. إنّ عملية تنفيذ نظام الملفات الأصلية في Emscripten، والتي تُعرف باسم "JS FS" لأنّه تم تنفيذها باستخدام JavaScript، استخدمت النموذج البسيط لتنفيذ نظام الملفات في الخيط الرئيسي فقط. عندما تريد سلسلة تعليمات أخرى الوصول إلى ملف، تُرسِل
طلبًا إلى السلسلة الرئيسية. وهذا يعني أنّ سلسلة التعليمات الأخرى تتوقف عند
طلب يمتد إلى سلسلة تعليمات أخرى، وتعالجه سلسلة التعليمات الرئيسية في النهاية.
يكون هذا النموذج البسيط مثاليًا إذا كان الخيط الرئيسي فقط هو الذي يصل إلى الملفات، وهو أسلوب شائع. ومع ذلك، إذا كانت سلاسل المهام الأخرى تُجري عمليات قراءة وكتابة، تحدث مشاكل. أولاً، تنتهي سلسلة التعليمات الرئيسية بتنفيذ مهام لسلاسل تعليمات أخرى، ما يؤدي إلى تأخُّر ملحوظ للمستخدم. بعد ذلك، تنتهي سلاسل التعليمات في الخلفية بالانتظار إلى أن تصبح سلسلت التعليمات الرئيسية متاحة لكي تؤدي العمل الذي تحتاجه، لذا يصبح الأداء أبطأ (أو، أسوأ من ذلك، قد تنتهي في حالة جمود إذا كانت سلسلة التعليمات الرئيسية في انتظار سلسلة التعليمات العاملة هذه).
WasmFS
لحلّ هذه المشكلة، يتضمّن Emscripten عملية تنفيذ جديدة لنظام الملفات، وهي WasmFS. تم كتابة WasmFS بلغة C++ وتمت تجميعه إلى Wasm، على عكس نظام الملفات الأصلي الذي كان بتنسيق JavaScript. يتيح WasmFS الوصول إلى نظام الملفات من عدة خيوط مع الحد الأدنى من النفقات العامة، وذلك من خلال تخزين الملفات في ذاكرة Wasm الخطية التي تتم مشاركتها بين جميع الخيوط. يمكن الآن لجميع سلاسل التعليمات تنفيذ عمليات الإدخال/الإخراج من الملفات بأداء متساوٍ، ويمكنها في كثير من الأحيان تجنُّب حظر بعضها البعض.
يُظهر مقياس أداء بسيط لنظام الملفات الميزة الكبيرة لواسم WasmFS مقارنةً بنظام الملفات القديم لـ JS.
يقارن هذا الإجراء بين تشغيل رمز نظام الملفات مباشرةً على سلسلة التعليمات الرئيسية وتشغيله على pthread واحد. في نظام JS FS القديم، يجب توجيه كل عملية في نظام الملفات إلى سلسلة التعليمات الرئيسية، ما يجعلها أبطأ بدرجة كبيرة على pthread. ويرجع ذلك إلى أنّ نظام JS FS لا يقتصر على قراءة/كتابة بعض البايتات، بل يُجري عمليات تواصل بين سلاسل المهام، ويشمل ذلك عمليات قفل وانتظار وقائمة انتظار. في مقابل ذلك، يمكن لواسم WasmFS الوصول إلى الملفات من أي سلسلة محادثات بشكلٍ متساوٍ، وبالتالي يُظهر الرسم البياني أنّه لا يوجد فرق عملي بين السلسلة الرئيسية وسلسلة pthread. ونتيجةً لذلك، يكون WasmFS أسرع بـ 32 مرة من JS FS عند استخدام ملف pthread.
يُرجى العلم أنّ هناك أيضًا فرقًا في الخيط الرئيسي حيث يكون WasmFS أسرع بمرتين . ويرجع ذلك إلى أنّ نظام JS FS يستدعي JavaScript لكل عملية في نظام الملفات، وهو ما يتجنّبه WasmFS. لا يستخدم WasmFS JavaScript إلا عند الضرورة (على سبيل المثال، لاستخدام Web API)، ما يترك معظم ملفات WasmFS في Wasm. بالإضافة إلى ذلك، حتى في حال كان JavaScript مطلوبًا، يمكن أن يستخدم WasmFS سلسلة محادثات مساعدة بدلاً من السلسلة الرئيسية لتجنُّب وقت الاستجابة الذي يلاحظه المستخدم. ولهذا السبب، قد تلاحظ تحسينات في السرعة عند استخدام WasmFS حتى إذا لم يكن تطبيقك متعدد المواضيع (أو إذا كان متعدد المواضيع ولكنه يستخدم الملفات في السلسلة الأساسية فقط ).
استخدِم WasmFS على النحو التالي:
emcc -sWASMFS
يتم استخدام WasmFS في مرحلة الإنتاج ويُعدّ نظامًا ثابتًا، ولكنّه لا يتيح بعد جميع ميزات نظام JS FS القديم. من ناحية أخرى، يتضمّن الإصدار الجديد بعض الميزات الجديدة المهمة، مثل إتاحة استخدام نظام الملفات الخاص الأصلي (OPFS، وهو نظام يُنصح به بشدة لمساحة التخزين الدائمة). ينصح فريق Emscripten باستخدام WasmFS ما لم تكن بحاجة إلى ميزة لم يتم نقلها بعد.
الخاتمة
إذا كان لديك تطبيق متعدّد المواضيع يُجري الكثير من عمليات التوزيع أو يستخدم
الملفات، يمكنك الاستفادة كثيرًا من استخدام WasmFS و/أو mimalloc
. يمكن تجربتهما بسهولة في مشروع Emscripten من خلال إعادة الترجمة باستخدام الخيارات описанة في هذا المنشور.
يمكنك أيضًا تجربة هذه الميزات إذا كنت لا تستخدم مؤشرات الترابط: كما ذكرنا سابقًا، تقدّم عمليات التنفيذ الأكثر حداثة تحسينات ملحوظة حتى على وحدة معالجة مركزية واحدة في بعض الحالات.