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