अपने ऐप्लिकेशन के JavaScript में किसी हॉट पाथ को WebAssembly से बदलना

यह लगातार तेज़ है,

मैंने अपने पिछले लेख में बताया था कि WebAssembly की मदद से, C/C++ के लाइब्रेरी नेटवर्क को वेब पर कैसे उपलब्ध कराया जा सकता है. एक ऐसा ऐप्लिकेशन है जिसमें C/C++ लाइब्रेरी का बड़े पैमाने पर इस्तेमाल किया जाता है. squoosh हमारा वेब ऐप्लिकेशन है. इसकी मदद से, इमेज को कई तरह के कोडेक से कंप्रेस किया जा सकता है. ये कोडेक, C++ से WebAssembly में कंपाइल किए गए हैं.

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

मेरे अनुभव के हिसाब से, वेब पर परफ़ॉर्मेंस से जुड़ी ज़्यादातर समस्याएं ज़बरदस्ती लेआउट और ज़्यादा पेंट की वजह से होती हैं, लेकिन कभी-कभी ऐप्लिकेशन को कंप्यूटेशन के हिसाब से महंगा काम करना पड़ता है, जिसमें बहुत ज़्यादा समय लगता है. WebAssembly यहां सहायता कर सकता है.

द हॉट पाथ

स्क्वाश में हमने एक JavaScript फ़ंक्शन लिखा है जो इमेज बफ़र को 90 डिग्री के गुणज में घुमाता है. इसके लिए, OffscreenCanvas का इस्तेमाल करना सही रहेगा. हालांकि, यह उन ब्राउज़र पर काम नहीं करता जिन्हें हम टारगेट कर रहे थे. साथ ही, यह Chrome में थोड़ी गड़बड़ी वाले ब्राउज़र पर काम नहीं करता.

यह फ़ंक्शन किसी इनपुट इमेज के हर पिक्सल पर इटरेट करता है और उसे रोटेशन पाने के लिए आउटपुट इमेज में किसी अलग जगह पर कॉपी करता है. 4094 पिक्सल x 4096 पिक्सल (16 मेगापिक्सल) की इमेज के लिए, इसे इनर कोड ब्लॉक में 1.6 करोड़ से ज़्यादा बार बदलाव करने की ज़रूरत होगी. इसे हम "हॉट पाथ" कहते हैं. बार-बार कई बार करने के बावजूद, हमने तीन में से दो ब्राउज़र पर टेस्ट किया और वह काम 2 सेकंड या उससे कम समय में पूरा कर लेते हैं. इस तरह के इंटरैक्शन के लिए स्वीकार की जाने वाली अवधि.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

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

वहीं दूसरी ओर, WebAssembly पूरी तरह से प्रोसेस करने की तेज़ स्पीड पर आधारित है. इसलिए, अगर हमें इस तरह के कोड के लिए सभी ब्राउज़र में तेज़ और अनुमानित परफ़ॉर्मेंस चाहिए, तो WebAssembly से मदद मिल सकती है.

उम्मीद के मुताबिक परफ़ॉर्मेंस के लिए WebAssembly

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

WebAssembly के लिए लिखना

पहले हम C/C++ लाइब्रेरी लेते थे और उन्हें WebAssembly में कंपाइल करते थे, ताकि वेब पर उनकी सुविधाओं का इस्तेमाल किया जा सके. हमने लाइब्रेरी के कोड की पहचान नहीं की थी, हमने बस कुछ C/C++ कोड लिखे थे, ताकि ब्राउज़र और लाइब्रेरी को आपस में जोड़ा जा सके. इस बार हमारा मकसद अलग है: हम WebAssembly को ध्यान में रखते हुए, शुरुआत से कुछ लिखना चाहते हैं, ताकि हम WebAssembly के फ़ायदों का इस्तेमाल कर सकें.

WebAssembly आर्किटेक्चर

WebAssembly के लिए लिखते समय, WebAssembly) के बारे में थोड़ा और जान लेना बेहतर होगा.

WebAssembly.org को कोट करने के लिए:

