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