TypeScript में WebAssembly

यह लेख TypeScript में WebAssembly की व्याख्या करता है।

हम TypeScript और WebAssembly को एकीकृत करने के व्यावहारिक और आसान तरीकों को बताएंगे।

YouTube Video

TypeScript में WebAssembly

WebAssembly (Wasm) एक बाइनरी फॉर्मेट रनटाइम है जो ब्राउज़र में लगभग नेटिव स्पीड पर चलता है। TypeScript से Wasm को कॉल करके, आप कंप्यूट-इंटेंसिव प्रक्रियाओं और C/C++ या Rust में लिखी मौजूदा नेटिव लाइब्रेरीज़ का कुशलता से उपयोग कर सकते हैं।

मूल निष्पादन प्रवाह

यहां, हम Wasm के मूल निष्पादन प्रवाह की व्याख्या करेंगे। TypeScript (या ब्राउज़र) .wasm फ़ाइल प्राप्त करता है, उसे इंस्टैंसिएट करता है, और एक्सपोर्ट की गई फ़ंक्शन्स को कॉल करता है।

    1. AssemblyScript, Rust, C++ का उपयोग करके .wasm बाइनरी बनाएं, या कोई मौजूदा बाइनरी तैयार करें।
    1. TypeScript (या ब्राउज़र) में .wasm फ़ाइल प्राप्त करें और उसे सिंक्रोनस या असिंक्रोनस रूप से इंस्टैंसिएट करें।
    1. एक्सपोर्टेड फंक्शन्स को कॉल करें और आवश्यकता पड़ने पर WebAssembly.Memory का उपयोग करके मेमोरी साझा करें।

WebAssembly.instantiateStreaming

अगला, हम Wasm फ़ाइल लोड करने और एक्सपोर्टेड फ़ंक्शन को कॉल करने का एक मूल उदाहरण दिखाएंगे। ब्राउज़र को instantiateStreaming का समर्थन करना चाहिए।

निम्नलिखित कोड simple.wasm को सर्वर से प्राप्त करने और add फ़ंक्शन को कॉल करने का एक उदाहरण है।

 1// TypeScript: load-and-call.ts
 2// Fetch and instantiate a wasm module and call its exported `add` function.
 3async function run() {
 4  const response = await fetch('http://localhost:3000/simple.wasm');
 5  // Use instantiateStreaming when available for efficiency.
 6  const { instance } = await WebAssembly.instantiateStreaming(response, {});
 7  // @ts-ignore
 8  const result = instance.exports.add(2, 3);
 9  console.log('2 + 3 =', result);
10}
11run().catch(console.error);
  • Wasm के अंदर की फंक्शन्स instance.exports में संग्रहीत होती हैं।
  • चूंकि TypeScript को टाइप जानकारी नहीं मिलती, इसलिए आपको @ts-ignore का उपयोग करना होगा या अपनी खुद की टाइप डेफिनिशन बनानी होगी।

AssemblyScript का उपयोग करके वर्कफ़्लो

AssemblyScript आपको TypeScript जैसी सिंटैक्स में Wasm लिखने की सुविधा देता है, जिससे यह TypeScript डेवलपर्स के लिए आसान विकल्प बन जाता है। यहाँ, हम एक साधारण फ़ंक्शन तैयार करते हैं, उसे .wasm और .d.ts में बनाते हैं, और फिर उसे TypeScript से कॉल करते हैं।

1// assembly/index.ts (AssemblyScript)
2// npm install --save-dev assemblyscript
3
4// Export a simple function to add two integers.
5export function add(a: i32, b: i32): i32 {
6  return a + b;
7}
  • asc (यानि AssemblyScript कंपाइलर) का उपयोग करके, आप एक .wasm फ़ाइल और आवश्यकता होने पर एक टाइप डिफिनिशन .d.ts फ़ाइल बना सकते हैं। इसे लोकली ट्राई करने के लिए, assemblyscript को npm से इंस्टॉल करें और इसे बनाएं।
