JavaScript की परफ़ॉर्मेंस के रहस्यों को सुलझाने के लिए, फ़ोरेंसिक और डिटेक्टिव प्रोसेस का इस्तेमाल करें

परिचय

पिछले कुछ सालों में, वेब ऐप्लिकेशन की स्पीड काफ़ी बढ़ गई है. कई ऐप्लिकेशन अब इतनी तेज़ी से काम करते हैं कि मैंने कुछ डेवलपर को यह कहते सुना है कि "क्या वेब इतना तेज़ है?". कुछ ऐप्लिकेशन के लिए ऐसा हो सकता है, लेकिन हमें पता है कि बेहतर परफ़ॉर्मेंस वाले ऐप्लिकेशन पर काम करने वाले डेवलपर के लिए, यह तेज़ी काफ़ी नहीं है. JavaScript वर्चुअल मशीन टेक्नोलॉजी में काफ़ी सुधार हुए हैं. इसके बावजूद, हाल ही की एक स्टडी से पता चला है कि Google के ऐप्लिकेशन V8 में अपना 50% से 70% समय बिताते हैं. आपके ऐप्लिकेशन के पास सीमित समय होता है. एक सिस्टम से साइकल कम करने का मतलब है कि कोई दूसरा सिस्टम ज़्यादा काम कर सकता है. याद रखें कि 60fps पर चलने वाले ऐप्लिकेशन के लिए, हर फ़्रेम में सिर्फ़ 16 मिलीसेकंड का समय होता है. ऐसा न होने पर, जैंक होता है. JavaScript को ऑप्टिमाइज़ करने और JavaScript ऐप्लिकेशन की प्रोफ़ाइल बनाने के बारे में जानने के लिए, आगे पढ़ें. इसमें Find Your Way to Oz में, V8 टीम के परफ़ॉर्मेंस डिटेक्टिव की कहानी बताई गई है. इसमें वे परफ़ॉर्मेंस से जुड़ी एक समस्या का पता लगाते हैं.

Google I/O 2013 सेशन

मैंने यह कॉन्टेंट, Google I/O 2013 में पेश किया था. नीचे दिया गया वीडियो देखें:

परफ़ॉर्मेंस क्यों मायने रखती है?

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

परफ़ॉर्मेंस से जुड़ी समस्याएं हल करना

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

V8 CSI: Oz

Find Your Way to Oz गेम बनाने वाले बेहतरीन डेवलपर ने V8 टीम से परफ़ॉर्मेंस से जुड़ी एक समस्या के बारे में बताया. वे इस समस्या को खुद हल नहीं कर पाए थे. कभी-कभी Oz फ़्रीज़ हो जाता था, जिसकी वजह से ऐप्लिकेशन में रुकावट आती थी. ऑस्ट्रेलिया के डेवलपर ने Chrome DevTools में टाइमलाइन पैनल का इस्तेमाल करके, शुरुआती जांच की थी. मेमोरी के इस्तेमाल को देखते हुए, उन्हें सैव टाथ ग्राफ़ मिला. हर सेकंड में, गैर-ज़रूरी डेटा इकट्ठा करने वाला टूल 10 एमबी का गैर-ज़रूरी डेटा इकट्ठा कर रहा था. साथ ही, गैर-ज़रूरी डेटा इकट्ठा करने के दौरान होने वाली रुकावटों की वजह से, ऐप्लिकेशन में रुकावट आ रही थी. यह Chrome DevTools में टाइमलाइन के इस स्क्रीनशॉट से मिलता-जुलता है:

DevTools की टाइमलाइन

V8 डिटेक्टिव, जैकब और यांग ने इस केस की जांच शुरू की. V8 टीम और ऑस्ट्रेलिया की टीम के जैकब और यांग के बीच लंबी बातचीत हुई. हमने इस बातचीत को उन अहम इवेंट तक सीमित कर दिया है जिनसे हमें इस समस्या को ट्रैक करने में मदद मिली.

सबूत

सबसे पहले, शुरुआती सबूत इकट्ठा करके उनकी जांच की जाती है.

हमें किस तरह का ऐप्लिकेशन चाहिए?

