جارٍ تحميل وحدات 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 (حسب التوقيت العالمي المتفَّق عليه)