1# build commands
2# npm install --save-dev assemblyscript
3npx asc assembly/index.ts   -o build/simple.wasm   -t build/simple.wat   --bindings esm   --exportTable   --sourceMap
4
5# optionally generate d.ts with --exportRuntime or use as-bind / loader tools

यहां TypeScript साइड से फेच और कॉल करने का एक उदाहरण है।

1// ts client that loads AssemblyScript-generated wasm
2async function runAssemblyScript() {
3  const res = await fetch('http://localhost:3000/build/simple.wasm');
4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
5  // @ts-ignore
6  console.log('AssemblyScript add:', instance.exports.add(10, 7));
7}
8runAssemblyScript().catch(console.error);
  • AssemblyScript में मेमोरी मॉडल और स्ट्रिंग्स को सावधानी से संभालना पड़ता है, लेकिन यह सामान्य संख्यात्मक गणनाओं के लिए बहुत आसान है।

Rust + wasm-bindgen (एक शक्तिशाली और सामान्यतः उपयोग किया जाने वाला विकल्प)

यह अनुभाग समझाता है कि Rust में Wasm कैसे लिखा जाता है और इसे 'wasm-bindgen' का उपयोग करके JavaScript या TypeScript के साथ कैसे जोड़ा जाता है। यहां, हम एक सिंपल फाइबोनाच्ची फंक्शन का उदाहरण देकर दिखाएंगे कि कैसे जेनरेटेड मॉड्यूल को ES मॉड्यूल के रूप में इंपोर्ट किया जा सकता है।

wasm-bindgen का उपयोग करके Rust साइड से फंक्शन्स को एक्सपोर्ट करें।

 1// src/lib.rs (Rust)
 2// install wasm-pack from https://drager.github.io/wasm-pack/installer/
 3use wasm_bindgen::prelude::*;
 4
 5// Export a function to JavaScript using wasm-bindgen.
 6#[wasm_bindgen]
 7pub fn fib(n: u32) -> u32 {
 8    if n <= 1 { return n; }
 9    let mut a = 0;
10    let mut b = 1;
11    for _ in 2..=n {
12        let tmp = a + b;
13        a = b;
14        b = tmp;
15    }
16    b
17}
  • जब आप wasm-pack या wasm-bindgen CLI से बिल्ड करते हैं, तो TypeScript और JS रैपर के लिए टाइप डेफिनिशन जेनरेट होती हैं, जिससे आप उन्हें सीधे ESM के रूप में इंपोर्ट कर सकते हैं।
1# build with wasm-pack
2# install wasm-pack from https://drager.github.io/wasm-pack/installer/
3wasm-pack build --target nodejs --out-dir pkg

TypeScript साइड पर, pkg से ES मॉड्यूल को इंपोर्ट करें और उपयोग करें।

 1// Node.js: import WASM module built with --target web
 2// import init, { fib } from '../pkg/my_wasm_module.js';
 3// Node.js: import WASM module built with --target nodejs
 4import wasm from '../pkg/my_wasm_module.js';
 5
 6async function run() {
 7  //await init();   // --target web
 8  console.log('fib(10)=', wasm.fib(10));
 9}
10
11run().catch(console.error);
  • wasm-pack जावास्क्रिप्ट रैपर और .d.ts टाइप डेफिनिशन जेनरेट करता है, जिससे TypeScript से उपयोग करना आसान हो जाता है। कृपया ध्यान दें कि जब आप wasm-pack कमांड के --target विकल्प के लिए web निर्दिष्ट करते हैं, तो असिंक्रोनस इनिशियलाइज़ेशन आवश्यक होता है।

मेमोरी साझा करने का वास्तविक उदाहरण: एरे पास और प्रोसेस करना (लो लेवल)