जब C या Rust कोड का कोई हिस्सा WebAssembly में कंपाइल किया जाता है, तो आपको एक .wasm फ़ाइल मिलती है, जिसमें मॉड्यूल की जानकारी होती है. इस जानकारी में उन "इंपोर्ट" की सूची दी गई है जिनकी ज़रूरत मॉड्यूल को अपने एनवायरमेंट से होती है. एक्सपोर्ट की एक सूची होती है जिसे यह मॉड्यूल, होस्ट को उपलब्ध कराता है. जैसे, फ़ंक्शन, कॉन्सटेंट, मेमोरी के कई हिस्से. इसमें फ़ंक्शन के लिए असल बाइनरी निर्देश भी शामिल होते हैं.

जब तक मैं इसे नहीं देखता तब तक मुझे कुछ समझ नहीं आया: WebAssembly को एक "स्टैक-आधारित वर्चुअल मशीन" बनाने वाला स्टैक, उस मेमोरी के हिस्से में सेव नहीं किया जाता जिसे WebAssembly मॉड्यूल इस्तेमाल करते हैं. स्टैक को पूरी तरह से वीएम की मदद से ऐक्सेस किया जा सकता है. साथ ही, वेब डेवलपर इसे ऐक्सेस नहीं कर सकते (DevTools को छोड़कर). इसलिए, ऐसे WebAssembly मॉड्यूल लिखे जा सकते हैं जिन्हें किसी भी अतिरिक्त मेमोरी की ज़रूरत नहीं होती और सिर्फ़ वीएम-इंटरनल स्टैक का इस्तेमाल किया जाता है.

हमारे मामले में, हमें अपनी इमेज के पिक्सल का आर्बिट्रेरी ऐक्सेस देने और उस इमेज का घूमा हुआ वर्शन जनरेट करने के लिए, कुछ और मेमोरी का इस्तेमाल करना होगा. WebAssembly.Memory ऐसा ही है.

मेमोरी मैनेज करना

आम तौर पर, ज़्यादा मेमोरी का इस्तेमाल करने के बाद, आपको उस मेमोरी को मैनेज करने की ज़रूरत महसूस होगी. मेमोरी के किन हिस्सों का इस्तेमाल किया जा रहा है? कौनसी सेवाएं बिना किसी शुल्क के उपलब्ध हैं? उदाहरण के लिए, C में, आपके पास malloc(n) फ़ंक्शन है जो लगातार n बाइट का मेमोरी स्पेस ढूंढता है. इस तरह के फ़ंक्शन को "ऐलोकेटर" भी कहा जाता है. हालांकि, इस्तेमाल हो रहे आलोकेटर को लागू करने की प्रोसेस, आपके WebAssembly मॉड्यूल में शामिल होनी चाहिए. इससे आपकी फ़ाइल का साइज़ बढ़ जाएगा. इन मेमोरी मैनेजमेंट फ़ंक्शन का साइज़ और परफ़ॉर्मेंस, इस्तेमाल किए गए एल्गोरिदम के आधार पर काफ़ी अलग-अलग हो सकती है. यही वजह है कि कई भाषाएं चुनने के लिए कई तरह के इंप्लीमेंटेशन की सुविधा देती हैं ("dmalloc", "emmalloc", "wee_alloc" वगैरह).

हमारे मामले में, WebAssembly मॉड्यूल चलाने से पहले, हमें इनपुट इमेज के डाइमेंशन (इसलिए, आउटपुट इमेज के डाइमेंशन) के बारे में पता होता है. यहां हमने एक अवसर देखा: परंपरागत तौर पर, हम इनपुट इमेज के आरजीबीए बफ़र को WebAssembly फ़ंक्शन में, पैरामीटर के तौर पर पास करते हैं. साथ ही, घुमाई गई इमेज को रिटर्न वैल्यू के तौर पर दिखाते हैं. इस रिटर्न वैल्यू को जनरेट करने के लिए, हमें ऐलोकेटर का इस्तेमाल करना होगा. हालांकि, हमें पता है कि कुल मेमोरी की ज़रूरत है या नहीं (इनपुट इमेज के साइज़ का दोगुना, इनपुट के लिए एक बार और आउटपुट के लिए एक बार), इसलिए हम JavaScript का इस्तेमाल करके, इनपुट इमेज को WebAssembly मेमोरी में रख सकते हैं. दूसरी, घुमाई गई इमेज जनरेट करने के लिए WebAssembly मॉड्यूल चलाएं और फिर नतीजे को वापस पढ़ने के लिए JavaScript का इस्तेमाल करें. हम मेमोरी मैनेजमेंट का इस्तेमाल किए बिना भी इससे बच सकते हैं!

