عند العمل باستخدام WebAssembly، تحتاج غالبًا إلى تنزيل وحدة معيّنة وتجميعها وإنشاء مثيل لها ثم استخدام كل ما يتم تصديره في JavaScript. توضّح هذه المشاركة المنهج الذي ننصح به لتحقيق الكفاءة المثلى.
عند العمل مع WebAssembly، غالبًا ما تريد تنزيل وحدة وتجميعها وإنشاء مثيل لها، ثم استخدام ما يتم تصديره في JavaScript. تبدأ هذه المشاركة بقطعة رمز برمجي شائعة ولكنها غير مثالية تؤدي ذلك بالضبط، وتناقش العديد من التحسينات المحتملة، وتوضّح في النهاية أبسط وأكفأ طريقة لتشغيل WebAssembly من JavaScript.
ينفِّذ مقتطف الرمز البرمجي هذا عملية التنزيل والتجميع والإنشاء بالكامل، ولكن بطريقة غير مثالية:
لا تستخدم هذه المعلومات.
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
لاحظ كيف نستخدم new WebAssembly.Module(buffer)
لتحويل وحدة تخزين مؤقت للردّ إلى وحدة. هذه واجهة برمجة تطبيقات
غير متزامنة، ما يعني أنّها تحظر سلسلة التعليمات الرئيسية إلى أن تكتمل. ولتجنّب استخدامه، يُوقف ChromeWebAssembly.Module
للمخازن المؤقتة التي يزيد حجمها عن 4 كيلوبايت. للتغلب على حدّ الحجم، يمكننا
استخدام await WebAssembly.compile(buffer)
بدلاً من ذلك:
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
لا يزال await WebAssembly.compile(buffer)
لا يزال هو النهج الأمثل، ولكننا سنصل إليه خلال
ثانية.
أصبحت كل عملية في المقتطف المعدَّل تقريبًا غير متزامنة الآن، بما أنّ استخدام await
واضح. الاستثناء الوحيد هو new WebAssembly.Instance(module)
، الذي يفرض قيودًا على حجم ملف التخزين المؤقت بالحجم نفسه، أي 4 كيلوبايت، في Chrome. من أجل الاتساق وإبقاء السلسلة الرئيسية
خالية، يمكننا استخدام السلسلة غير المتزامنة
WebAssembly.instantiate(module)
.
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
لنعود إلى تحسين compile
الذي أشرت إليه سابقًا. باستخدام ميزة التجميع أثناء البث، يمكن للمتصفّح أن يبدأ compiling compiling تجميع وحدة WebAssembly أثناء تنزيل وحدات البايت الخاصة بالوحدة. وبما أنّ عملية التنزيل
والتجميع تتم بشكل موازٍ، يكون ذلك أسرع، لا سيما بالنسبة إلى الحمولات الكبيرة.
لتفعيل هذا التحسين، استخدِم WebAssembly.compileStreaming
بدلاً من WebAssembly.compile
.
يسمح لنا هذا التغيير أيضًا بالتخلص من وحدة تخزين مؤقتة للصفيف الوسيط، لأنّه يمكننا الآن تمرير مثيل
Response
الذي تم إرجاعه بواسطة await fetch(url)
مباشرةً.
(async () => {
const response = await fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(response);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
تقبل واجهة برمجة التطبيقات WebAssembly.compileStreaming
أيضًا وعدًا يؤدي إلى مثيل Response
. إذا لم تكن بحاجة إلى استخدام response
في أي مكان آخر في الرمز، يمكنك تمرير الوعد
الذي قدّمه fetch
مباشرةً، بدون await
الحصول على النتيجة بشكل صريح:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(fetchPromise);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
إذا لم تكن بحاجة إلى نتيجة fetch
في مكان آخر، يمكنك أيضًا تمريرها مباشرةً:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
أجد شخصيًا أنّه من الأسهل قراءة المحتوى عندما يكون في سطر منفصل.
هل لاحظت كيف نجمع الاستجابة في وحدة، ثم ننشئ مثيلًا لها على الفور؟ وكما اتضح، بإمكان WebAssembly.instantiate
التجميع وإنشاء مثيله في خطوة واحدة، حيث تنفّذ واجهة برمجة التطبيقات WebAssembly.instantiateStreaming
ذلك بطريقة مباشرة:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
// To create a new instance later:
const otherInstance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
إذا كنت بحاجة إلى مثيل واحد فقط، ما مِن داعٍ للاحتفاظ بعنصر module
،
ويمكنك تبسيط الرمز البرمجي بشكل أكبر:
// This is our recommended way of loading WebAssembly.
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
ويمكن تلخيص التحسينات التي طبّقناها على النحو التالي:
- استخدام واجهات برمجة التطبيقات غير المتزامنة لتجنُّب حظر سلسلة التعليمات الرئيسية
- استخدِم واجهات برمجة التطبيقات للبث لتجميع وحدات WebAssembly وإنشاء مثيل لها بسرعة أكبر
- عدم كتابة رموز لا تحتاج إليها
نتمنى لك الاستمتاع باستخدام WebAssembly.