जब Wasm के साथ बड़ी मात्रा में डेटा का आदान-प्रदान होता है, तो कुशल डेटा एक्सचेंज के लिए ArrayBuffer साझा करना महत्वपूर्ण होता है। यहां हम AssemblyScript का उपयोग कर एक उदाहरण दिखा रहे हैं, लेकिन यही सिद्धांत Rust के wasm-bindgen के साथ भी लागू होता है।

AssemblyScript साइड पर मेमोरी में लिखने के लिए एक एक्सपोर्टेड फंक्शन तैयार करें। उदाहरण के लिए, एरे के हर एलिमेंट को स्क्वायर करने वाला फंक्शन ऐसा दिखेगा।

 1// assembly/array_ops.ts (AssemblyScript)
 2// Square values in place in the wasm linear memory starting at `ptr` for `len` elements.
 3export function square_in_place(ptr: usize, len: i32): void {
 4  // Treat memory as a pointer to 32-bit integers.
 5  for (let i = 0; i < len; i++) {
 6    let offset = ptr + (i << 2); // i * 4 bytes
 7    let value = load<i32>(offset);
 8    store<i32>(offset, value * value);
 9  }
10}

AssemblyScript द्वारा उपयोग की जाने वाली मेमोरी सेटिंग्स निर्दिष्ट करने के लिए, निम्नलिखित asconfig.json तैयार करें।

1{
2  "options": {
3    "memoryBase": 0,
4    "importMemory": false,
5    "initialMemory": 1,
6    "maximumMemory": 10
7  }
8}
1 npx asc assembly/array_ops.ts   -o build/array_ops.wasm   -t build/array_ops.wat   --bindings esm   --exportTable   --sourceMap
  • इस फंक्शन को कॉल करने के लिए, आपको ArrayBuffer को Wasm मेमोरी स्पेस में कॉपी करना होगा और पॉइंटर पास करना होगा।

नीचे TypeScript में WebAssembly.Memory का उपयोग करते हुए डेटा कॉपी करने और फंक्शन कॉल करने का एक उदाहरण है।

 1// TypeScript: use memory to pass array to wasm
 2async function runArrayOps() {
 3  const res = await fetch('http://localhost:3000/build/array_ops.wasm');
 4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
 5  // @ts-ignore
 6  const memory: WebAssembly.Memory = instance.exports.memory;
 7  // Create a view into wasm memory.
 8  const i32View = new Int32Array(memory.buffer);
 9
10  // Example data
11  const input = new Int32Array([1, 2, 3, 4]);
12  // Choose an offset (in i32 elements) to copy data to (simple example: at index 0).
13  const offset = 0;
14  i32View.set(input, offset);
15
16  // Call wasm function: ptr in bytes, len in elements
17  // @ts-ignore
18  instance.exports.square_in_place(offset * 4, input.length);
19
20  // Read back result
21  const result = i32View.slice(offset, offset + input.length);
22  console.log('squared:', result);
23}
24runArrayOps().catch(console.error);
  • memory.buffer साझा लिनियर मेमोरी है; कॉपी को कम से कम करना प्रोसेसिंग स्पीड बढ़ाने में मदद करता है। ध्यान दें कि पॉइंटर बाइट्स में पोजीशन को दर्शाते हैं, जबकि TypedArrays एलिमेंट काउंट से मैनेज होती हैं, इसलिए इन अंतर को न मिलाएं।

टाइप-सुरक्षित हैंडलिंग: TypeScript टाइप डेफिनिशन तैयार करें

Wasm एक्सपोर्ट्स जावास्क्रिप्ट ऑब्जेक्ट्स हैं, इसलिए TypeScript साइड पर टाइप डेफिनिशन देना डेवेलपमेंट को आसान बना देगा। यहां टाइप डेफिनिशन फ़ाइल का एक साधारण उदाहरण है।