Oz का डेमो, इंटरैक्टिव 3D ऐप्लिकेशन है. इस वजह से, यह ग़ैर-ज़रूरी डेटा हटाने की प्रोसेस की वजह से होने वाले रुकावटों के प्रति बहुत संवेदनशील है. याद रखें कि 60fps पर चलने वाले इंटरैक्टिव ऐप्लिकेशन को, JavaScript से जुड़ा सारा काम करने के लिए 16 मिलीसेकंड का समय मिलता है. साथ ही, Chrome को ग्राफ़िक कॉल को प्रोसेस करने और स्क्रीन को ड्रॉ करने के लिए भी कुछ समय चाहिए.

Oz, डबल वैल्यू पर बहुत सारे अंकगणितीय कैलकुलेशन करता है और WebAudio और WebGL को बार-बार कॉल करता है.

हमें परफ़ॉर्मेंस से जुड़ी किस तरह की समस्या दिख रही है?

हमें वीडियो में रुकावटें दिख रही हैं. इसे फ़्रेम ड्रॉप या जंक भी कहा जाता है. ये रुकावटें, ग़ैर-ज़रूरी डेटा हटाने की प्रोसेस के साथ होती हैं.

क्या डेवलपर सबसे सही तरीकों का पालन कर रहे हैं?

हां, ऑस्ट्रेलिया के डेवलपर, JavaScript VM की परफ़ॉर्मेंस और ऑप्टिमाइज़ेशन की तकनीकों के बारे में अच्छी तरह से जानते हैं. ध्यान दें कि ऑस्ट्रेलिया के डेवलपर, सोर्स भाषा के तौर पर CoffeeScript का इस्तेमाल कर रहे थे और CoffeeScript कंपाइलर की मदद से JavaScript कोड जनरेट कर रहे थे. ऑस्ट्रेलिया के डेवलपर के लिखे गए कोड और V8 के इस्तेमाल किए जा रहे कोड के बीच अंतर की वजह से, जांच करना मुश्किल हो गया था. Chrome DevTools में अब सोर्स मैप काम करते हैं. इससे यह काम आसान हो जाता.

गै़रबेज कलेक्टर क्यों चलता है?

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

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

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

V8 की यंग मेमोरी

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

आपको यह समझना चाहिए कि जब भी किसी ऑब्जेक्ट को साफ़ तौर पर या फिर किसी अन्य तरीके से (new, [], या {} को कॉल करके) असाइन किया जाता है, तो आपका ऐप्लिकेशन गै़रबेज कलेक्शन और ऐप्लिकेशन के रुकने की समस्या के करीब पहुंच जाता है.

क्या इस ऐप्लिकेशन के लिए, 10 एमबी/सेकंड का गै़र-ज़रूरी डेटा जनरेट होगा?

कम शब्दों में, नहीं. डेवलपर, 10 एमबी/सेकंड का गै़र-ज़रूरी डेटा भेजने के लिए कुछ नहीं कर रहा है.

संदिग्ध लोग

जांच के अगले चरण में, संदिग्ध लोगों की पहचान की जाती है और फिर उनमें से कुछ लोगों को चुना जाता है.

संदिग्ध व्यक्ति #1

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

संदिग्ध व्यक्ति #2

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

संदिग्ध व्यक्ति #3

ऑप्टिमाइज़ नहीं किए गए कोड में अंकगणित. बिना ऑप्टिमाइज़ किए गए कोड में, सभी गणना के नतीजे असल ऑब्जेक्ट में असाइन किए जाते हैं. उदाहरण के लिए, यह स्निपेट:

var a = p * d;
var b = c + 3;
var c = 3.3 * dt;
point.x = a * b * c;

इससे पांच HeapNumber ऑब्जेक्ट बनते हैं. पहले तीन वैरिएबल, a, b, और c के लिए हैं. चौथा, एनोनीम वैल्यू (a * b) के लिए है और पांचवां, #4 * c से है. पांचवां आखिर में point.x को असाइन किया जाता है.

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

संदिग्ध व्यक्ति #4

किसी प्रॉपर्टी में डबल प्रिसीज़न वाली संख्या को सेव करना. संख्या को सेव करने के लिए, HeapNumber ऑब्जेक्ट बनाया जाना चाहिए. साथ ही, प्रॉपर्टी में बदलाव करके, उसे इस नए ऑब्जेक्ट पर ले जाना चाहिए. HeapNumber पर पॉइंट करने के लिए प्रॉपर्टी में बदलाव करने से, गै़रबै़ज नहीं बनेगा. हालांकि, ऐसा हो सकता है कि कई डबल प्रिसीज़न नंबर, ऑब्जेक्ट प्रॉपर्टी के तौर पर सेव किए जा रहे हों. कोड में इस तरह के स्टेटमेंट मौजूद हैं:

