ऑब्जेक्ट पूल के साथ स्टैटिक मेमोरी JavaScript

परिचय

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

आपकी यादों की टाइमलाइन का स्नैपशॉट

आपका एक सहकर्मी हंसता है, क्योंकि उसे पता है कि आपके कंप्यूटर की मेमोरी की वजह से, उसकी परफ़ॉर्मेंस में समस्या आ रही है.

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

सॉ-टूथ का क्या मतलब है

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

गारबेज कलेक्शन और परफ़ॉर्मेंस से जुड़ी लागत

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

गार्बेज कलेक्शन को अक्सर मैन्युअल तरीके से मेमोरी मैनेज करने के उलट दिखाया जाता है. इसमें प्रोग्रामर को यह बताना होता है कि किन ऑब्जेक्ट को डीएलोकेट करना है और मेमोरी सिस्टम में वापस भेजना है

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

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

मेमोरी चर्न (मेमोरी का तेज़ी से कम होना) और गार्बेज कलेक्शन टैक्स को कम करना

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

इस प्रोसेस से, आपका मेमोरी ग्राफ़ इस खाते से ट्रांसफ़र हो जाएगा :

आपकी यादों की टाइमलाइन का स्नैपशॉट

इस पर:

स्टैटिक मेमोरी JavaScript

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

स्टैटिक-मेमोरी JavaScript की ओर बढ़ना

स्टैटिक मेमोरी JavaScript एक ऐसी तकनीक है जिसमें ऐप्लिकेशन के शुरू होने पर, उसके लिए ज़रूरी पूरी मेमोरी को पहले से तय कर दिया जाता है. साथ ही, ऐप्लिकेशन के चलने के दौरान उस मेमोरी को मैनेज किया जाता है, क्योंकि अब ऑब्जेक्ट की ज़रूरत नहीं होती. इस लक्ष्य को कुछ आसान चरणों में हासिल किया जा सकता है:

  1. अपने ऐप्लिकेशन को इस तरह से तैयार करें कि वह यह तय कर सके कि इस्तेमाल के अलग-अलग मामलों के लिए, ज़रूरी लाइव मेमोरी ऑब्जेक्ट की ज़्यादा से ज़्यादा संख्या (हर टाइप के लिए) कितनी है
  2. ज़्यादा से ज़्यादा मेमोरी को पहले से तय करने के लिए, अपने कोड को फिर से लागू करें. इसके बाद, मुख्य मेमोरी में जाने के बजाय, उन्हें मैन्युअल तरीके से फ़ेच/रिलीज़ करें.

असल में, पहले लक्ष्य को हासिल करने के लिए, हमें दूसरे लक्ष्य पर थोड़ा काम करना होगा. इसलिए, चलिए दूसरे लक्ष्य से शुरुआत करते हैं.

ऑब्जेक्ट पूल

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

किसी ऐप्लिकेशन में अलग-अलग तरह के ऑब्जेक्ट टाइप होते हैं. इसलिए, ऑब्जेक्ट पूल का सही तरीके से इस्तेमाल करने के लिए, आपको हर टाइप के लिए एक पूल की ज़रूरत होती है. इससे, ऐप्लिकेशन के रनटाइम के दौरान, ज़्यादा चर्न रेट का पता चलता है.

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

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

ऑब्जेक्ट को पहले से असाइन करना

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

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

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

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

यह कोई जादुई समाधान नहीं है

ऐप्लिकेशन की एक पूरी कैटगरी है, जिसमें स्टैटिक मेमोरी ग्रोथ पैटर्न का इस्तेमाल किया जा सकता है. हालांकि, Chrome DevRel के साथी रेनाटो मैंगिनी ने बताया है कि इसके कुछ नुकसान भी हैं.

नतीजा

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

सोर्स कोड

वेब पर ऑब्जेक्ट पूल के कई तरीके मौजूद हैं. इसलिए, हम आपको किसी और तरीके से बोर नहीं करेंगे. इसके बजाय, हम आपको इन पर रीडायरेक्ट करेंगे. इनमें से हर एक को लागू करने के लिए, अलग-अलग बारीकियां हैं. यह ज़रूरी है, क्योंकि हर ऐप्लिकेशन के इस्तेमाल के लिए, लागू करने की अलग-अलग ज़रूरतें हो सकती हैं.

रेफ़रंस