उपयोगकर्ता अनुभव और ऐप्लिकेशन की सफलता के लिए, इस बात से कोई फ़र्क़ नहीं पड़ता कि किस तरह का ऐप्लिकेशन डेवलप किया जा रहा है, ऐप्लिकेशन की परफ़ॉर्मेंस को ऑप्टिमाइज़ करना और यह पक्का करना ज़रूरी है कि वह तेज़ी से लोड हो और बिना किसी रुकावट के इंटरैक्शन की सुविधा मिले. ऐसा करने का एक तरीका यह है कि प्रोफ़ाइलिंग टूल का इस्तेमाल करके, किसी ऐप्लिकेशन की गतिविधि की जांच की जाए. इससे यह जाना जा सकता है कि समयसीमा के दौरान, ऐप्लिकेशन में क्या चल रहा है. DevTools में परफ़ॉर्मेंस पैनल एक बेहतरीन प्रोफ़ाइलिंग टूल है. इसकी मदद से वेब ऐप्लिकेशन की परफ़ॉर्मेंस का विश्लेषण किया जा सकता है और उसे ऑप्टिमाइज़ किया जा सकता है. यदि आपका ऐप्लिकेशन Chrome में चल रहा है, तो यह आपको इस बात का विस्तृत विज़ुअल अवलोकन देता है कि आपके ऐप्लिकेशन के चलाए जाने के दौरान ब्राउज़र क्या कर रहा है. इस गतिविधि को समझने से आपको पैटर्न, अड़चनें और परफ़ॉर्मेंस बढ़ाने के ऐसे हॉटस्पॉट को पहचानने में मदद मिल सकती है जिन पर आप परफ़ॉर्मेंस को बेहतर बनाने के लिए कदम उठा सकते हैं.
यहां दिए गए उदाहरण में, परफ़ॉर्मेंस पैनल के इस्तेमाल के बारे में बताया गया है.
प्रोफ़ाइल बनाने के उदाहरण को सेट अप करना और उसे फिर से बनाना
हाल ही में, हमने परफ़ॉर्मेंस पैनल को ज़्यादा बेहतर बनाने के लिए एक लक्ष्य सेट किया है. खास तौर पर, हम चाहते थे कि यह बड़ी संख्या में परफ़ॉर्मेंस डेटा को ज़्यादा तेज़ी से लोड करे. उदाहरण के लिए, ऐसा तब होता है, जब लंबे समय तक चलने वाली या जटिल प्रोसेस की प्रोफ़ाइल बनाई जा रही हो या बहुत ज़्यादा जानकारी वाला डेटा कैप्चर किया जा रहा हो. इसके लिए, यह समझना ज़रूरी था कि ऐप्लिकेशन की परफ़ॉर्मेंस कैसे और क्यों.
आपको पता होगा कि DevTools खुद एक वेब ऐप्लिकेशन है. परफ़ॉर्मेंस पैनल का इस्तेमाल करके, इसकी प्रोफ़ाइल बनाई जा सकती है. इस पैनल की प्रोफ़ाइल बनाने के लिए, DevTools खोलकर इससे अटैच किया गया दूसरा DevTools खोलें. Google में, इस सेटअप को DevTools-on-DevTools के नाम से जाना जाता है.
सेटअप के तैयार होने पर, प्रोफ़ाइल की जाने वाली स्थिति को फिर से बनाना और रिकॉर्ड करना होगा. भ्रम की स्थिति से बचने के लिए, ओरिजनल DevTools विंडो को "पहला DevTools इंस्टेंस" कहा जाएगा. साथ ही, पहले इंस्टेंस की जांच करने वाली विंडो को "दूसरा DevTools इंस्टेंस" कहा जाएगा.
दूसरे DevTools इंस्टेंस में, परफ़ॉर्मेंस पैनल—जिसे यहां से परफ़ पैनल कहा जाएगा—यह स्थिति को फिर से बनाने के लिए, DevTools के पहले इंस्टेंस की जांच करता है, जो एक प्रोफ़ाइल लोड करता है.
दूसरे DevTools इंस्टेंस पर लाइव रिकॉर्डिंग शुरू होती है, जबकि पहले इंस्टेंस पर, डिस्क पर मौजूद किसी फ़ाइल से प्रोफ़ाइल लोड की जाती है. बड़े इनपुट को प्रोसेस करने की सटीक प्रोफ़ाइल बनाने के लिए, एक बड़ी फ़ाइल लोड की जाती है. जब दोनों इंस्टेंस लोड हो जाते हैं, तब परफ़ॉर्मेंस प्रोफ़ाइलिंग का डेटा, जिसे आम तौर पर ट्रेस कहा जाता है वह प्रोफ़ाइल लोड करने वाले परफ़ पैनल के दूसरे DevTools इंस्टेंस में दिखता है.
शुरुआती स्थिति: सुधार के अवसरों की पहचान करना
लोड होने के बाद, अगले स्क्रीनशॉट में हमारे दूसरे परफ़ॉर्मेंस पैनल इंस्टेंस पर यह जानकारी मिली. मुख्य थ्रेड की गतिविधि पर फ़ोकस करें. यह थ्रेड, मुख्य लेबल वाले ट्रैक में दिखता है. फ़्लेम चार्ट में गतिविधि के पांच बड़े ग्रुप देखे जा सकते हैं. इनमें ऐसे टास्क शामिल होते हैं जिनमें सबसे ज़्यादा समय लोड होता है. इन टास्क को पूरा करने में करीब 10 सेकंड का समय लगेगा. इस स्क्रीनशॉट में, परफ़ॉर्मेंस पैनल का इस्तेमाल इनमें से हर गतिविधि ग्रुप पर फ़ोकस करने के लिए किया गया है, ताकि यह देखा जा सके कि क्या देखा जा सकता है.
पहली गतिविधि का ग्रुप: ग़ैर-ज़रूरी काम
ऐसा लग रहा था कि गतिविधि का पहला ग्रुप लेगसी कोड था, जो अब भी चल रहा था. हालांकि, उसकी कोई ज़रूरत नहीं थी. मूल रूप से, processThreadEvents
लेबल वाले हरे ब्लॉक में सब कुछ बर्बाद हो गया. यह एक झटका था. उस फ़ंक्शन कॉल को हटाने से करीब 1.5 सेकंड का समय बचा. कूल!
दूसरा गतिविधि ग्रुप
दूसरे गतिविधि ग्रुप में, समस्या को हल करने के लिए पहले वाले ग्रुप जितना आसान नहीं था. buildProfileCalls
को पूरा होने में करीब 0.5 सेकंड लगे. इस टास्क से बचा नहीं जा सकता.
दिलचस्पी की वजह से, हमने आगे की जांच करने के लिए परफ़ॉर्मेंस पैनल में मेमोरी विकल्प चालू किया. इससे हमें पता चला कि buildProfileCalls
की गतिविधि में भी बहुत ज़्यादा मेमोरी इस्तेमाल हो रही थी. यहां देखा जा सकता है कि buildProfileCalls
के चलने के दौरान, नीली लाइन वाला ग्राफ़ अचानक से किस तरह ऊपर-नीचे होता रहता है. इससे पता चलता है कि मेमोरी लीक हो सकती है.
इस सवाल का जवाब देने के लिए, हमने जांच के लिए मेमोरी पैनल का इस्तेमाल किया. DevTools का दूसरा पैनल, जो परफ़ पैनल में मौजूद मेमोरी पैनल से अलग है. मेमोरी पैनल के अंदर, "ऐलोकेशन सैंपलिंग" प्रोफ़ाइलिंग टाइप को चुना गया, जिसमें सीपीयू प्रोफ़ाइल को लोड करने वाले परफ़ पैनल के लिए हीप स्नैपशॉट को रिकॉर्ड किया गया.
यह स्क्रीनशॉट, इकट्ठा किया गया हीप स्नैपशॉट दिखाता है.
इस हीप स्नैपशॉट से, पता चला कि Set
क्लास बहुत ज़्यादा मेमोरी इस्तेमाल कर रही थी. कॉल पॉइंट की जांच करने पर, यह पता चला कि हम बड़े पैमाने पर बनाए गए ऑब्जेक्ट को बिना वजह ही Set
टाइप की प्रॉपर्टी असाइन कर रहे थे. यह लागत बढ़ रही थी और बहुत ज़्यादा मेमोरी खर्च हो रही थी. इतना ही नहीं, बड़े इनपुट पर ऐप्लिकेशन का क्रैश होना आम बात थी.
सेट, यूनीक आइटम को स्टोर करने के लिए काम के होते हैं. साथ ही, ये ऐसे ऑपरेशन भी उपलब्ध कराते हैं जो उनके कॉन्टेंट की खासियत का इस्तेमाल करते हैं. जैसे, डेटासेट की डुप्लीकेट कॉपी करना और ज़्यादा बेहतर लुकअप देना. हालांकि, उन सुविधाओं की ज़रूरत नहीं थी, क्योंकि स्टोर किया गया डेटा, सोर्स से यूनीक होगा. इसलिए, पहले सेट की ज़रूरत नहीं थी. मेमोरी के बंटवारे को बेहतर बनाने के लिए, प्रॉपर्टी टाइप को Set
से बदलकर प्लेन अरे किया गया है. इस बदलाव को लागू करने के बाद, एक और हीप स्नैपशॉट लिया गया. साथ ही, देखा गया कि मेमोरी में कम डेटा खर्च हुआ है. इस बदलाव के बाद, स्पीड में सुधार नहीं हो पाया, लेकिन इसका दूसरा फ़ायदा यह था कि ऐप्लिकेशन बार-बार क्रैश नहीं होता.
तीसरा गतिविधि ग्रुप: डेटा के स्ट्रक्चर के उतार-चढ़ाव को जानना
तीसरा सेक्शन खास है: फ़्लेम चार्ट में देखा जा सकता है कि इसमें छोटे, लेकिन लंबे कॉलम हैं. ये कॉलम डीप फ़ंक्शन कॉल और इस मामले में डीप रिकर्सन की जानकारी देते हैं. कुल मिलाकर, यह सेक्शन करीब 1.4 सेकंड तक चला. इस सेक्शन के नीचे देखने पर, यह साफ़ था कि इन कॉलम की चौड़ाई एक फ़ंक्शन की अवधि से तय की गई थी: appendEventAtLevel
, जिससे पता चला कि यह एक बॉटलनेक हो सकती है
appendEventAtLevel
फ़ंक्शन को लागू करने के दौरान, एक बात सामने आई. इनपुट में हर एक डेटा एंट्री (जिसे कोड में "इवेंट" के नाम से जाना जाता है) के लिए, मैप में एक आइटम जोड़ा गया, जो टाइमलाइन एंट्री की वर्टिकल पोज़िशन को ट्रैक करता था. यह एक समस्या थी, क्योंकि स्टोर किए गए आइटम की मात्रा बहुत ज़्यादा थी. कुंजी-आधारित लुकअप के लिए Maps तेज़ी से काम करते हैं, लेकिन यह फ़ायदा मुफ़्त में नहीं मिलता. उदाहरण के लिए, जैसे-जैसे मैप बड़ा होता जाता है, वैसे-वैसे रिहैशिंग की वजह से इसमें डेटा जोड़ना महंगा हो जाता है. जब मैप पर एक बार में बड़ी संख्या में आइटम जोड़े जाते हैं, तो इस लागत का ध्यान खींचने में मदद मिलती है.
/**
* Adds an event to the flame chart data at a defined vertical level.
*/
function appendEventAtLevel (event, level) {
// ...
const index = data.length;
data.push(event);
this.indexForEventMap.set(event, index);
// ...
}
हमने एक और तरीका आज़माया. इसके लिए, हमें फ़्लेम चार्ट की हर एंट्री के लिए, मैप में कोई आइटम जोड़ने की ज़रूरत नहीं पड़ी. सुधार महत्वपूर्ण था, जिससे पुष्टि होती है कि अड़चन गतिविधि ग्रुप को करीब 1.4 सेकंड से घटकर 200 मिलीसेकंड तक जाना पड़ा.
पहले:
बाद में:
चौथा गतिविधि ग्रुप: डुप्लीकेट काम को रोकने के लिए, गैर-ज़रूरी काम और कैश डेटा को टालना
इस विंडो पर ज़ूम इन करने पर देखा जा सकता है कि फ़ंक्शन कॉल के दो करीब-करीब एक जैसे ब्लॉक मौजूद हैं. कॉल किए गए फ़ंक्शन के नाम देखकर, यह पता लगाया जा सकता है कि इन ब्लॉक में पेड़ बनाने वाला कोड है. उदाहरण के लिए, refreshTree
या buildChildren
जैसे नाम. असल में, मिलता-जुलता कोड वह होता है जो पैनल के निचले ड्रॉर में ट्री व्यू बनाता है. दिलचस्प बात यह है कि लोड होने के तुरंत बाद, ये ट्री व्यू नहीं दिखते. इसके बजाय, उपयोगकर्ता को ट्री दिखाने के लिए एक ट्री व्यू ("सबसे नीचे", "कॉल ट्री", और ड्रॉर में "इवेंट लॉग" टैब) चुनने की ज़रूरत होगी. इसके अलावा, स्क्रीनशॉट से साफ़ तौर पर पता चलता है कि ट्री बनाने की प्रोसेस दो बार पूरी की गई थी.
इस फ़ोटो से जुड़ी दो समस्याओं का पता चला है:
- गैर-ज़रूरी टास्क की वजह से, कॉन्टेंट लोड होने में लगने वाले समय में रुकावट आ रही थी. उपयोगकर्ताओं को हर बार इसके आउटपुट की ज़रूरत नहीं होती. इसलिए, प्रोफ़ाइल लोड होने के लिए यह टास्क ज़रूरी नहीं है.
- इन टास्क के नतीजे कैश मेमोरी में सेव नहीं किए गए. इसलिए, डेटा में कोई बदलाव न होने के बावजूद, पेड़ों को दो बार कैलकुलेट किया गया.
जब उपयोगकर्ता ने मैन्युअल तौर पर ट्री व्यू खोला था, तब हमने ट्री कैलकुलेशन को टालने से शुरुआत की थी. सिर्फ़ तभी इन पेड़ों को बनाने की कीमत चुकानी पड़ती है. इस सुविधा को दो बार चलाने में करीब 3.4 सेकंड लगते थे. इसलिए, टालने की वजह से, कॉन्टेंट लोड होने में लगने वाले समय में काफ़ी अंतर आया. हम अब भी इस तरह के टास्क को कैश मेमोरी में सेव करने की कोशिश कर रहे हैं.
गतिविधि का पांचवां ग्रुप: जब भी हो सके, कॉल की जटिल कैटगरी से बचें
इस ग्रुप को ध्यान से देखने पर यह साफ़ तौर पर पता चला कि किसी खास कॉल चेन का बार-बार अनुरोध किया जा रहा था. यही पैटर्न फ़्लेम चार्ट में अलग-अलग जगहों पर छह बार दिखा और इस विंडो की कुल अवधि करीब 2.4 सेकंड थी!
मिलते-जुलते कोड को कई बार कॉल किया जा रहा है. यह वह हिस्सा है जो "मिनीमैप" पर रेंडर होने के लिए डेटा को प्रोसेस करता है (पैनल के सबसे ऊपर टाइमलाइन गतिविधि की खास जानकारी). मुझे यह नहीं पता था कि ऐसा कई बार क्यों हो रहा है, लेकिन छह बार ऐसा नहीं होना चाहिए! अगर कोई दूसरी प्रोफ़ाइल लोड नहीं की जाती है, तो कोड का आउटपुट अप-टू-डेट रहना चाहिए. सिद्धांत के तौर पर, कोड सिर्फ़ एक बार चलना चाहिए.
जांच करने पर हमने पाया कि मिलते-जुलते कोड को, लोडिंग पाइपलाइन में कई हिस्सों की वजह से कॉल किया गया था. ऐसा, मिनीमैप को कैलकुलेट करने वाले फ़ंक्शन को सीधे या किसी और तरीके से कॉल करने की वजह से किया गया था. इसकी वजह यह है कि समय के साथ प्रोग्राम के कॉल ग्राफ़ की जटिलता बढ़ती गई और इस कोड में ज़्यादा निर्भरता वाली चीज़ें अनजाने में जुड़ गई थीं. इस समस्या का कोई तुरंत समाधान नहीं है. इसे हल करने का तरीका, विचाराधीन कोड बेस के आर्किटेक्चर पर निर्भर करता है. हमारे मामले में, अगर इनपुट डेटा में कोई बदलाव नहीं हुआ, तो हमें कॉल के क्रम से जुड़ी जटिलता को थोड़ा कम करना पड़ा. साथ ही, कोड को एक्ज़ीक्यूट होने से रोकने के लिए जांच करनी पड़ी. इसे लागू करने के बाद, हमने टाइमलाइन के इस नज़रिए को समझा:
ध्यान दें कि मिनीमैप रेंडरिंग का काम एक बार नहीं, बल्कि दो बार होता है. ऐसा इसलिए होता है, क्योंकि हर प्रोफ़ाइल के लिए दो मिनीमैप बनाए जाते हैं: पहला, पैनल में सबसे ऊपर दी गई खास जानकारी के लिए और दूसरा, ड्रॉप-डाउन मेन्यू के लिए जो इतिहास में से मौजूदा समय में दिखने वाली प्रोफ़ाइल को चुनता है (इस मेन्यू के हर आइटम में, चुनी गई प्रोफ़ाइल की खास जानकारी मौजूद होती है). हालांकि, इन दोनों की सामग्री बिलकुल एक जैसी है. इसलिए, यह ज़रूरी है कि दोनों में से कोई एक, दूसरे के लिए फिर से इस्तेमाल किया जा सके.
ये मिनीमैप, दोनों इमेज हैं, जो कैनवस पर बनाई गई हैं. इसलिए, यह बात drawImage
की कैनवस यूटिलिटी सुविधा का इस्तेमाल करने और बाद में कोड को सिर्फ़ एक बार चलाने से हुई. इससे समय बचता है. इस कोशिश की वजह से, ग्रुप की अवधि 2.4 सेकंड से घटाकर 140 मिलीसेकंड कर दी गई.
नतीजा
इन सभी समाधानों (और यहां-वहां कुछ और छोटे पैमाने) को लागू करने के बाद, प्रोफ़ाइल लोड होने की टाइमलाइन में इस तरह का बदलाव हुआ:
पहले:
बाद में:
इन सुधारों के बाद कॉन्टेंट लोड होने में लगने वाला समय दो सेकंड था. इसका मतलब है कि कम मेहनत में करीब 80%तक का सुधार हुआ. ज़्यादातर काम, पहले से किए गए सुधारों की वजह से हुए. शुरुआत में क्या करें की सही पहचान करना बहुत ज़रूरी था. साथ ही, परफ़ॉर्मेंस पैनल इसके लिए सही टूल था.
यह बात हाइलाइट करना भी ज़रूरी है कि ये नंबर खास तौर पर किसी स्टडी के विषय के लिए इस्तेमाल की जा रही प्रोफ़ाइल के लिए हैं. वह प्रोफ़ाइल हमें दिलचस्प लगी, क्योंकि वह खास तौर पर बड़ी थी. इसके बावजूद, हर प्रोफ़ाइल के लिए प्रोसेस करने की प्रोसेस एक जैसी होती है. इसलिए, अहम सुधार, परफ़ॉर्मेंस पैनल में लोड की गई हर प्रोफ़ाइल पर लागू होता है.
सीखने वाली अहम बातें
अपने ऐप्लिकेशन की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में, इन नतीजों से कुछ सीखने की ज़रूरत है:
1. रनटाइम के दौरान परफ़ॉर्मेंस के पैटर्न की पहचान करने के लिए, प्रोफ़ाइलिंग टूल का इस्तेमाल करें
आपके ऐप्लिकेशन के चलने के दौरान उसमें क्या होता है, यह समझने के लिए प्रोफ़ाइलिंग टूल बहुत उपयोगी होते हैं. खास तौर पर, इनकी मदद से परफ़ॉर्मेंस को बेहतर बनाने के अवसरों की पहचान की जा सकती है. Chrome DevTools में परफ़ॉर्मेंस पैनल, वेब ऐप्लिकेशन के लिए एक शानदार विकल्प है. यह ब्राउज़र में वेब प्रोफ़ाइलिंग का नेटिव टूल है. साथ ही, यह वेब प्लैटफ़ॉर्म की नई सुविधाओं के साथ अप-टू-डेट रहता है. साथ ही, अब यह काफ़ी तेज़ हो गया है! 😉
ऐसे सैंपल इस्तेमाल करें जिनका इस्तेमाल प्रतिनिधि वर्कलोड के तौर पर किया जा सकता है. साथ ही, यह भी जानें कि आपको क्या-क्या मिलता है!
2. जटिल कॉल क्रमों से बचें
जहां तक हो सके, कॉल ग्राफ़ को बहुत जटिल बनाने से बचें. जटिल कॉल क्रमों की मदद से, परफ़ॉर्मेंस रिग्रेशन को लागू करना आसान होता है. साथ ही, यह समझना भी मुश्किल होता है कि आपका कोड सही तरीके से क्यों काम कर रहा है. इस वजह से, कोड में सुधार करना मुश्किल हो जाता है.
3. गैर-ज़रूरी काम की पहचान करें
उम्र बढ़ने वाले कोडबेस में ऐसा कोड होना आम बात है जिसकी अब ज़रूरत नहीं होती. हमारे मामले में, लेगसी और ग़ैर-ज़रूरी कोड को लोड होने में लगने वाले कुल समय में सबसे ज़्यादा समय लग रहा था. उसे हटाना सबसे मुश्किल काम था.
4. डेटा स्ट्रक्चर का सही तरीके से इस्तेमाल करना
परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के लिए डेटा स्ट्रक्चर का इस्तेमाल करें. साथ ही, यह तय करने के लिए कि किस डेटा स्ट्रक्चर का इस्तेमाल करना है उसकी लागत और ट्रेड-ऑफ़ को समझें. यह डेटा स्ट्रक्चर के लिए स्पेस की जटिलता के साथ-साथ, लागू होने वाले ऑपरेशन में लगने वाले समय की जटिलता भी है.
5. मुश्किल या दोहराए जाने वाले ऑपरेशन के लिए डुप्लीकेट नतीजों से बचने के लिए, नतीजों को कैश मेमोरी में सेव करें
अगर इस कार्रवाई को पूरा करना महंगा है, तो इसके नतीजों को अगली बार ज़रूरत पड़ने पर सेव करना बेहतर विकल्प है. अगर यह काम कई बार किया जाता है, तो भी ऐसा करना सही रहेगा. भले ही, हर बार अलग-अलग समय पर ज़्यादा पैसे खर्च न किए जा रहे हों.
6. गैर-ज़रूरी काम को टालें
अगर टास्क के आउटपुट की तुरंत ज़रूरत नहीं है और टास्क के पूरा होने की वजह से क्रिटिकल पाथ आगे बढ़ रहा है, तो टास्क के आउटपुट की ज़रूरत होने पर उसे लेज़ी कॉल के ज़रिए टालें.
7. बड़े इनपुट पर कुशल एल्गोरिदम का इस्तेमाल करें
बड़े इनपुट के लिए, समय की जटिलता वाले एल्गोरिदम का इस्तेमाल करना अहम हो जाता है. हमने इस उदाहरण में इस कैटगरी पर ध्यान नहीं दिया है, लेकिन इनकी अहमियत को नज़रअंदाज़ किया जा सकता है.
8. बोनस: अपनी पाइपलाइन को बेंचमार्क करें
यह पक्का करने के लिए कि आपका उभरता हुआ कोड तेज़ बना रहे, उसके व्यवहार पर नज़र रखना और मानकों के साथ उसकी तुलना करना समझदारी है. इस तरह, आप खुद ही प्रतिगमन की पहचान करते हैं और पूरी तरह से भरोसेमंद बनाते हैं और आपको लंबे समय तक सफलता हासिल करने के लिए तैयार करते हैं.