sprite.position.x += 0.5 * (dt);

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

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

संदिग्ध व्यक्ति #4 की संभावना है.

फ़ोरेंसिक जांच सेवा

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

एक्सपेरिमेंट #1

संदिग्ध #3 (ऑप्टिमाइज़ नहीं किए गए फ़ंक्शन में अंकगणितीय गणना) की जांच करना. V8 JavaScript इंजन में लॉगिंग सिस्टम पहले से मौजूद होता है. इससे, इंजन के अंदर होने वाली प्रोसेस के बारे में अहम जानकारी मिल सकती है.

Chrome बिलकुल भी काम नहीं कर रहा है. फ़्लैग के साथ Chrome लॉन्च करना:

--no-sandbox --js-flags="--prof --noprof-lazy --log-timer-events"

इसके बाद, Chrome को पूरी तरह से बंद करने पर, मौजूदा डायरेक्ट्री में v8.log फ़ाइल बन जाएगी.

v8.log के कॉन्टेंट को समझने के लिए, आपको v8 का वही वर्शन डाउनलोड करना होगा जिसका इस्तेमाल आपका Chrome कर रहा है (about:version पर जाएं). इसके बाद, उसे बिल्ड करें.

v8 को बिल्ड करने के बाद, टिक प्रोसेसर का इस्तेमाल करके लॉग को प्रोसेस किया जा सकता है:

$ tools/linux-tick-processor /path/to/v8.log

(अपने प्लैटफ़ॉर्म के हिसाब से, Linux के लिए Mac या Windows का इस्तेमाल करें.) (इस टूल को v8 में, सबसे ऊपर के लेवल की सोर्स डायरेक्ट्री से चलाना चाहिए.)

टिक प्रोसेसर, JavaScript फ़ंक्शन की टेबल दिखाता है. इसमें उन फ़ंक्शन के लिए टिक की संख्या दिखती है जिन पर सबसे ज़्यादा टिक मिले हैं:

[JavaScript]:
ticks  total  nonlib   name
167   61.2%   61.2%  LazyCompile: *opt demo.js:12
 40   14.7%   14.7%  LazyCompile: unopt demo.js:20
 15    5.5%    5.5%  Stub: KeyedLoadElementStub
 13    4.8%    4.8%  Stub: BinaryOpStub_MUL_Alloc_Number+Smi
  6    2.2%    2.2%  Stub: BinaryOpStub_ADD_OverwriteRight_Number+Number
  4    1.5%    1.5%  Stub: KeyedStoreElementStub
  4    1.5%    1.5%  KeyedLoadIC:  {12}
  2    0.7%    0.7%  KeyedStoreIC:  {13}
  1    0.4%    0.4%  LazyCompile: ~main demo.js:30

आपको दिख सकता है कि demo.js में तीन फ़ंक्शन थे: opt, unopt, और main. ऑप्टिमाइज़ किए गए फ़ंक्शन के नाम के बगल में तारे का निशान (*) लगा होता है. ध्यान दें कि फ़ंक्शन opt ऑप्टिमाइज़ किया गया है और unopt को ऑप्टिमाइज़ नहीं किया गया है.

V8 डिटेक्टिव के टूल बैग में, plot-timer-event एक और अहम टूल है. इसे इस तरह से लागू किया जा सकता है:

$ tools/plot-timer-event /path/to/v8.log

इस कोड को चलाने के बाद, मौजूदा डायरेक्ट्री में timer-events.png नाम की एक png फ़ाइल बन जाती है. इसे खोलने पर, आपको कुछ ऐसा दिखेगा:

टाइमर इवेंट

सबसे नीचे मौजूद ग्राफ़ के अलावा, डेटा पंक्तियों में दिखता है. X ऐक्सिस, समय (मिलीसेकंड) है. बाईं ओर, हर लाइन के लिए लेबल होते हैं:

टाइमर इवेंट का Y-ऐक्सिस

V8.Execute लाइन में, हर प्रोफ़ाइल टिक पर काली वर्टिकल लाइन खींची गई है. यह लाइन उस जगह पर खींची गई है जहां V8, JavaScript कोड को चला रहा था. V8.GCScavenger में, हर प्रोफ़ाइल टिक पर नीली वर्टिकल लाइन खींची गई है. यह लाइन उस जगह पर खींची गई है जहां V8, नई जनरेशन का कलेक्शन कर रहा था. इसी तरह, V8 के बाकी स्टेटस के लिए भी ऐसा ही करें.

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

