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