प्रॉडक्ट से जुड़ी खबरें
18% तेज़ी से कंपाइल करने की सुविधा, क्वालिटी से कोई समझौता नहीं
आठ मिनट में पढ़ें
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% हिस्से को कवर करते हैं. इन नए ह्यूरिस्टिक्स में से एक, ज़रूरी DEX रजिस्टर (~0.2-1.3% सुधार) के बारे में था. दूसरा, निर्देशों की संख्या (~2% सुधार) के बारे में.
हमारे पास BitVector का कस्टम वर्शन है, जिसका इस्तेमाल हम कई जगहों पर करते हैं. हमने कुछ तय साइज़ वाले बिट वेक्टर के लिए, साइज़ बदलने वाले BitVector क्लास को आसान BitVectorView से बदल दिया. इससे कुछ इनडायरेक्शन और रन-टाइम रेंज की जांच खत्म हो जाती है. साथ ही, बिट वेक्टर ऑब्जेक्ट के कंस्ट्रक्शन की स्पीड बढ़ जाती है.
इसके अलावा, BitVectorView क्लास को, पुराने BitVector की तरह हमेशा uint32_t का इस्तेमाल करने के बजाय, स्टोरेज के टाइप के हिसाब से टेंप्लेट किया गया. इससे कुछ कार्रवाइयां, जैसे कि Union(), 64-बिट प्लैटफ़ॉर्म पर एक साथ दोगुने बिट प्रोसेस कर सकती हैं. Android ओएस को कंपाइल करते समय, असर डालने वाले फ़ंक्शन के सैंपल में कुल मिलाकर 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 • तीन मिनट में पढ़ें
-
प्रॉडक्ट से जुड़ी खबरें
पिछले साल, हमने Android डेवलपर की पहचान की पुष्टि करने की सुविधा शुरू की थी. इससे इकोसिस्टम की सुरक्षा को मज़बूत किया जा सकता है और नुकसान पहुंचाने वाले ऐप्लिकेशन रिलीज़ करने के लिए, बुरे मकसद से काम करने वाले लोगों या ग्रुप को अपनी पहचान छिपाने से रोका जा सकता है.
Matthew Forsythe • दो मिनट में पढ़ें
-
प्रॉडक्ट से जुड़ी खबरें
ऑगमेंटेड ओवरले से लेकर पूरी तरह से इमर्सिव एनवायरमेंट तक, Android XR का इकोसिस्टम तेज़ी से बढ़ रहा है. Samsung Galaxy XR आज से ही उपलब्ध है.
Stevan Silva, Vinny DaSilva • तीन मिनट में पढ़ें
अप-टू-डेट रहें
Android डेवलपमेंट से जुड़ी अहम जानकारी, हर हफ़्ते अपने इनबॉक्स में पाएं.