WebAssembly मॉड्यूल बेहतर तरीके से लोड किए जा रहे हैं

WebAssembly का इस्तेमाल करते समय, अक्सर किसी मॉड्यूल को डाउनलोड करके उसे कंपाइल करना, इंस्टैंशिएट करना, और फिर JavaScript में एक्सपोर्ट किए गए कॉन्टेंट का इस्तेमाल करना होता है. इस पोस्ट में, बेहतर परफ़ॉर्मेंस के लिए हमारे सुझाए गए तरीके के बारे में बताया गया है.

WebAssembly का इस्तेमाल करते समय, अक्सर किसी मॉड्यूल को डाउनलोड करना, उसे कंपाइल करना, उसका उदाहरण देना, और फिर JavaScript में एक्सपोर्ट किए गए कॉन्टेंट का इस्तेमाल करना होता है. यह पोस्ट एक सामान्य, लेकिन सबसे अच्छे कोड स्निपेट से शुरुआत करती है, जो ठीक यही काम करता है. इसमें कई संभावित ऑप्टिमाइज़ेशन के बारे में चर्चा की गई है. साथ ही, आखिर में यह JavaScript से WebAssembly चलाने का सबसे आसान और असरदार तरीका दिखाता है.

यह कोड स्निपेट, डाउनलोड-कंपाइल-इंस्टैंशिएट डांस को पूरा करता है. हालांकि, यह ऐसा सबसे सही तरीके से नहीं करता:

इसका इस्तेमाल न करें!

(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) का इस्तेमाल कैसे करते हैं. यह एक सिंक्रोनस एपीआई है. इसका मतलब है कि यह मुख्य थ्रेड को तब तक ब्लॉक करता है, जब तक वह पूरा नहीं हो जाता. इसके इस्तेमाल को रोकने के लिए, Chrome WebAssembly.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) के लिए यह शर्त लागू नहीं होती. Chrome में, new WebAssembly.Instance(module) के लिए भी बफ़र साइज़ की वही सीमा 4 केबी है. एक जैसी सुविधाएं देने और मुख्य थ्रेड को खाली रखने के लिए, हम असाइनोक्रोनस 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 ऑप्टिमाइज़ेशन पर वापस आते हैं जिसके बारे में मैंने पहले बताया था. स्ट्रीमिंग कंपाइलेशन की मदद से, ब्राउज़र पहले से ही WebAssembly मॉड्यूल को कंपाइल करना शुरू कर सकता है. हालांकि, मॉड्यूल बाइट अब भी डाउनलोड हो रहे हैं. डाउनलोड और कंपाइलेशन एक साथ होने की वजह से, यह प्रोसेस तेज़ी से होती है. खास तौर पर, बड़े पेलोड के लिए.

जब डाउनलोड में लगने वाला समय, WebAssembly मॉड्यूल के कंपाइलेशन समय से ज़्यादा होता है, तो आखिरी बाइट डाउनलोड होने के तुरंत बाद WebAssembly.compileStreaming()
का कंपाइलेशन पूरा हो जाता है.

इस ऑप्टिमाइज़ेशन को चालू करने के लिए, WebAssembly.compile के बजाय WebAssembly.compileStreaming का इस्तेमाल करें. इस बदलाव की मदद से, इंटरमीडिएट कलेक्शन बफ़र से भी छुटकारा मिल जाता है. ऐसा इसलिए, क्योंकि अब हम await fetch(url) से मिले Response इंस्टेंस को सीधे पास कर सकते हैं.

(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 का आनंद लें!