पसंद के लिए बनाया गया

अगर आपने उस ओरिजनल JavaScript फ़ंक्शन को देखा है जिसे हम WebAssembly-fy की मदद से देखना चाहते हैं, तो आपको पता चलेगा कि यह पूरी तरह से कंप्यूटेशनल कोड है. इसमें JavaScript के लिए कोई खास एपीआई नहीं है. इसलिए, इस कोड को किसी भी भाषा में पोर्ट करने के लिए, आपका तरीका आसान होना चाहिए. हमने तीन अलग-अलग भाषाओं का मूल्यांकन किया है जो WebAssembly को कंपाइल करती हैं: C/C++, Rust, और AssemblyScript. हर भाषा के लिए हमें एक ही सवाल का जवाब चाहिए: हम मेमोरी मैनेजमेंट फ़ंक्शन का इस्तेमाल किए बिना, रॉ मेमोरी को कैसे ऐक्सेस कर सकते हैं?

C और Emscripten

Emscripten, WebAssembly टारगेट के लिए एक C कंपाइलर है. Emscripten का मकसद है, GCC या clang जैसे जाने-माने C कंपाइलर के लिए ड्रॉप-इन की तरह काम करना. साथ ही, उससे ज़्यादातर कॉन्टेंट फ़्लैग होता है. यह Emscripten के मिशन का अहम हिस्सा है, क्योंकि यह WebAssembly में मौजूदा C और C++ कोड को कंपाइल करना जितना आसान हो सके, ज़्यादा आसान बनाना चाहता है.

रॉ मेमोरी ऐक्सेस करने का काम C में होता है और इसी वजह से पॉइंटर मौजूद होते हैं:

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

यहां हम 0x124 की संख्या को, साइन नहीं किए गए 8-बिट पूर्णांक (या बाइट) में, पॉइंटर में बदल रहे हैं. यह असरदार तरीके से, ptr वैरिएबल को एक कलेक्शन में बदल देता है. यह मेमोरी पता 0x124 से शुरू होता है. इसे किसी अन्य कलेक्शन की तरह इस्तेमाल किया जा सकता है. इससे, हमें पढ़ने और लिखने के लिए अलग-अलग बाइट ऐक्सेस की जा सकती है. हमारे मामले में, हम उस इमेज के आरजीबीए बफ़र की गणना कर रहे हैं जिसे हम रोटेशन पाने के लिए फिर से क्रम में बनाना चाहते हैं. किसी पिक्सल को मूव करने के लिए, हमें असल में एक बार में लगातार चार बाइट डेटा ट्रांसफ़र करने की ज़रूरत होती है (हर चैनल के लिए एक बाइट: R, G, B, और A). इसे आसान बनाने के लिए, हम बिना हस्ताक्षर वाले 32-बिट पूर्णांकों की एक कैटगरी बना सकते हैं. पारंपरिक तरीके से, हमारी इनपुट इमेज चौथे पते से शुरू होगी और आउटपुट इमेज, इनपुट इमेज के खत्म होने के बाद सीधे शुरू होगी:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

पूरे JavaScript फ़ंक्शन को C में पोर्ट करने के बाद, हम C फ़ाइल को emcc के साथ कंपाइल कर सकते हैं:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

हमेशा की तरह, emscripten c.js नाम की एक ग्लू कोड फ़ाइल जनरेट करता है. साथ ही, c.wasm नाम का एक Wasm मॉड्यूल जनरेट करता है. ध्यान दें कि Wasm मॉड्यूल gzip सिर्फ़ ~260 बाइट तक सेव होता है, जबकि gzip के बाद ग्लू कोड करीब 3.5 केबी का होता है. कुछ समय के बाद, हम ग्लू कोड छोड़ गए और वनीला एपीआई के साथ WebAssembly मॉड्यूल को इंस्टैंशिएट कर पाए. ऐसा अक्सर Emscripten के साथ तब तक हो सकता है, जब तक कि आप C स्टैंडर्ड लाइब्रेरी का कोई भी इस्तेमाल न कर रहे हों.

Rust

