अगर इसे मेज़र नहीं किया जा सकता, तो इसे बेहतर नहीं बनाया जा सकता.
लॉर्ड केल्विन
अपने HTML5 गेम को तेज़ी से चलाने के लिए, आपको पहले परफ़ॉर्मेंस से जुड़ी समस्याओं का पता लगाना होगा. हालांकि, ऐसा करना मुश्किल हो सकता है. फ़्रेम प्रति सेकंड (एफ़पीएस) के डेटा का आकलन करना एक शुरुआत है. हालांकि, पूरी जानकारी पाने के लिए, आपको Chrome गतिविधियों की बारीकियों को समझना होगा.
about:tracing
टूल से आपको अहम जानकारी मिलती है. इससे, परफ़ॉर्मेंस को बेहतर बनाने के लिए, जल्दबाजी में किए जाने वाले ऐसे बदलावों से बचा जा सकता है जो अच्छी नीयत से किए गए अनुमान पर आधारित होते हैं. इससे आपका समय और ऊर्जा बचेगी. साथ ही, आपको यह साफ़ तौर पर पता चलेगा कि Chrome हर फ़्रेम के साथ क्या कर रहा है. इस जानकारी का इस्तेमाल करके, अपने गेम को ऑप्टिमाइज़ किया जा सकता है.
नमस्ते about:tracing
Chrome का about:tracing
टूल, आपको किसी समयावधि के दौरान Chrome की सभी गतिविधियों की जानकारी देता है. यह जानकारी इतनी बारीकी से दी जाती है कि आपको शुरुआत में यह ज़्यादा लग सकती है. Chrome में कई फ़ंक्शन, पहले से ही ट्रैकिंग के लिए इंस्ट्रूमेंट किए गए होते हैं. इसलिए, मैन्युअल तौर पर इंस्ट्रूमेंट किए बिना भी, अपनी परफ़ॉर्मेंस को ट्रैक करने के लिए about:tracing
का इस्तेमाल किया जा सकता है. (अपने JS को मैन्युअल तरीके से इंस्ट्रूमेंट करने के बारे में, बाद में दिया गया सेक्शन देखें)
ट्रैकिंग व्यू देखने के लिए, Chrome के ऑमनीबॉक्स (पता बार) में "about:tracing" टाइप करें.
ट्रैकिंग टूल की मदद से, रिकॉर्डिंग शुरू की जा सकती है. इसके बाद, कुछ सेकंड के लिए गेम चलाकर ट्रैक डेटा देखा जा सकता है. डेटा कुछ ऐसा दिख सकता है:
हां, यह उलझन पैदा करने वाला है. चलिए, अब इसकी जानकारी पढ़ने के तरीके के बारे में बात करते हैं.
हर लाइन, प्रोफ़ाइल की जा रही प्रोसेस को दिखाती है. बाईं-दाईं ओर मौजूद ऐक्सिस, समय को दिखाता है. साथ ही, हर रंगीन बॉक्स, इंस्ट्रूमेंट किए गए फ़ंक्शन कॉल को दिखाता है. इसमें कई तरह के संसाधनों की लाइनें होती हैं. गेम की प्रोफ़ाइलिंग के लिए, CrGpuMain और CrRendererMain सबसे ज़्यादा काम के हैं. CrGpuMain से पता चलता है कि ग्राफ़िक्स प्रोसेसिंग यूनिट (जीपीयू) क्या कर रही है. हर ट्रेस में, ट्रेस की अवधि के दौरान खुले हर टैब के लिए CrRendererMain लाइनें होती हैं. इनमें about:tracing
टैब भी शामिल है.
ट्रेस डेटा पढ़ते समय, सबसे पहले यह तय करना होता है कि CrRendererMain की कौनसी लाइन आपके गेम से जुड़ी है.
इस उदाहरण में, दो उम्मीदवार हैं: 2216 और 6516. माफ़ करें, फ़िलहाल आपके ऐप्लिकेशन को चुनने का कोई बेहतर तरीका नहीं है. हालांकि, समय-समय पर अपडेट होने वाली लाइन को ढूंढकर या अपने कोड को ट्रेस पॉइंट के साथ मैन्युअल तरीके से इंस्ट्रूमेंट करके, उस लाइन को ढूंढा जा सकता है जिसमें आपका ट्रेस डेटा है. इस उदाहरण में, ऐसा लगता है कि अपडेट की फ़्रीक्वेंसी से 6516 एक मुख्य लूप चला रहा है. ट्रैक शुरू करने से पहले सभी अन्य टैब बंद कर दें. इससे, सही CrRendererMain ढूंढना आसान हो जाएगा. हालांकि, आपके गेम के अलावा अन्य प्रोसेस के लिए, अब भी CrRendererMain लाइनें हो सकती हैं.
अपना फ़्रेम ढूंढना
अपने गेम के लिए, ट्रैकिंग टूल में सही लाइन ढूंढने के बाद, अगला चरण मुख्य लूप ढूंढना है. मुख्य लूप, ट्रैकिंग डेटा में बार-बार दिखने वाले पैटर्न की तरह दिखता है. W, A, S, D बटन का इस्तेमाल करके, ट्रैकिंग डेटा को नेविगेट किया जा सकता है: A और D बटन का इस्तेमाल करके, बाईं या दाईं ओर (समय में आगे और पीछे) और W और S बटन का इस्तेमाल करके, डेटा पर ज़ूम इन और ज़ूम आउट किया जा सकता है. अगर आपका गेम 60Hz पर चल रहा है, तो आपके मुख्य लूप का एक पैटर्न हर 16 मिलीसेकंड में दोहराया जाएगा.
गेम का हार्टबीट पता करने के बाद, यह पता लगाया जा सकता है कि हर फ़्रेम में आपका कोड क्या कर रहा है. W, A, S, D का इस्तेमाल करके ज़ूम इन करें, ताकि फ़ंक्शन बॉक्स में टेक्स्ट पढ़ा जा सके.
बॉक्स का यह कलेक्शन, फ़ंक्शन कॉल की सीरीज़ दिखाता है. इसमें हर कॉल को रंगीन बॉक्स से दिखाया जाता है. हर फ़ंक्शन को उसके ऊपर मौजूद बॉक्स से कॉल किया गया था. इसलिए, इस मामले में, यह देखा जा सकता है कि MessageLoop::RunTask ने RenderWidget::OnSwapBuffersComplete को कॉल किया, जिसने RenderWidget::DoDeferredUpdate को कॉल किया. इसके बाद, इसी तरह से अन्य फ़ंक्शन कॉल किए गए. इस डेटा को पढ़कर, यह पूरी जानकारी मिल सकती है कि किसने किसको कॉल किया और हर कॉल को पूरा करने में कितना समय लगा.
हालांकि, यहां थोड़ी समस्या आती है. about:tracing
से एक्सपोज़ की गई जानकारी, Chrome के सोर्स कोड से मिले रॉ फ़ंक्शन कॉल हैं. नाम से यह अनुमान लगाया जा सकता है कि हर फ़ंक्शन क्या कर रहा है, लेकिन यह जानकारी उपयोगकर्ता के लिए आसान नहीं है. अपने फ़्रेम का पूरा फ़्लो देखने के लिए यह तरीका मददगार है. हालांकि, आपको यह जानने के लिए कुछ और ज़्यादा जानकारी चाहिए कि क्या हो रहा है.
ट्रेस टैग जोड़ना
अच्छी बात यह है कि ट्रेस डेटा बनाने के लिए, अपने कोड में मैन्युअल इंस्ट्रूमेंटेशन जोड़ने का एक आसान तरीका है: console.time
और console.timeEnd
.
console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");
ऊपर दिया गया कोड, बताए गए टैग के साथ ट्रैकिंग व्यू के नाम में नए बॉक्स बनाता है. इसलिए, ऐप्लिकेशन को फिर से चलाने पर, आपको "अपडेट" और "रेंडर" बॉक्स दिखेंगे. इन बॉक्स में, हर टैग के लिए शुरू और खत्म होने के कॉल के बीच बीता समय दिखता है.
इसका इस्तेमाल करके, अपने कोड में हॉटस्पॉट को ट्रैक करने के लिए, लोगों के पढ़ने लायक ट्रैकिंग डेटा बनाया जा सकता है.
जीपीयू या सीपीयू?
हार्डवेयर से तेज़ी लाने की सुविधा वाले ग्राफ़िक्स के लिए, प्रोफ़ाइलिंग के दौरान यह सबसे अहम सवाल पूछा जा सकता है: क्या यह कोड जीपीयू पर निर्भर है या सीपीयू पर? हर फ़्रेम के लिए, आपको जीपीयू पर कुछ रेंडरिंग और सीपीयू पर कुछ लॉजिक का काम करना होगा. यह समझने के लिए कि आपके गेम को धीमा करने की क्या वजह है, आपको यह देखना होगा कि दोनों रिसॉर्स के बीच काम का बंटवारा कैसे किया गया है.
सबसे पहले, ट्रैकिंग व्यू में CrGPUMain नाम की लाइन ढूंढें. इससे पता चलता है कि किसी खास समय पर GPU व्यस्त है या नहीं.
इससे पता चलता है कि आपके गेम के हर फ़्रेम से, CrRendererMain के साथ-साथ जीपीयू पर भी सीपीयू का इस्तेमाल होता है. ऊपर दिया गया ट्रेस, इस्तेमाल का एक बहुत ही आसान उदाहरण दिखाता है. इसमें, हर 16 मिलीसेकंड के फ़्रेम के ज़्यादातर समय के लिए, सीपीयू और जीपीयू, दोनों ही काम नहीं कर रहे हैं.
ट्रैकिंग व्यू तब काफ़ी मददगार होता है, जब आपका गेम धीरे चल रहा हो और आपको यह पता न हो कि कौनसा संसाधन ज़्यादा इस्तेमाल हो रहा है. डीबग करने के लिए, यह देखना ज़रूरी है कि GPU और सीपीयू लाइनें आपस में कैसे जुड़ी हैं. पहले दिए गए उदाहरण को ही लें, लेकिन अपडेट लूप में थोड़ा और काम जोड़ें.
console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");
console.time("render");
render();
console.timeEnd("render");
अब आपको एक ट्रेस दिखेगा, जो ऐसा दिखेगा:
इस ट्रेस से हमें क्या पता चलता है? हम देख सकते हैं कि फ़ोटो में दिखाया गया फ़्रेम, 2270 से 2320 मिलीसेकंड तक चला. इसका मतलब है कि हर फ़्रेम को 50 मिलीसेकंड (20 हर्ट्ज़ का फ़्रेम रेट) लग रहे हैं. अपडेट बॉक्स के बगल में, रेंडर फ़ंक्शन को दिखाने वाले रंगीन बॉक्स के स्लिवर दिख सकते हैं. हालांकि, फ़्रेम में अपडेट ही मुख्य रूप से दिखता है.
सीपीयू के मुकाबले, जीपीयू का इस्तेमाल बहुत कम किया जा रहा है. इस कोड को ऑप्टिमाइज़ करने के लिए, ऐसे ऑपरेशन देखे जा सकते हैं जिन्हें शेडर कोड में किया जा सकता है. साथ ही, संसाधनों का बेहतर इस्तेमाल करने के लिए, उन्हें GPU पर ले जाया जा सकता है.
अगर शेडर कोड खुद ही धीमा है और जीपीयू का ज़्यादा इस्तेमाल हो रहा है, तो क्या होगा? अगर हम सीपीयू से ग़ैर-ज़रूरी काम हटा दें और इसके बजाय, फ़्रैगमेंट शेडर कोड में कुछ काम जोड़ दें, तो क्या होगा. यहां एक ऐसा फ़्रैगमेंट शेडर दिया गया है जो ज़रूरत से ज़्यादा महंगा है:
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
for(int i=0; i<9999; i++) {
gl_FragColor = vec4(1.0, 0, 0, 1.0);
}
}
उस शेडर का इस्तेमाल करने वाले कोड का ट्रेस कैसा दिखता है?
फिर से, फ़्रेम की अवधि नोट करें. यहां दोहराए जाने वाले पैटर्न की अवधि 200 मिलीसेकंड (फ़्रेम रेट करीब 5 हर्ट्ज़) है. यह अवधि 2750 से 2950 मिलीसेकंड के बीच है. CrRendererMain लाइन लगभग पूरी तरह खाली है. इसका मतलब है कि ज़्यादातर समय सीपीयू (CPU) काम नहीं कर रहा है, जबकि जीपीयू (GPU) ओवरलोड है. यह इस बात का पक्का संकेत है कि आपके शेडर बहुत भारी हैं.
अगर आपको यह नहीं पता था कि फ़्रेमरेट कम होने की वजह क्या है, तो 5 हर्ट्ज़ के अपडेट को देखकर, आपको गेम कोड में जाकर गेम लॉजिक को ऑप्टिमाइज़ करने या हटाने की कोशिश करनी पड़ सकती है. इस मामले में, इससे कोई फ़ायदा नहीं होगा, क्योंकि गेम लूप में मौजूद लॉजिक से समय नहीं बर्बाद हो रहा है. असल में, इस ट्रेस से पता चलता है कि हर फ़्रेम में सीपीयू का ज़्यादा काम करना असल में "बिना किसी शुल्क के" होगा. ऐसा इसलिए, क्योंकि सीपीयू बेकार में काम कर रहा है. इसलिए, उसे ज़्यादा काम देने से फ़्रेम में लगने वाले समय पर कोई असर नहीं पड़ेगा.
असल उदाहरण
अब देखते हैं कि किसी असल गेम का ट्रैकिंग डेटा कैसा दिखता है. ओपन वेब टेक्नोलॉजी से बनाए गए गेम की एक खास बात यह है कि इनमें अपने पसंदीदा प्रॉडक्ट में क्या हो रहा है, यह देखा जा सकता है. अगर आपको प्रोफ़ाइलिंग टूल आज़माने हैं, तो Chrome Web Store से अपना पसंदीदा WebGL टाइटल चुनें और about:tracing
की मदद से उसकी प्रोफ़ाइल बनाएं. यह शानदार WebGL गेम Skid Racer से लिया गया ट्रैस का उदाहरण है.
ऐसा लगता है कि हर फ़्रेम को करीब 20 मिलीसेकंड लगते हैं. इसका मतलब है कि फ़्रेम रेट करीब 50 एफ़पीएस है. इस चार्ट से पता चलता है कि सीपीयू और जीपीयू, दोनों का इस्तेमाल बराबर किया जा रहा है. हालांकि, जीपीयू का इस्तेमाल ज़्यादा किया जा रहा है. अगर आपको यह देखना है कि WebGL गेम के असल उदाहरणों की प्रोफ़ाइल कैसी होती है, तो Chrome वेब स्टोर पर मौजूद वेबजीएल की मदद से बनाए गए कुछ गेम आज़माएं. इनमें ये गेम शामिल हैं:
नतीजा
अगर आपको अपना गेम 60Hz पर चलाना है, तो हर फ़्रेम के लिए, आपके सभी ऑपरेशन को सीपीयू के 16 मि॰से॰ और जीपीयू के 16 मि॰से॰ में पूरा करना होगा. आपके पास दो संसाधन हैं, जिनका इस्तेमाल एक साथ किया जा सकता है. साथ ही, परफ़ॉर्मेंस को बेहतर बनाने के लिए, इनके बीच काम को शिफ़्ट किया जा सकता है. Chrome का about:tracing
व्यू, यह जानने के लिए एक अहम टूल है कि आपका कोड असल में क्या कर रहा है. इससे आपको सही समस्याओं को हल करके, डेवलपमेंट के समय को बढ़ाने में मदद मिलेगी.
आगे क्या करना है?
जीपीयू के अलावा, Chrome रनटाइम के अन्य हिस्सों को भी ट्रैक किया जा सकता है. Chrome का शुरुआती वर्शन, Chrome Canary है. इसमें आईओ, IndexedDB, और कई अन्य गतिविधियों को ट्रैक करने के लिए टूल मौजूद हैं. इवेंट को ट्रैक करने की मौजूदा स्थिति के बारे में ज़्यादा जानने के लिए, Chromium का यह लेख पढ़ें.
अगर आप वेब गेम डेवलपर हैं, तो यहां दिया गया वीडियो देखना न भूलें. यह GDC 2012 में Google की गेम डेवलपर एडवोकेट टीम की ओर से दी गई एक प्रज़ेंटेशन है. इसमें Chrome पर गेम की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में बताया गया है: