جارٍ تحميل وحدات WebAssembly بكفاءة

عند العمل مع 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، يُنهي WebAssembly.compileStreaming()
 عملية التجميع تقريبًا بعد تنزيل البايتات الأخيرة.

لتفعيل هذا التحسين، استخدِم 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.

إنّ محتوى هذه الصفحة مرخّص بموجب ترخيص Creative Commons Attribution 4.0‏ ما لم يُنصّ على خلاف ذلك، ونماذج الرموز مرخّصة بموجب ترخيص Apache 2.0‏. للاطّلاع على التفاصيل، يُرجى مراجعة سياسات موقع Google Developers‏. إنّ Java هي علامة تجارية مسجَّلة لشركة Oracle و/أو شركائها التابعين.

تاريخ التعديل الأخير: 2018-04-12 (حسب التوقيت العالمي المتفَّق عليه)