Rust एक नई और आधुनिक प्रोग्रामिंग भाषा है, जिसमें रिच टाइप सिस्टम है. इसके पास रनटाइम और मालिकाना हक वाला मॉडल नहीं है, जो मेमोरी और थ्रेड की सुरक्षा की गारंटी देता है. Rust, WebAssembly की मुख्य सुविधा के तौर पर भी काम करता है और Rust की टीम ने WebAssembly के नेटवर्क में कई बेहतरीन टूल बनाए हैं.

इनमें से एक टूल wasm-pack है, जिसे rustvasm वर्किंग ग्रुप ने बनाया है. wasm-pack आपके कोड को लेकर, उसे वेब-फ़्रेंडली मॉड्यूल में बदल देता है. यह वेबपैक जैसे बंडलर के साथ बेहतर तरीके से काम करता है. wasm-pack बहुत ही सुविधाजनक अनुभव है, लेकिन यह अभी सिर्फ़ Rust के लिए काम करती है. ग्रुप, अन्य WebAssembly को टारगेट करने वाली भाषाओं में भी इसे शामिल करने पर विचार कर रहा है.

Rust में, स्लाइस, C में मौजूद कलेक्शन होते हैं. C की तरह ही, हमें ऐसे स्लाइस बनाने होंगे जो हमारे शुरुआती पतों का इस्तेमाल करते हैं. यह रस्ट के लागू किए जाने वाले मेमोरी सुरक्षा मॉडल के ख़िलाफ़ है. इसलिए, हमें unsafe कीवर्ड का इस्तेमाल करना पड़ता है. इससे हमें ऐसा कोड लिखने की अनुमति मिलती है जो उस मॉडल का पालन नहीं करता.

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

इसका इस्तेमाल करके रस्ट फ़ाइलों को कंपाइल करना

$ wasm-pack build

7.6 केबी Wasm मॉड्यूल देता है, जिसमें करीब 100 बाइट ग्लू कोड होता है (gzip के बाद दोनों कोड).

AssemblyScript

AssemblyScript एक युवा प्रोजेक्ट है, जो कि TypeScript-to-WebAssembly कंपाइलर है. हालांकि, यह ध्यान रखना ज़रूरी है कि यह सिर्फ़ टाइपस्क्रिप्ट का इस्तेमाल नहीं करेगा. असेंबलीस्क्रिप्ट, टाइपस्क्रिप्ट जैसे सिंटैक्स का इस्तेमाल करती है, लेकिन स्टैंडर्ड लाइब्रेरी को खुद ही बदल देती है. उनकी स्टैंडर्ड लाइब्रेरी, WebAssembly की क्षमताओं को मॉडल करती है. इसका मतलब है कि WebAssembly के आस-पास मौजूद किसी भी TypeScript को कंपाइल नहीं किया जा सकता. हालांकि, इसका मतलब यह है कि WebAssembly में लिखने के लिए आपको कोई नई प्रोग्रामिंग भाषा सीखने की ज़रूरत नहीं है!

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

हमारे rotate() फ़ंक्शन की छोटी सतह को ध्यान में रखते हुए, इस कोड को असेंबलीस्क्रिप्ट में पोर्ट करना काफ़ी आसान था. रॉ मेमोरी को ऐक्सेस करने के लिए, load<T>(ptr: usize) और store<T>(ptr: usize, value: T) फ़ंक्शन, AssemblyScript देता है. हमारी असेंबली स्क्रिप्ट फ़ाइल को कंपाइल करने के लिए, हमें सिर्फ़ AssemblyScript/assemblyscript एनपीएम पैकेज इंस्टॉल करने और

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

असेंबलीस्क्रिप्ट हमें ~300 बाइट Wasm मॉड्यूल और no ग्लू कोड देगा. यह मॉड्यूल सिर्फ़ वनीला WebAssembly एपीआई के साथ काम करता है.

वेबअसेंबली फ़ोरेंसिक

Rust का 7.6 केबी, दो अन्य भाषाओं की तुलना में हैरतअंगेज़ तरीके से बड़ा है. WebAssembly इकोसिस्टम में ऐसे कुछ टूल हैं जिनकी मदद से WebAssembly फ़ाइलों का विश्लेषण किया जा सकता है. इन टूल की मदद से, इन फ़ाइलों का विश्लेषण किया जा सकता है. इस बात से कोई फ़र्क़ नहीं पड़ता कि फ़ाइलें किस भाषा में बनाई गई हैं. साथ ही, इन टूल से स्थिति को बेहतर बनाने में मदद मिलती है.

