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

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

गतिविधि का पहला ग्रुप: ज़रूरी काम नहीं है
इससे पता चला कि गतिविधि का पहला ग्रुप लेगसी कोड था, जो अब भी चल रहा था. हालांकि, इसकी अब ज़रूरत नहीं थी. असल में, processThreadEvents
लेबल वाले हरे ब्लॉक में मौजूद सभी जानकारी किसी काम की नहीं थी. यह काम बहुत जल्दी हो गया. उस फ़ंक्शन कॉल को हटाने से, करीब डेढ़ सेकंड का समय बचा. कूल!
दूसरा ऐक्टिविटी ग्रुप
दूसरे गतिविधि ग्रुप में, समस्या का समाधान पहले ग्रुप की तरह आसान नहीं था. buildProfileCalls
में करीब 0.5 सेकंड लगे. इस टास्क को टाला नहीं जा सकता था.

ज़्यादा जानने के लिए, हमने परफ़ॉर्मेंस पैनल में मेमोरी विकल्प चालू किया. इससे हमें पता चला कि buildProfileCalls
गतिविधि भी बहुत ज़्यादा मेमोरी इस्तेमाल कर रही थी. यहां, यह देखा जा सकता है कि buildProfileCalls
को चलाने के समय, नीले रंग का लाइन ग्राफ़ अचानक से ऊपर-नीचे हो रहा है. इससे पता चलता है कि मेमोरी लीक हो सकती है.

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

यहां दिए गए स्क्रीनशॉट में, इकट्ठा किया गया हीप स्नैपशॉट दिखाया गया है.

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

तीसरा गतिविधि ग्रुप: डेटा स्ट्रक्चर के फ़ायदे और नुकसान का आकलन करना
तीसरा सेक्शन खास है: फ़्लेम चार्ट में देखा जा सकता है कि इसमें पतले लेकिन लंबे कॉलम शामिल हैं. ये डीप फ़ंक्शन कॉल और इस मामले में डीप रिकर्सन दिखाते हैं. कुल मिलाकर, यह सेक्शन करीब 1.4 सेकंड तक चला. इस सेक्शन के सबसे नीचे देखने पर पता चला कि इन कॉलम की चौड़ाई, एक फ़ंक्शन की अवधि से तय होती है: appendEventAtLevel
. इससे पता चलता है कि यह एक बॉटलनेक हो सकता है
appendEventAtLevel
फ़ंक्शन को लागू करने के दौरान, एक चीज़ काफ़ी अहम थी. इनपुट में मौजूद हर डेटा एंट्री के लिए, मैप में एक आइटम जोड़ा गया था. इसे कोड में "इवेंट" के तौर पर जाना जाता है. यह मैप, टाइमलाइन एंट्री की वर्टिकल पोज़िशन को ट्रैक करता था. इससे समस्या हुई, क्योंकि सेव किए गए आइटम की संख्या बहुत ज़्यादा थी. मैप, कुंजियों के आधार पर तेज़ी से खोज करते हैं. हालांकि, इसके लिए आपको कुछ कीमत चुकानी पड़ती है. मैप बड़ा होने पर, उसमें डेटा जोड़ने का खर्च बढ़ सकता है. उदाहरण के लिए, रीहैशिंग की वजह से ऐसा हो सकता है. जब मैप में एक के बाद एक कई आइटम जोड़े जाते हैं, तब इस लागत का पता चलता है.
/**
* 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. बोनस: अपनी पाइपलाइन के लिए बेंचमार्क तय करना
यह पक्का करने के लिए कि आपका कोड तेज़ी से काम करता रहे, उसके व्यवहार को मॉनिटर करना और मानकों से तुलना करना सही होता है. इस तरह, आपको रिग्रेशन का पता पहले ही चल जाता है. साथ ही, आपको पूरे सिस्टम की विश्वसनीयता को बेहतर बनाने में मदद मिलती है. इससे आपको लंबे समय तक सफलता मिलती है.