कोड कंपाइल करने में 18% कम समय लगता है, क्वालिटी से कोई समझौता नहीं किया जाता
पढ़ने में 8 मिनट लगेंगेAndroid Runtime (ART) की टीम ने कोड कंपाइल करने में लगने वाले समय को 18% तक कम कर दिया है. हालांकि, कंपाइल किए गए कोड या मेमोरी से जुड़ी किसी भी समस्या की क्वालिटी से कोई समझौता नहीं किया गया है. यह सुधार, 2025 की हमारी पहल का हिस्सा था. इसका मकसद, मेमोरी के इस्तेमाल या कंपाइल किए गए कोड की क्वालिटी से समझौता किए बिना, कोड कंपाइल करने में लगने वाले समय को कम करना था.
ART के लिए, कोड कंपाइल करने में लगने वाले समय को ऑप्टिमाइज़ करना ज़रूरी है. उदाहरण के लिए, जस्ट-इन-टाइम (JIT) कंपाइल करने पर, यह सीधे तौर पर ऐप्लिकेशन की परफ़ॉर्मेंस और डिवाइस की कुल परफ़ॉर्मेंस पर असर डालता है. कोड कंपाइल करने में कम समय लगने से, ऑप्टिमाइज़ेशन शुरू होने में लगने वाला समय कम हो जाता है. इससे लोगों को बेहतर और ज़्यादा रिस्पॉन्सिव अनुभव मिलता है. इसके अलावा, JIT और अहेड-ऑफ़-टाइम (AOT) दोनों के लिए, कोड कंपाइल करने में लगने वाले समय में सुधार करने से, कंपाइलेशन प्रोसेस के दौरान संसाधनों की खपत कम हो जाती है. इससे बैटरी लाइफ़ और डिवाइस के तापमान को बेहतर बनाने में मदद मिलती है. खास तौर पर, कम सुविधाओं वाले डिवाइसों पर.
कोड कंपाइल करने में लगने वाले समय को कम करने से जुड़े कुछ सुधार, जून 2025 में Android के वर्शन में लॉन्च किए गए थे. बाकी सुधार, साल के आखिर में Android के वर्शन में उपलब्ध होंगे. इसके अलावा, Android के वर्शन 12 और उसके बाद के वर्शन इस्तेमाल करने वाले सभी लोग, मेनलाइन अपडेट के ज़रिए इन सुधारों को पा सकते हैं.
ऑप्टिमाइज़ करने वाले कंपाइलर को ऑप्टिमाइज़ करना
किसी कंपाइलर को ऑप्टिमाइज़ करना हमेशा एक मुश्किल काम होता है. आपको कंपाइलर की स्पीड बढ़ाने के लिए, कुछ न कुछ छोड़ना पड़ता है. हमने अपने लिए एक बहुत ही साफ़ और मुश्किल लक्ष्य तय किया है: कंपाइलर को तेज़ बनाना, लेकिन ऐसा मेमोरी से जुड़ी समस्याओं को बढ़ाए बिना और सबसे ज़रूरी बात यह है कि कंपाइलर से जनरेट होने वाले कोड की क्वालिटी को कम किए बिना करना. अगर कंपाइलर तेज़ है, लेकिन ऐप्लिकेशन धीरे चलते हैं, तो हम अपने लक्ष्य को हासिल नहीं कर पाए हैं.
हमने इन ज़रूरी शर्तों को पूरा करने के लिए, अपने डेवलपमेंट के समय को खर्च करने का फ़ैसला किया. इससे हमें गहराई से जांच करने और ऐसे बेहतर समाधान ढूंढने में मदद मिली जो इन ज़रूरी शर्तों को पूरा करते हैं. आइए, इस बारे में ज़्यादा जानें कि हम सुधार के लिए जगहें ढूंढने और अलग-अलग समस्याओं के सही समाधान ढूंढने के लिए कैसे काम करते हैं.
ऑप्टिमाइज़ेशन के लिए काम के विकल्प ढूंढना
किसी मेट्रिक को ऑप्टिमाइज़ करने से पहले, आपको उसे मेज़र करना होगा. ऐसा न करने पर, आपको कभी भी यह पक्का नहीं हो पाएगा कि आपने उसे बेहतर बनाया है या नहीं. हमारे लिए अच्छी बात यह है कि कोड कंपाइल करने में लगने वाले समय की स्पीड में काफ़ी स्थिरता होती है. हालांकि, इसके लिए आपको कुछ सावधानियां बरतनी होंगी. जैसे, बदलाव से पहले और बाद में मेज़र करने के लिए एक ही डिवाइस का इस्तेमाल करना और यह पक्का करना कि आपका डिवाइस ज़्यादा गर्म न हो. इसके अलावा, हमारे पास कंपाइलर के आंकड़ों जैसे तय मेज़रमेंट भी होते हैं. इनसे हमें यह समझने में मदद मिलती है कि बैकग्राउंड में क्या हो रहा है.
इन सुधारों के लिए, हम अपने डेवलपमेंट के समय को खर्च कर रहे थे. इसलिए, हम जितनी तेज़ी से हो सके, उतनी तेज़ी से काम करना चाहते थे. इसका मतलब है कि हमने समाधानों का प्रोटोटाइप बनाने के लिए, कुछ प्रतिनिधि ऐप्लिकेशन (पहले पक्ष के ऐप्लिकेशन, तीसरे पक्ष के ऐप्लिकेशन, और Android ऑपरेटिंग सिस्टम का मिक्स) लिए. इसके बाद, हमने मैन्युअल और ऑटोमेटेड, दोनों तरह की टेस्टिंग करके यह पुष्टि की कि फ़ाइनल वर्शन काम का है.
हमने हाथ से चुने गए एपीके के सेट के साथ, स्थानीय तौर पर मैन्युअल कंपाइल को ट्रिगर किया. इसके बाद, कंपाइलेशन की प्रोफ़ाइल ली और pprof का इस्तेमाल करके यह देखा कि हम अपना समय कहां खर्च कर रहे हैं.
pprof में, किसी प्रोफ़ाइल के फ़्लेम ग्राफ़ का उदाहरण
pprof टूल बहुत काम का है. इसकी मदद से, हम डेटा को स्लाइस, फ़िल्टर, और सॉर्ट कर सकते हैं. उदाहरण के लिए, यह देखा जा सकता है कि कंपाइलर के किन फ़ेज़ या तरीकों में ज़्यादा समय लग रहा है. हम pprof के बारे में ज़्यादा जानकारी नहीं देंगे. बस यह जान लें कि अगर बार बड़ा है, तो इसका मतलब है कि कंपाइलेशन में ज़्यादा समय लगा.
इनमें से एक व्यू “बॉटम अप” है. इसमें यह देखा जा सकता है कि किन तरीकों में ज़्यादा समय लग रहा है. नीचे दी गई इमेज में, हम Kill नाम का एक तरीका देख सकते हैं. इसमें कंपाइल करने में लगने वाले समय का 1% से ज़्यादा समय लग रहा है. ब्लॉग पोस्ट में, अन्य टॉप तरीकों के बारे में भी बाद में बताया जाएगा.
किसी प्रोफ़ाइल का बॉटम अप व्यू
हमारे ऑप्टिमाइज़ करने वाले कंपाइलर में, ग्लोबल वैल्यू नंबरिंग (जीवीएन) नाम का एक फ़ेज़ होता है. आपको इस बारे में चिंता करने की ज़रूरत नहीं है कि यह पूरी तरह से क्या करता है. हालांकि, यह जानना ज़रूरी है कि इसमें `Kill` नाम का एक तरीका है. इसकी मदद से, फ़िल्टर के हिसाब से कुछ नोड मिटाए जा सकते हैं. इसमें ज़्यादा समय लगता है, क्योंकि इसे सभी नोड को बारी-बारी से देखना होता है. हमने देखा कि कुछ मामलों में हमें पहले से पता होता है कि जांच गलत होगी. भले ही, उस समय हमारे पास कोई भी नोड मौजूद हो. इन मामलों में, हम पूरी तरह से जांच को छोड़ सकते हैं. इससे जांच में लगने वाला समय 1.023% से घटकर ~0.3% हो जाता है. साथ ही, जीवीएन के रनटाइम में ~15% का सुधार होता है.
ऑप्टिमाइज़ेशन के लिए काम के विकल्प लागू करना
हमने यह बताया कि समय को कैसे मेज़र किया जाए और यह कैसे पता लगाया जाए कि समय कहां खर्च हो रहा है. हालांकि, यह सिर्फ़ शुरुआत है. अगला कदम, कंपाइल करने में लगने वाले समय को ऑप्टिमाइज़ करना है.
आम तौर पर, ऊपर दिए गए `Kill` जैसे मामले में, हम यह देखते हैं कि नोड को कैसे बारी-बारी से देखा जाए. साथ ही, उदाहरण के लिए, चीज़ों को एक साथ करके या एल्गोरिदम को बेहतर बनाकर, इसे तेज़ी से किया जा सकता है. दरअसल, हमने सबसे पहले यही कोशिश की. जब हमें कुछ नहीं मिला, तो हमने सोचा कि "एक मिनट रुको..." और हमें पता चला कि समाधान यह है कि (कुछ मामलों में) बारी-बारी से देखने की प्रोसेस को पूरी तरह से छोड़ दिया जाए! इस तरह के ऑप्टिमाइज़ेशन करते समय, छोटी-छोटी चीज़ों पर ध्यान देने की वजह से, बड़ी चीज़ों को नज़रअंदाज़ करना आसान है.
अन्य मामलों में, हमने कई अलग-अलग तकनीकों का इस्तेमाल किया. इनमें ये शामिल हैं:
- यह तय करने के लिए ह्यूरिस्टिक्स का इस्तेमाल करना कि किसी ऑप्टिमाइज़ेशन से काम के नतीजे मिलेंगे या नहीं. अगर नहीं मिलेंगे, तो उसे छोड़ा जा सकता है
- कंप्यूट किए गए डेटा को कैश करने के लिए, अतिरिक्त डेटा स्ट्रक्चर का इस्तेमाल करना
- स्पीड बढ़ाने के लिए, मौजूदा डेटा स्ट्रक्चर में बदलाव करना
- कुछ मामलों में साइकल से बचने के लिए, नतीजों को धीरे-धीरे कंप्यूट करना
- सही ऐब्स्ट्रैक्शन का इस्तेमाल करना - गैर-ज़रूरी सुविधाओं से कोड की स्पीड कम हो सकती है
- कई लोड के ज़रिए, अक्सर इस्तेमाल किए जाने वाले पॉइंटर को ट्रैक करने से बचना
हमें कैसे पता चलेगा कि ऑप्टिमाइज़ेशन के लिए काम करना सही है या नहीं?
यह एक मुश्किल काम है. यह पता लगाने के बाद कि किसी हिस्से में कंपाइल करने में ज़्यादा समय लग रहा है और उसे बेहतर बनाने के लिए डेवलपमेंट का समय खर्च करने के बाद, कभी-कभी आपको कोई समाधान नहीं मिल पाता. ऐसा हो सकता है कि कुछ न किया जा सके, समाधान लागू करने में ज़्यादा समय लगे, किसी अन्य मेट्रिक में काफ़ी गिरावट आए, कोड बेस की जटिलता बढ़े वगैरह. इस ब्लॉग पोस्ट में आपको ऑप्टिमाइज़ेशन के लिए काम के जितने भी विकल्प दिखते हैं, उनके अलावा ऐसे कई विकल्प हैं जो काम के नहीं हैं.
अगर आपकी स्थिति भी ऐसी ही है, तो यह अनुमान लगाने की कोशिश करें कि कम से कम काम करके, मेट्रिक को कितना बेहतर बनाया जा सकता है. इसका मतलब है कि इस क्रम में:
- पहले से इकट्ठा की गई मेट्रिक या सिर्फ़ अपने अनुभव के आधार पर अनुमान लगाना
- जल्दी और आसान प्रोटोटाइप के साथ अनुमान लगाना
- कोई समाधान लागू करना.
अपने समाधान की कमियों का अनुमान लगाना न भूलें. उदाहरण के लिए, अगर आपको अतिरिक्त डेटा स्ट्रक्चर पर निर्भर रहना है, तो आप कितनी मेमोरी का इस्तेमाल करना चाहेंगे?
समस्या को गहराई से समझना
अब हम उन बदलावों के बारे में बात करेंगे जिन्हें हमने लागू किया है.
हमने FindReferenceInfoOf नाम के एक तरीके को ऑप्टिमाइज़ करने के लिए, एक बदलाव लागू किया है. यह तरीका, किसी एंट्री को ढूंढने के लिए वेक्टर की लीनियर खोज कर रहा था. हमने उस डेटा स्ट्रक्चर को निर्देश के आईडी से इंडेक्स करने के लिए अपडेट किया, ताकि FindReferenceInfoOf, O(n) के बजाय O(1) हो जाए. इसके अलावा, हमने साइज़ बदलने से बचने के लिए, वेक्टर को पहले से ही एलोकेट कर दिया. हमने मेमोरी को थोड़ा बढ़ाया, क्योंकि हमें एक अतिरिक्त फ़ील्ड जोड़ना पड़ा. इससे यह गिना जाता था कि हमने वेक्टर में कितनी एंट्री डाली हैं. हालांकि, यह एक छोटा सा बदलाव था, क्योंकि पीक मेमोरी में कोई बढ़ोतरी नहीं हुई. इससे हमारे LoadStoreAnalysis फ़ेज़ की स्पीड 34-66% बढ़ गई. इससे कंपाइल करने में लगने वाले समय में ~0.5-1.8% का सुधार हुआ.
हमारे पास HashSet का एक कस्टम वर्शन है, जिसका इस्तेमाल हम कई जगहों पर करते हैं. इस डेटा स्ट्रक्चर को बनाने में काफ़ी समय लग रहा था. हमें इसकी वजह पता चली. कई साल पहले, इस डेटा स्ट्रक्चर का इस्तेमाल सिर्फ़ कुछ जगहों पर किया जाता था. इन जगहों पर बहुत बड़े HashSets का इस्तेमाल किया जाता था. इसे ऑप्टिमाइज़ करने के लिए, इसमें बदलाव किया गया था. हालांकि, आजकल इसका इस्तेमाल कम एंट्री और कम समय के लिए किया जाता था. इसका मतलब है कि हम इस बड़े HashSet को बनाकर साइकल बर्बाद कर रहे थे. हालांकि, हमने इसे कुछ एंट्री के लिए ही इस्तेमाल किया और फिर छोड़ दिया. इस बदलाव से, हमने कंपाइल करने में लगने वाले समय में ~1.3-2% का सुधार किया. इसके अलावा, मेमोरी के इस्तेमाल में ~0.5-1% की कमी आई, क्योंकि हम पहले की तरह बड़े डेटा स्ट्रक्चर का इस्तेमाल नहीं कर रहे थे.
हमने डेटा स्ट्रक्चर को कॉपी करने से बचने के लिए, उन्हें रेफ़रंस के तौर पर लैम्डा को पास करके, कंपाइल करने में लगने वाले समय में ~0.5-1% का सुधार किया. यह एक ऐसी चीज़ थी जिसे ओरिजनल समीक्षा में नज़रअंदाज़ कर दिया गया था और यह हमारे कोडबेस में कई सालों तक मौजूद रही. pprof में प्रोफ़ाइल देखने के बाद, हमें पता चला कि ये तरीके बहुत सारे डेटा स्ट्रक्चर बना और मिटा रहे थे. इससे हमें इनकी जांच करने और इन्हें ऑप्टिमाइज़ करने में मदद मिली.
हमने कंप्यूट की गई वैल्यू को कैश करके, कंपाइल किए गए आउटपुट को लिखने वाले फ़ेज़ की स्पीड बढ़ाई. इससे कंपाइल करने में लगने वाले कुल समय में ~1.3-2.8% का सुधार हुआ. हालांकि, अतिरिक्त बुककीपिंग बहुत ज़्यादा थी और ऑटोमेटेड टेस्टिंग से हमें मेमोरी रिग्रेशन के बारे में पता चला. इसके बाद, हमने उसी कोड को फिर से देखा और एक नया वर्शन लागू किया. इससे न सिर्फ़ मेमोरी से जुड़ी समस्या ठीक हुई, बल्कि कंपाइल करने में लगने वाले समय में ~0.5-1.8% का और सुधार हुआ! इस दूसरे बदलाव में, हमें दो डेटा स्ट्रक्चर में से एक को हटाने के लिए, इस फ़ेज़ को फिर से फ़ैक्टर करना पड़ा और यह सोचना पड़ा कि यह फ़ेज़ कैसे काम करना चाहिए.
हमारे ऑप्टिमाइज़ करने वाले कंपाइलर में एक फ़ेज़ होता है. यह बेहतर परफ़ॉर्मेंस पाने के लिए, फ़ंक्शन कॉल को इनलाइन करता है. इनलाइन करने के लिए, हम किसी भी कंप्यूटेशन से पहले ह्यूरिस्टिक्स और काम करने के बाद, लेकिन इनलाइनिंग को फ़ाइनल करने से ठीक पहले, फ़ाइनल जांच, दोनों का इस्तेमाल करते हैं. अगर इनमें से किसी भी जांच में यह पता चलता है कि इनलाइनिंग काम की नहीं है (उदाहरण के लिए, बहुत सारे नए निर्देश जोड़े जाएंगे), तो हम मेथड कॉल को इनलाइन नहीं करते.
हमने दो जांचों को “फ़ाइनल जांच” कैटगरी से “ह्यूरिस्टिक” कैटगरी में ले जाया, ताकि यह अनुमान लगाया जा सके कि कोई इनलाइनिंग सफल होगी या नहीं. ऐसा हमने समय लेने वाले किसी भी कंप्यूटेशन से पहले किया. यह अनुमान सटीक नहीं है, लेकिन हमने पुष्टि की कि हमारे नए ह्यूरिस्टिक्स, परफ़ॉर्मेंस पर असर डाले बिना, पहले इनलाइन किए गए 99.9% हिस्से को कवर करते हैं. इन नए ह्यूरिस्टिक्स में से एक, ज़रूरी डीईएक्स रजिस्टर (~0.2-1.3% सुधार) के बारे में था. दूसरा, निर्देशों की संख्या (~2% सुधार) के बारे में था.
हमारे पास BitVector का एक कस्टम वर्शन है, जिसका इस्तेमाल हम कई जगहों पर करते हैं. हमने कुछ तय साइज़ वाले बिट वेक्टर के लिए, साइज़ बदलने वाले BitVector क्लास को आसान BitVectorView से बदल दिया. इससे कुछ इनडायरेक्शन और रन-टाइम रेंज की जांच खत्म हो जाती है. साथ ही, बिट वेक्टर ऑब्जेक्ट के कंस्ट्रक्शन की स्पीड बढ़ जाती है.
इसके अलावा, BitVectorView क्लास को, पुराने BitVector की तरह हमेशा uint32_t का इस्तेमाल करने के बजाय, स्टोरेज के टाइप के हिसाब से टेंप्लेट किया गया था. इससे कुछ कार्रवाइयां, जैसे कि Union(), 64-बिट प्लैटफ़ॉर्म पर एक साथ दो गुना बिट प्रोसेस कर सकती हैं. Android OS को कंपाइल करते समय, असर डालने वाले फ़ंक्शन के सैंपल में कुल मिलाकर 1% से ज़्यादा की कमी आई. यह कई बदलावों के ज़रिए किया गया [1, 2, 3, 4, 5, 6]
अगर हम सभी ऑप्टिमाइज़ेशन के बारे में विस्तार से बात करेंगे, तो हमें पूरा दिन लग जाएगा! अगर आपको कुछ और ऑप्टिमाइज़ेशन के बारे में जानना है, तो हमारे लागू किए गए कुछ अन्य बदलाव देखें:
- कंपाइल करने में लगने वाले समय को ~0.6-1.6% तक बेहतर बनाने के लिए, बुककीपिंग जोड़ना.
- अगर मुमकिन हो, तो साइकल से बचने के लिए, डेटा को धीरे-धीरे कंप्यूट करना.
- जब प्रीकंप्यूटिंग काम का न हो, तो उसे छोड़ने के लिए, अपने कोड को रिफ़ैक्टर करना.
- कुछ डिपेंडेंट लोड चेन से बचना, जब एलोकेटर को अन्य जगहों से आसानी से पाया जा सकता है.
- गैर-ज़रूरी काम से बचने के लिए, जांच जोड़ने का एक और मामला.
- रजिस्टर एलोकेटर में, रजिस्टर टाइप (कोर/एफ़पी) पर बार-बार ब्रांचिंग से बचना.
- पक्का करना कि कुछ ऐरे, शुरू किए जाएं कंपाइल करने के समय ही. इसके लिए clang पर निर्भर न रहना.
- कुछ लूप को साफ़ करना. रेंज लूप का इस्तेमाल करना. इन्हें clang बेहतर तरीके से ऑप्टिमाइज़ कर सकता है, क्योंकि लूप के साइड इफ़ेक्ट की वजह से, इसे कंटेनर के इंटरनल पॉइंटर को फिर से लोड करने की ज़रूरत नहीं होती. हर इनपुट के लिए, इनलाइन किए गए `InputAt(.)` के ज़रिए लूप में, वर्चुअल फ़ंक्शन `HInstruction::GetInputRecords()` को कॉल करने से बचना.
- कंपाइलर ऑप्टिमाइज़ेशन का इस्तेमाल करके, विज़िटर पैटर्न के लिए Accept() फ़ंक्शन से बचना.
नतीजा
ART के कंपाइल करने में लगने वाले समय की स्पीड को बेहतर बनाने की हमारी कोशिशों से, काफ़ी सुधार हुए हैं. इससे Android ज़्यादा फ़्लूइड और बेहतर हुआ है. साथ ही, बैटरी लाइफ़ और डिवाइस के तापमान को बेहतर बनाने में भी मदद मिली है. ऑप्टिमाइज़ेशन के लिए काम के विकल्पों की पहचान करके और उन्हें लागू करके, हमने दिखाया है कि मेमोरी के इस्तेमाल या कोड की क्वालिटी से समझौता किए बिना, कंपाइल करने में लगने वाले समय को काफ़ी कम किया जा सकता है.
हमारी इस कोशिश में, pprof जैसे टूल की मदद से प्रोफ़ाइलिंग करना, बार-बार काम करना, और कभी-कभी कम फ़ायदे वाले तरीकों को छोड़ना शामिल था. ART की टीम की सामूहिक कोशिशों से, कंपाइल करने में लगने वाले समय को काफ़ी कम किया गया है. साथ ही, आने वाले समय में होने वाले सुधारों के लिए भी आधार तैयार किया गया है.
ये सभी सुधार, साल 2025 के आखिर में Android के अपडेट में उपलब्ध हैं. साथ ही, Android 12 और उसके बाद के वर्शन के लिए, मेनलाइन अपडेट के ज़रिए भी उपलब्ध हैं. हमें उम्मीद है कि ऑप्टिमाइज़ेशन की हमारी प्रोसेस के बारे में यह जानकारी, कंपाइलर इंजीनियरिंग की जटिलताओं और फ़ायदों के बारे में अहम जानकारी देगी!
-
प्रॉडक्ट से जुड़ी खबरें
Google Play की सबसे बड़ी प्राथमिकता है कि लोगों को ऑनलाइन सुरक्षित अनुभव मिले और उन्हें नुकसान से बचाया जा सके.
Paul Feng • पढ़ने में 2 मिनट लगेंगे -
प्रॉडक्ट से जुड़ी खबरें
आज हम आधिकारिक तौर पर Jetpack Compose 1.0 के रिलीज़ होने के पांच साल पूरे होने का जश्न मना रहे हैं. 28 जुलाई, 2021 को लॉन्च किए गए वर्शन 1.0 से लेकर, हमारे सबसे नए वर्शन 1.11 तक, हमने एपीआई में काफ़ी बदलाव देखे हैं. इसलिए, हम इस मौके का जश्न मना रहे हैं.
Rebecca Franks, Nick Butcher, Loryn Hairston • पढ़ने में 5 मिनट लगेंगे -
प्रॉडक्ट से जुड़ी खबरें
Android Studio Quail 2 का स्टेबल वर्शन अब उपलब्ध है. इसका इस्तेमाल प्रोडक्शन में किया जा सकता है. इससे आपके आईडीई में, एजेंटिक वर्कफ़्लो, नेटिव तौर पर इंटिग्रेट की गई मेमोरी लीक प्रोफ़ाइलिंग, और कॉन्टेक्स्ट-अवेयर क्रैश रेमेडिएशन की सुविधा मिलेगी.
-
अमान असफ़ॉ • पढ़ने में 3 मिनट लगेंगे
Amman Asfaw
•
- #Android Studio में Gemini
- # Quail 2
- #Android Studio
Android डेवलपमेंट से जुड़ी नई जानकारी हर हफ़्ते अपने इनबॉक्स में पाएं.