ट्विगी

Twiggy, Rust की WebAssembly टीम का एक और टूल है, जो WebAssembly मॉड्यूल से अहम जानकारी वाला बहुत सारा डेटा एक्सट्रैक्ट करती है. यह टूल रस्ट के लिए नहीं है. इसकी मदद से, मॉड्यूल के कॉल ग्राफ़ जैसी चीज़ों की जांच की जा सकती है. साथ ही, इस्तेमाल न किए गए या ग़ैर-ज़रूरी सेक्शन की पहचान की जा सकती है और यह पता लगाया जा सकता है कि कौनसे सेक्शन आपके मॉड्यूल की फ़ाइल के कुल साइज़ पर असर डाल रहे हैं. बाद में, Twiggy के top निर्देश का इस्तेमाल करके ऐसा किया जा सकता है:

$ twiggy top rotate_bg.wasm
Twiggy इंस्टॉल करने का स्क्रीनशॉट

इस मामले में हम देख सकते हैं कि हमारी फ़ाइल का ज़्यादातर साइज़ ऐलकेटर से बनता है. यह हैरानी की बात है, क्योंकि हमारा कोड, डाइनैमिक ऐलोकेशन का इस्तेमाल नहीं कर रहा है. "फ़ंक्शन के नाम" सब-सेक्शन, योगदान देने वाला एक अन्य बड़ा फ़ैक्टर है.

Wasm-स्ट्रिप

wasm-strip, WebAssembly बाइनरी टूलकिट का एक टूल है या इसे कम शब्दों में कहते हैं. इसमें कई टूल मौजूद हैं. इनकी मदद से, WebAssembly मॉड्यूल की जांच की जा सकती है और उनमें बदलाव किया जा सकता है. wasm2wat एक डिस असेंबलर है, जो बाइनरी Wasm मॉड्यूल को ऐसे फ़ॉर्मैट में बदल देता है जिसे इंसान पढ़ सकें. Wabt में wat2wasm भी होता है. इसकी मदद से, किसी व्यक्ति के आसानी से पढ़े जा सकने वाले फ़ॉर्मैट को, वापस बाइनरी Wasm मॉड्यूल में बदला जा सकता है. हालांकि, हमने अपनी WebAssembly फ़ाइलों की जांच करने के लिए, इन दो सहायक टूल का इस्तेमाल किया है, लेकिन हमें wasm-strip सबसे काम का लगा. wasm-strip, WebAssembly मॉड्यूल से गैर-ज़रूरी सेक्शन और मेटाडेटा को हटा देता है:

$ wasm-strip rotate_bg.wasm

इससे रस्ट मॉड्यूल का फ़ाइल साइज़ 7.5 केबी से 6.6 केबी (gzip के बाद) तक कम हो जाता है.

wasm-opt

wasm-opt, बाइनरियन का एक टूल है. इसके लिए WebAssembly मॉड्यूल की ज़रूरत होती है. साथ ही, यह सिर्फ़ बाइट कोड के आधार पर, साइज़ और परफ़ॉर्मेंस, दोनों के लिए इसे ऑप्टिमाइज़ करने की कोशिश करता है. Emscripten जैसे कुछ टूल पहले से इस टूल को चलाते हैं, जबकि कुछ नहीं चलाते हैं. आम तौर पर इन टूल का इस्तेमाल करके कुछ और बाइट बचाना एक अच्छा आइडिया है.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

wasm-opt से हम gzip के बाद कुल 6.2 केबी छोड़ने के लिए, और मुट्ठी भर बाइट डेटा छोटा कर सकते हैं.

#![no_std]

कुछ सलाह और रिसर्च करने के बाद, हमने रस्ट की स्टैंडर्ड लाइब्रेरी का इस्तेमाल किए बिना, #![no_std] सुविधा का इस्तेमाल किए बिना अपना Rust कोड फिर से बनाया. यह हमारे मॉड्यूल से ऐलोकेटर कोड को हटाते हुए, डाइनैमिक मेमोरी का बंटवारा भी बंद हो जाता है. इस रस्ट फ़ाइल को इनके साथ कंपाइल करें

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