नीचे आपको न्यूनतम टाइप डेफिनिशन दिख रही है, जिसे आप simple.d.ts के रूप में मैन्युअली बना सकते हैं।

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • इसे अपने tsconfig.json की typeRoots में रखने या declare module का उपयोग करने से टाइप चेकिंग सक्षम हो जाएगी। wasm-pack आसानी से .d.ts फाइलें अपने आप जेनरेट करता है, इसलिए इन्हें उपयोग करना लाभदायक है।

रनटाइम पर इनिशियलाइजेशन पैटर्न: सिंक्रोनस बनाम असिंक्रोनस

चूंकि Wasm मॉड्यूल्स को I/O (फेचिंग) और कंपाइलेशन की जरूरत होती है, इसलिए आमतौर पर असिंक्रोनस इनिशियलाइज़ेशन होता है। हालांकि, एक पैटर्न यह भी है जिसमें आप पहले से WebAssembly.Module को कैश करते हैं और फिर सिंक्रोनस रूप से इंस्टैंसिएट करते हैं।

नीचे असिंक्रोनस रूप से WebAssembly इनिशियलाइज़ करने का बेसिक कोड स्ट्रक्चर दिया गया है। वास्तविक प्रोजेक्ट्स में यही पैटर्न अनुशंसित है।

1// async init pattern
2async function initWasm(url: string) {
3  const res = await fetch(url);
4  const { instance, module } = await WebAssembly.instantiateStreaming(res, {});
5  return instance;
6}
  • असिंक्रोनस इनिशियलाइज़ेशन से आप लचीले ढंग से एरर हैंडलिंग और लेज़ी लोडिंग को सम्मिलित कर सकते हैं, जिससे यह व्यावहारिक डेवेलपमेंट में सबसे सुविधाजनक है। इसके अलावा, wasm-pack द्वारा जेनरेटेड कोड में इनिशियलाइजेशन के लिए init() API शामिल होता है, इसलिए इस फ्लो के अभ्यासी होने से आपका काम सरल हो जाएगा।

व्यावहारिक प्रदर्शन विचार

यहां कुछ बातें दी गई हैं जिन्हें ध्यान में रखकर आप प्रदर्शन में महत्वपूर्ण सुधार ला सकते हैं। TypeScript और WebAssembly को जोड़ते समय इन अनुकूलन टिप्स को ध्यान में रखें।

  • जब फंक्शन कॉल्स बहुत बार होती हैं, तब जावास्क्रिप्ट और Wasm के बीच कॉल्स की ओवरहेड एक बड़ी बाधा बन सकती है। हम सुझाव देते हैं कि डेटा को बैच करें और उसे एक साथ प्रोसेस करें, जितना संभव हो।
  • मेमोरी एलोकेशन और कॉपी करने से प्रोसेसिंग लोड बढ़ता है। इन ऑपरेशनों को न्यूनतम करने के लिए साझा बफर्स और पॉइंटर्स का उपयोग करें।
  • फ्लोटिंग-प्वाइंट नंबर्स को संभालते समय सावधानी बरतें। TypeScript में, वे number टाइप के बन जाते हैं, लेकिन Wasm साइड की टाइप्स मिलाकर आप उन्हें सही ढंग से हैंडल कर सकते हैं।

सारांश

TypeScript और WebAssembly को मिलाकर आप ब्राउज़र में लगभग नेटिव प्रदर्शन प्राप्त कर सकते हैं। यह विशेष रूप से तब प्रभावी है जब कार्य बहुत अधिक गणना-प्रधान हों या जब आप मौजूदा नेटिव संसाधनों का उपयोग करना चाहते हों। वेब एप्लिकेशन के प्रदर्शन को बेहतर बनाना हो तो यह संयोजन एक बहुत शक्तिशाली विकल्प है।

आप हमारे YouTube चैनल पर Visual Studio Code का उपयोग करके ऊपर दिए गए लेख के साथ आगे बढ़ सकते हैं। कृपया YouTube चैनल को भी देखें।

YouTube Video