परिचय
हाल के सालों में, वेब ऐप्लिकेशन की स्पीड में काफ़ी सुधार हुआ है. अब कई ऐप्लिकेशन इतनी तेज़ी से काम करते हैं कि मैंने कुछ डेवलपर को यह कहते हुए सुना है कि "क्या वेब काफ़ी तेज़ है?". कुछ ऐप्लिकेशन के लिए यह सही हो सकता है, लेकिन हमें पता है कि बेहतरीन परफ़ॉर्मेंस वाले ऐप्लिकेशन पर काम करने वाले डेवलपर के लिए, यह काफ़ी तेज़ नहीं है. JavaScript वर्चुअल मशीन टेक्नोलॉजी में शानदार सुधार होने के बावजूद, हाल ही में की गई एक स्टडी से पता चला है कि Google के ऐप्लिकेशन, V8 में 50% से 70% समय बिताते हैं. आपके ऐप्लिकेशन के पास सीमित समय होता है. एक सिस्टम के साइकल को कम करने का मतलब है कि दूसरा सिस्टम ज़्यादा काम कर सकता है. याद रखें कि 60 एफ़पीएस पर चलने वाले ऐप्लिकेशन में, हर फ़्रेम के लिए सिर्फ़ 16 मिलीसेकंड होते हैं. ऐसा न होने पर, जंक की समस्या आ सकती है. JavaScript को ऑप्टिमाइज़ करने और JavaScript ऐप्लिकेशन की प्रोफ़ाइल बनाने के बारे में जानने के लिए, आगे पढ़ें. इसमें V8 टीम के परफ़ॉर्मेंस डिटेक्टिव की कहानी बताई गई है. इसमें Find Your Way to Oz में परफ़ॉर्मेंस से जुड़ी एक ऐसी समस्या का पता लगाया गया है जिसके बारे में ज़्यादा जानकारी नहीं है.
Google I/O 2013 सेशन
मैंने इस कॉन्टेंट को Google I/O 2013 में पेश किया था. नीचे दिया गया वीडियो देखें:
परफ़ॉर्मेंस क्यों मायने रखती है?
सीपीयू साइकल, ज़ीरो सम गेम की तरह होते हैं. सिस्टम के किसी एक हिस्से में कम रिसॉर्स का इस्तेमाल करने से, दूसरे हिस्से में ज़्यादा रिसॉर्स का इस्तेमाल किया जा सकता है. इसके अलावा, सिस्टम को बेहतर तरीके से चलाया जा सकता है. तेज़ी से काम करना और ज़्यादा काम करना, अक्सर एक-दूसरे से अलग लक्ष्य होते हैं. उपयोगकर्ता नई सुविधाएं चाहते हैं. साथ ही, वे यह भी चाहते हैं कि आपका ऐप्लिकेशन बेहतर तरीके से काम करे. JavaScript वर्चुअल मशीनें तेज़ी से काम कर रही हैं. हालांकि, इसका मतलब यह नहीं है कि परफ़ॉर्मेंस से जुड़ी उन समस्याओं को अनदेखा कर दिया जाए जिन्हें आज ठीक किया जा सकता है. ऐसा इसलिए, क्योंकि अपने वेब ऐप्लिकेशन में परफ़ॉर्मेंस से जुड़ी समस्याओं को हल करने वाले कई डेवलपर को इस बारे में पहले से ही पता है. रीयल-टाइम में, हाई फ़्रेम रेट वाले ऐप्लिकेशन में जैंक की समस्या को दूर करना सबसे ज़रूरी होता है. Insomniac Games ने एक स्टडी की, जिससे पता चला कि किसी गेम की सफलता के लिए, फ़्रेम रेट का लगातार अच्छा होना ज़रूरी है: "एक अच्छा फ़्रेम रेट, अब भी किसी प्रॉडक्ट के पेशेवर और अच्छी तरह से बने होने की निशानी है." वेब डेवलपर के लिए ज़रूरी जानकारी.
परफ़ॉर्मेंस से जुड़ी समस्याएं हल करना
परफ़ॉर्मेंस से जुड़ी समस्या को हल करना, किसी अपराध को सुलझाने जैसा है. आपको सबूतों की ध्यान से जांच करनी होगी, संभावित वजहों का पता लगाना होगा, और अलग-अलग तरह के समाधान आज़माकर देखने होंगे. इस पूरी प्रोसेस के दौरान, आपको अपनी मेज़रमेंट को दस्तावेज़ में दर्ज करना होगा, ताकि आप यह पक्का कर सकें कि आपने समस्या को वाकई ठीक कर लिया है. इस तरीके और किसी केस को सुलझाने के लिए जासूसों के तरीके में बहुत कम अंतर होता है. जासूस सबूतों की जांच करते हैं, संदिग्धों से पूछताछ करते हैं, और प्रयोग करते हैं, ताकि उन्हें कोई ठोस सबूत मिल सके.
V8 CSI: Oz
Find Your Way to Oz बनाने वाले शानदार विज़र्ड ने V8 टीम से संपर्क किया. उन्हें परफ़ॉर्मेंस से जुड़ी एक समस्या आ रही थी, जिसे वे खुद हल नहीं कर पा रहे थे. कभी-कभी Oz फ़्रीज़ हो जाता था, जिससे जैंक की समस्या आती थी. Oz के डेवलपर ने Chrome DevTools में टाइमलाइन पैनल का इस्तेमाल करके, शुरुआती जांच की थी. मेमोरी के इस्तेमाल को देखते हुए, उन्हें सॉ टूथ ग्राफ़ मिला. हर सेकंड, गारबेज कलेक्टर 10 एमबी गारबेज इकट्ठा कर रहा था. साथ ही, गारबेज कलेक्शन के रुकने की वजह से जैंक की समस्या आ रही थी. Chrome Devtools में टाइमलाइन के इस स्क्रीनशॉट की तरह:
V8 के जासूस, जैकब और यांग ने इस मामले की जांच शुरू की. इसके बाद, V8 टीम के जैकब और यांग और Oz टीम के बीच काफ़ी बातचीत हुई. मैंने इस बातचीत को उन अहम इवेंट तक सीमित कर दिया है जिनसे इस समस्या को ट्रैक करने में मदद मिली.
सबूत
सबसे पहले, शुरुआती सबूत इकट्ठा करके उनकी जांच की जाती है.
हम किस तरह के ऐप्लिकेशन की बात कर रहे हैं?
Oz डेमो, एक इंटरैक्टिव 3D ऐप्लिकेशन है. इस वजह से, यह गार्बेज कलेक्शन की वजह से होने वाली रुकावटों के प्रति काफ़ी संवेदनशील होता है. याद रखें कि 60fps पर चलने वाले इंटरैक्टिव ऐप्लिकेशन के पास, JavaScript से जुड़े सभी काम करने के लिए 16 मिलीसेकंड होते हैं. साथ ही, उसे Chrome के लिए कुछ समय छोड़ना होगा, ताकि वह ग्राफ़िक कॉल को प्रोसेस कर सके और स्क्रीन ड्रॉ कर सके.
Oz, डबल वैल्यू पर कई अंकगणितीय गणनाएं करता है. साथ ही, WebAudio और WebGL को बार-बार कॉल करता है.
हमें परफ़ॉर्मेंस से जुड़ी किस तरह की समस्या दिख रही है?
हमें वीडियो रुकने की समस्या दिख रही है. इसे फ़्रेम ड्रॉप या जैंक भी कहा जाता है. ये रुकावटें, गार्बेज कलेक्शन के चलने से जुड़ी होती हैं.
क्या डेवलपर सबसे सही तरीके अपना रहे हैं?
हां, Oz के डेवलपर JavaScript VM की परफ़ॉर्मेंस और ऑप्टिमाइज़ेशन की तकनीकों के बारे में अच्छी तरह जानते हैं. ध्यान देने वाली बात यह है कि Oz के डेवलपर, अपनी सोर्स लैंग्वेज के तौर पर CoffeeScript का इस्तेमाल कर रहे थे. साथ ही, CoffeeScript कंपाइलर के ज़रिए JavaScript कोड जनरेट कर रहे थे. इस वजह से, जांच करना कुछ मुश्किल हो गया. ऐसा इसलिए हुआ, क्योंकि Oz डेवलपर जो कोड लिख रहे थे और V8 जिस कोड का इस्तेमाल कर रहा था उनके बीच कोई कनेक्शन नहीं था. Chrome DevTools अब सोर्स मैप के साथ काम करता है. इससे यह काम आसान हो जाता.
गार्बेज कलेक्टर क्यों चलता है?
JavaScript में मेमोरी को डेवलपर के लिए, वीएम अपने-आप मैनेज करता है. V8, गार्बेज कलेक्शन के लिए एक सामान्य सिस्टम का इस्तेमाल करता है. इसमें मेमोरी को दो या उससे ज़्यादा जनरेशन में बांटा जाता है. यंग जनरेशन में वे ऑब्जेक्ट होते हैं जिन्हें हाल ही में मेमोरी असाइन की गई है. अगर कोई ऑब्जेक्ट लंबे समय तक बना रहता है, तो उसे पुरानी जनरेशन में ले जाया जाता है.
नई जनरेशन के डेटा को पुरानी जनरेशन के डेटा की तुलना में ज़्यादा फ़्रीक्वेंसी पर इकट्ठा किया जाता है. ऐसा इसलिए है, क्योंकि युवा पीढ़ी के लिए बनाए गए कलेक्शन की कीमत काफ़ी कम होती है. आम तौर पर, यह माना जाता है कि जीसी के बार-बार रुकने की वजह, यंग जनरेशन कलेक्शन है.
V8 में, यंग मेमोरी स्पेस को एक ही साइज़ के दो ब्लॉक में बांटा जाता है. किसी भी समय, मेमोरी के इन दो ब्लॉक में से सिर्फ़ एक का इस्तेमाल किया जाता है. इसे 'टू स्पेस' कहा जाता है. जब तक स्पेस में मेमोरी बची रहती है, तब तक नए ऑब्जेक्ट को असाइन करना आसान होता है. 'टू स्पेस' में मौजूद कर्सर को नए ऑब्जेक्ट के लिए ज़रूरी बाइट की संख्या के हिसाब से आगे बढ़ाया जाता है. यह प्रोसेस तब तक चलती है, जब तक कि 'to space' भर नहीं जाता. इस समय प्रोग्राम बंद हो जाता है और कलेक्शन शुरू हो जाता है.
इस समय, सोर्स स्पेस और डेस्टिनेशन स्पेस को आपस में बदल दिया जाता है. जिस स्पेस में ऑब्जेक्ट को कॉपी किया गया था और जो अब वह स्पेस है जहां से ऑब्जेक्ट को कॉपी किया जाना है उसे शुरू से आखिर तक स्कैन किया जाता है. इसके बाद, जो ऑब्जेक्ट अब भी मौजूद हैं उन्हें उस स्पेस में कॉपी किया जाता है जिसमें ऑब्जेक्ट को कॉपी किया जाना है या उन्हें पुरानी जनरेशन के हीप में प्रमोट किया जाता है. अगर आपको ज़्यादा जानकारी चाहिए, तो हमारा सुझाव है कि आप चेनी के एल्गोरिदम के बारे में पढ़ें.
आपको यह समझना चाहिए कि जब भी किसी ऑब्जेक्ट को साफ़ तौर पर या साफ़ तौर पर बताए बिना (new, [], या {} को कॉल करके) असाइन किया जाता है, तो आपका ऐप्लिकेशन गार्बेज कलेक्शन और ऐप्लिकेशन के रुकने की स्थिति के करीब पहुंच जाता है.
क्या इस ऐप्लिकेशन के लिए, 10 एमबी/सेकंड का गार्बेज डेटा जनरेट होने की उम्मीद है?
इसका जवाब है, नहीं. डेवलपर ऐसा कुछ नहीं कर रहा है जिससे 10 एमबी/सेकंड का गार्बेज जनरेट हो.
संदिग्ध
जांच का अगला चरण, संभावित संदिग्धों की पहचान करना और फिर उनकी संख्या कम करना है.
संदिग्ध #1
फ़्रेम के दौरान, नए फ़ंक्शन को कॉल करना. याद रखें कि ऐलोकेट किया गया हर ऑब्जेक्ट, आपको जीसी पॉज़ के करीब ले जाता है. खास तौर पर, ज़्यादा फ़्रेम रेट पर चलने वाले ऐप्लिकेशन को हर फ़्रेम के लिए शून्य ऐलोकेशन का लक्ष्य रखना चाहिए. आम तौर पर, इसके लिए ऐप्लिकेशन के हिसाब से, ऑब्जेक्ट को रीसाइकल करने वाला सिस्टम बनाना पड़ता है. V8 के जासूसों ने Oz टीम से संपर्क किया. उन्होंने बताया कि वे नए फ़ंक्शन को कॉल नहीं कर रहे थे. दरअसल, Oz की टीम को इस ज़रूरी शर्त के बारे में पहले से ही पता था. उन्होंने कहा, "यह तो शर्मिंदा करने वाली बात होगी". इस काम को सूची से हटा दें.
संदिग्ध #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 ऑब्जेक्ट अपने-आप असाइन हो जाता है. इससे हम गार्बेज कलेक्शन के पॉज़ के करीब पहुंच जाते हैं.
ध्यान दें कि टाइप किए गए ऐरे (या ऐसे सामान्य ऐरे जिसमें सिर्फ़ डबल वैल्यू होती हैं) का इस्तेमाल करके, इस समस्या से पूरी तरह बचा जा सकता है. ऐसा इसलिए, क्योंकि डबल प्रिसिज़न नंबर के लिए स्टोरेज सिर्फ़ एक बार असाइन किया जाता है. साथ ही, वैल्यू को बार-बार बदलने पर, नया स्टोरेज असाइन करने की ज़रूरत नहीं होती.
चौथा संदिग्ध, अपराधी हो सकता है.
फ़ोरेंसिक जांच सेवा
इस समय, जासूसों को दो संभावित संदिग्धों का पता चलता है: हीप नंबर को ऑब्जेक्ट प्रॉपर्टी के तौर पर सेव करना और ऑप्टिमाइज़ नहीं किए गए फ़ंक्शन के अंदर अंकगणितीय कंप्यूटेशन होना. अब लैब में जाकर, यह पता लगाने का समय आ गया था कि कौन सा संदिग्ध दोषी है. ध्यान दें: इस सेक्शन में, Oz के असल सोर्स कोड में मिली समस्या को फिर से जनरेट करने का तरीका बताया जाएगा. यह रीप्रोडक्शन, ओरिजनल कोड से काफ़ी छोटा होता है. इसलिए, इसे समझना आसान होता है.
एक्सपेरिमेंट #1
तीसरे संदिग्ध की जांच की जा रही है (ऑप्टिमाइज़ नहीं किए गए फ़ंक्शन में अंकगणितीय कंप्यूटेशन). 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 ऐक्सिस पर समय (मिलीसेकंड) दिखाया जाता है. बाईं ओर, हर लाइन के लिए लेबल शामिल हैं:
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 के बारे में ज़्यादा नहीं जानते. इसलिए, हम यह कैसे पता लगा सकते हैं कि updateSprites को ऑप्टिमाइज़ क्यों नहीं किया गया?
एक्सपेरिमेंट #2
इस फ़्लैग के साथ Chrome चलाना:
--js-flags="--trace-deopt --trace-opt-verbose"
ऑप्टिमाइज़ेशन और डीऑप्टिमाइज़ेशन डेटा का ज़्यादा शब्दों वाला लॉग दिखाता है. updateSprites के लिए डेटा में खोजने पर, हमें यह मिलता है:
[disabled optimization for updateSprites, reason: ForInStatement is not fast case]
जैसा कि डिटेक्टिव ने अनुमान लगाया था, 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 ऑब्जेक्ट की संख्या काफ़ी कम हो जाएगी. साथ ही, जीसी के रुकने की फ़्रीक्वेंसी भी कम हो जाएगी. नए कोड के साथ वही एक्सपेरिमेंट करके, इसकी पुष्टि करना आपके लिए आसान होगा. ध्यान से पढ़ने वाले व्यक्ति को पता चलेगा कि डबल नंबर अब भी प्रॉपर्टी के तौर पर सेव किए जा रहे हैं. अगर प्रोफ़ाइलिंग से पता चलता है कि यह फ़ायदेमंद है, तो पोज़िशन को डबल्स के कलेक्शन या टाइप किए गए डेटा कलेक्शन में बदलने से, बनाए जा रहे ऑब्जेक्ट की संख्या और कम हो जाएगी.
आखिरी चैप्टर
Oz के डेवलपर यहीं नहीं रुके. V8 डिटेक्टिव ने उन्हें जो टूल और तकनीकें बताई थीं उनकी मदद से, वे कुछ ऐसे फ़ंक्शन ढूंढ पाए जो डीऑप्टिमाइज़ेशन की वजह से काम नहीं कर रहे थे. साथ ही, उन्होंने कंप्यूटेशन कोड को लीफ़ फ़ंक्शन में बदल दिया, जिन्हें ऑप्टिमाइज़ किया गया था. इससे परफ़ॉर्मेंस और बेहतर हो गई.
तो फिर देर किस बात की, परफ़ॉर्मेंस से जुड़ी समस्याओं को हल करना शुरू करें!