wasm-opt, wasm-strip, और gzip के बाद 1.6 केबी Wam का मॉड्यूल मिला. हालांकि, यह अब भी C और असेंबलीस्क्रिप्ट से जनरेट किए गए मॉड्यूल से बड़ा है, लेकिन इसे लाइटवेट माना जा सकता है.

परफ़ॉर्मेंस

इससे पहले कि हम फ़ाइल के साइज़ के आधार पर किसी नतीजे पर जाएं, हमने फ़ाइल के साइज़ के बजाय, परफ़ॉर्मेंस को ऑप्टिमाइज़ किया. तो हमने परफ़ॉर्मेंस को कैसे मापा और इससे क्या नतीजे मिले?

मानदंड कैसे बनाएं

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

हमारा इस्तेमाल का उदाहरण दिलचस्प है कि किसी इमेज को बदलने वाला कोड एक बार या शायद दो बार इस्तेमाल किया जाएगा. इसलिए, ज़्यादातर मामलों में हमें ऑप्टिमाइज़ करने वाले कंपाइलर के फ़ायदे कभी नहीं मिलेंगे. बेंचमार्क करते समय इस बात का ध्यान रखना ज़रूरी है. हमारे WebAssembly मॉड्यूल को एक लूप में 10,000 बार चलाने से गैर-भरोसेमंद नतीजे मिलेंगे. सही आंकड़े पाने के लिए, हमें मॉड्यूल को एक बार चलाना होगा और उस एक बार के आंकड़ों के आधार पर फ़ैसले लेने होंगे.

परफ़ॉर्मेंस की तुलना

हर भाषा के हिसाब से स्पीड की तुलना
हर ब्राउज़र पर स्पीड की तुलना

ये दोनों ग्राफ़, एक ही डेटा पर अलग-अलग व्यू दिखाते हैं. पहले ग्राफ़ में हम हर ब्राउज़र के हिसाब से तुलना करते हैं. दूसरे ग्राफ़ में, हम इस्तेमाल की गई भाषा के हिसाब से तुलना करते हैं. कृपया ध्यान दें कि मैंने एक लॉगारिद्मिक टाइमस्केल चुना है. यह भी ज़रूरी है कि सभी मानदंडों के लिए, एक ही 16 मेगापिक्सल टेस्ट इमेज और एक ही होस्ट मशीन का इस्तेमाल किया जा रहा हो. हालांकि, एक ब्राउज़र को इस मशीन पर नहीं चलाया जा सकता था.

इन ग्राफ़ का बहुत ज़्यादा विश्लेषण किए बिना, यह साफ़ है कि हमने अपनी मूल परफ़ॉर्मेंस की समस्या को हल कर दिया है: सभी WebAssembly मॉड्यूल ~500 मि॰से॰ या उससे कम समय में चलते हैं. इससे यह पुष्टि होती है कि हमने शुरुआत में क्या बताया था: WebAssembly, आपको अनुमानित परफ़ॉर्मेंस देता है. हम चाहे कोई भी भाषा चुनें, ब्राउज़र और भाषाओं के बीच अंतर बहुत कम है. सटीक जानकारी के लिए: सभी ब्राउज़र में JavaScript का स्टैंडर्ड डीविएशन ~400 मि॰से॰ है, जबकि सभी ब्राउज़र में हमारे सभी WebAssembly मॉड्यूल का स्टैंडर्ड डीविएशन ~80 मि॰से॰ है.

प्रयास

दूसरी मेट्रिक यह है कि हमें अपने WebAssembly मॉड्यूल को स्क्वॉश में बनाने और इंटिग्रेट करने के लिए कितनी मेहनत करनी पड़ी. मेहनत से कोई संख्या असाइन करना मुश्किल है. इसलिए, मैं कोई ग्राफ़ नहीं बनाऊँगा, लेकिन कुछ चीज़ों के बारे में बताऊँगा:

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

wasm-pack के साथ रस्ट का इस्तेमाल करना भी बेहद आसान है. हालांकि, बड़े WebAssembly प्रोजेक्ट, बाइंडिंग हैं. इसमें बेहतर तरीके से काम करने के लिए, मेमोरी को मैनेज करने की ज़रूरत भी पड़ती है. हमें हैप्पी पाथ से थोड़ा हटकर, फ़ाइल के आकार को प्रतिस्पर्धी बनाना पड़ा.