जिस तरह का कोड चलाया जा रहा है

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

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

Oz के सोर्स कोड से टाइमर इवेंट प्लॉट को देखने पर, ऑप्टिमाइज़ किए गए कोड से ऑप्टिमाइज़ नहीं किए गए कोड में ट्रांज़िशन दिखता है. साथ ही, ऑप्टिमाइज़ नहीं किए गए कोड को लागू करते समय, कई नए जनरेशन कलेक्शन ट्रिगर हुए, जो नीचे दिए गए स्क्रीनशॉट की तरह ही हैं. ध्यान दें कि बीच में समय हटा दिया गया है:

टाइमर इवेंट प्लॉट

ध्यान से देखने पर, आपको पता चलेगा कि V8 जब JavaScript कोड को प्रोसेस कर रहा है, तब प्रोफ़ाइल टिक के उसी समय पर काली लाइनें नहीं दिख रही हैं जिस समय नई जनरेशन के कलेक्शन (नीली लाइनें) दिख रहे हैं. इससे साफ़ तौर पर पता चलता है कि कचरा इकट्ठा करने के दौरान, स्क्रिप्ट रोक दी गई है.

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

function updateSprites(dt) {
    for (var sprite in sprites) {
        sprite.position.x += 0.5 * dt;
        // 20 more lines of arithmetic computation.
    }
}

V8 के बारे में अच्छी तरह से जानने के बाद, उन्हें तुरंत पता चल गया कि V8 कभी-कभी for-i-in लूप कंस्ट्रक्ट को ऑप्टिमाइज़ नहीं करता. दूसरे शब्दों में, अगर किसी फ़ंक्शन में for-i-in लूप कंस्ट्रक्ट है, तो हो सकता है कि उसे ऑप्टिमाइज़ न किया जा सके. फ़िलहाल, यह एक खास मामला है. आने वाले समय में इसमें बदलाव हो सकता है. इसका मतलब है कि V8 एक दिन इस लूप कंस्ट्रक्ट को ऑप्टिमाइज़ कर सकता है. हम V8 के विशेषज्ञ नहीं हैं और हमें V8 के बारे में पूरी जानकारी नहीं है. इसलिए, हम यह कैसे तय कर सकते हैं कि updateSprites को ऑप्टिमाइज़ क्यों नहीं किया गया?

एक्सपेरिमेंट #2

Chrome को इस फ़्लैग के साथ चलाना:

--js-flags="--trace-deopt --trace-opt-verbose"

ऑप्टिमाइज़ेशन और डीऑप्टिमाइज़ेशन डेटा का ज़्यादा जानकारी वाला लॉग दिखाता है. updateSprites के लिए डेटा खोजने पर, हमें यह जानकारी मिलती है:

[updateSprites के लिए ऑप्टिमाइज़ेशन बंद है, वजह: ForInStatement फ़ास्ट केस नहीं है]

ठीक वैसे ही जैसे जांचकर्ताओं ने अनुमान लगाया था, इसकी वजह for-i-in लूप कंस्ट्रक्ट थी.

केस बंद हो गया

updateSprites को ऑप्टिमाइज़ न किए जाने की वजह का पता चलने के बाद, इसे ठीक करना आसान था. इसके लिए, कैलकुलेशन को उसके फ़ंक्शन में ले जाना ज़रूरी था. जैसे:

function updateSprite(sprite, dt) {
    sprite.position.x += 0.5 * dt;
    // 20 more lines of arithmetic computation.
}

function updateSprites(dt) {
    for (var sprite in sprites) {
        updateSprite(sprite, dt);
    }
}

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

आखिरी चैप्टर

ऑस्ट्रेलिया के डेवलपर यहीं तक नहीं रुके. V8 के विशेषज्ञों ने उन्हें कुछ टूल और तकनीकें शेयर कीं. इनकी मदद से, उन्होंने कुछ ऐसे फ़ंक्शन ढूंढे जो डीऑप्टिमाइज़ेशन की समस्या से जूझ रहे थे. साथ ही, उन्होंने कैलकुलेशन कोड को लीफ़ फ़ंक्शन में फ़ैक्टर किया, जिन्हें ऑप्टिमाइज़ किया गया. इससे परफ़ॉर्मेंस और भी बेहतर हुई.

अब जाकर, परफ़ॉर्मेंस से जुड़ी समस्याओं को हल करें!