C और Emscripten ने एक बहुत छोटा और बेहतर परफ़ॉर्म करने वाला WebAssembly मॉड्यूल बनाया. हालांकि, ग्लू कोड का इस्तेमाल करने और उसे कम करने की ज़रूरत न होने पर भी कुल साइज़ (WebAssembly मॉड्यूल + ग्लू कोड) का ऐक्सेस काफ़ी बड़ा हो गया है.

नतीजा

इस तरह, अगर आपके पास JS हॉट पाथ है और आपको इसे WebAssembly के साथ तेज़ या ज़्यादा एक जैसा बनाना है, तो किस भाषा का इस्तेमाल करना चाहिए. हमेशा की तरह, परफ़ॉर्मेंस से जुड़े सवालों के जवाब हैं: यह कई बातों पर निर्भर करता है. हमने क्या भेजा?

तुलना का ग्राफ़

हमने जिन भाषाओं का इस्तेमाल किया, उनके मॉड्यूल साइज़ / परफ़ॉर्मेंस के हिसाब से तुलना करने पर, सबसे बेहतर विकल्प या तो C या असेंबलीस्क्रिप्ट है. हमने ज़स्ट को भेजने का फ़ैसला किया. यह फ़ैसला लेने की कई वजहें हैं: Squoosh में अब तक भेजे गए सभी कोडेक, Emscripten का इस्तेमाल करके इकट्ठा किए गए हैं. हम WebAssembly नेटवर्क के बारे में ज़्यादा जानकारी पाना चाहते थे. साथ ही, प्रोडक्शन में अलग भाषा का इस्तेमाल करना चाहते थे. असेंबली स्क्रिप्ट एक अच्छा विकल्प है, लेकिन प्रोजेक्ट की तुलना में यह बहुत छोटा है. साथ ही, कंपाइलर, रस्ट कंपाइलर जितना मैच्योर नहीं है.

स्कैटर ग्राफ़ में Rust और दूसरी भाषाओं के साइज़ के बीच फ़ाइल के साइज़ का अंतर काफ़ी ज़्यादा दिखता है, लेकिन असल में यह कोई बड़ी चीज़ नहीं है: 2G पर भी 500B या 1.6 केबी लोड होने में, एक सेकंड के 1/10वें हिस्से से भी कम समय लगता है. उम्मीद है कि जल्द ही, रस्ट मॉड्यूल के साइज़ के अंतर को खत्म कर देगा.

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

ऐसा ही कहा जा रहा है: असेंबलीस्क्रिप्ट एक अच्छी खोज है. इसकी मदद से वेब डेवलपर, नई भाषा सीखे बिना WebAssembly मॉड्यूल बना सकते हैं. असेंबली स्क्रिप्ट टीम ने इस दिशा में बहुत कदम बढ़ाया है और अपने टूलचेन को बेहतर बनाने के लिए लगातार काम कर रही है. हम आने वाले समय में AssemblyScript पर ज़रूर नज़र रखेंगे.

अपडेट: ज़ंग

इस लेख को पब्लिश करने के बाद, Rust की टीम के निक फ़िट्ज़गेराल्ड ने हमें अपनी बेहतरीन Rust Wasm किताब के बारे में बताया. इसमें फ़ाइल के साइज़ को ऑप्टिमाइज़ करने के बारे में एक सेक्शन दिया गया है. वहां दिए गए निर्देशों (सबसे खास तौर पर लिंक टाइम ऑप्टिमाइज़ेशन और मैन्युअल पैनिक हैंडलिंग को चालू करना) से हमें "सामान्य" रस्ट कोड लिखने और फ़ाइल के साइज़ को बढ़ाए बिना, Cargo (रस्ट का npm) का इस्तेमाल करने में मदद मिली. gzip के बाद, Rust मॉड्यूल 370B पर खत्म होता है. ज़्यादा जानकारी के लिए, कृपया Squoosh पर मेरे खोले गए PR पर एक नज़र डालें.

इस यात्रा में उनकी सहायता के लिए एश्ले विलियम्स, स्टीव क्लैबनिक, निक फ़िट्ज़ेराल्ड, और मैक्स ग्रे को विशेष धन्यवाद.