प्रॉडक्ट से जुड़ी खबरें

18% तेज़ी से कंपाइल करने की सुविधा, क्वालिटी से कोई समझौता नहीं

आठ मिनट में पढ़ें
Santiago Aboy Solanes और Vladimír Marko

Android Runtime (ART) की टीम ने कंपाइल करने में लगने वाले समय को 18% तक कम कर दिया है. हालांकि, कंपाइल किए गए कोड या मेमोरी से जुड़ी किसी भी समस्या से कोई समझौता नहीं किया गया है. यह सुधार, कंपाइल करने में लगने वाले समय को बेहतर बनाने के लिए, 2025 की हमारी पहल का हिस्सा था. इसमें मेमोरी के इस्तेमाल या कंपाइल किए गए कोड की क्वालिटी से कोई समझौता नहीं किया गया.

ART के लिए, कंपाइल करने में लगने वाले समय की स्पीड को ऑप्टिमाइज़ करना ज़रूरी है. उदाहरण के लिए, जस्ट-इन-टाइम (JIT) कंपाइल करने पर, यह सीधे तौर पर ऐप्लिकेशन की परफ़ॉर्मेंस और डिवाइस की कुल परफ़ॉर्मेंस पर असर डालता है. तेज़ी से कंपाइल करने पर, ऑप्टिमाइज़ेशन शुरू होने में लगने वाला समय कम हो जाता है. इससे लोगों को बेहतर और ज़्यादा रिस्पॉन्सिव अनुभव मिलता है. इसके अलावा, JIT और अहेड-ऑफ़-टाइम (AOT) दोनों के लिए, कंपाइल करने में लगने वाले समय की स्पीड में सुधार करने से, कंपाइल करने की प्रोसेस के दौरान संसाधनों का इस्तेमाल कम होता है. इससे बैटरी लाइफ़ और डिवाइस के तापमान को फ़ायदा मिलता है. खास तौर पर, कम सुविधाओं वाले डिवाइसों पर.

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

ऑप्टिमाइज़ करने वाले कंपाइलर को ऑप्टिमाइज़ करना

किसी कंपाइलर को ऑप्टिमाइज़ करना हमेशा एक मुश्किल काम होता है. आपको स्पीड मुफ़्त में नहीं मिल सकती. इसके लिए, आपको कुछ छोड़ना होगा. हमने अपने लिए एक बहुत साफ़ और मुश्किल लक्ष्य तय किया है: कंपाइलर को तेज़ बनाना, लेकिन ऐसा मेमोरी से जुड़ी समस्याओं को ठीक करने और सबसे अहम, कंपाइलर से जनरेट होने वाले कोड की क्वालिटी को कम किए बिना करना. अगर कंपाइलर तेज़ है, लेकिन ऐप्लिकेशन धीरे चलते हैं, तो हम फ़ेल हो गए हैं.

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

ऑप्टिमाइज़ेशन के लिए काम के विकल्प ढूंढना

किसी मेट्रिक को ऑप्टिमाइज़ करने से पहले, आपको उसे मेज़र करना होगा. ऐसा न करने पर, आपको कभी भी यह पक्का नहीं हो पाएगा कि आपने उसे बेहतर बनाया है या नहीं. हमारे लिए अच्छी बात यह है कि कंपाइल करने में लगने वाले समय की स्पीड में काफ़ी स्थिरता होती है. हालांकि, इसके लिए आपको कुछ सावधानियां बरतनी होंगी. जैसे, बदलाव से पहले और बाद में मेज़र करने के लिए, उसी डिवाइस का इस्तेमाल करना होगा जिसका इस्तेमाल पहले किया गया था. साथ ही, यह पक्का करना होगा कि आपका डिवाइस ज़्यादा गर्म न हो. इसके अलावा, हमारे पास कंपाइलर के आंकड़ों जैसे तय मेज़रमेंट भी होते हैं. इनसे हमें यह समझने में मदद मिलती है कि बैकग्राउंड में क्या हो रहा है.

 

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

 

हमने हाथ से चुने गए एपीके के सेट के साथ, स्थानीय तौर पर मैन्युअल कंपाइल को ट्रिगर किया. इसके बाद, कंपाइल करने की प्रोफ़ाइल ली और pprof का इस्तेमाल करके यह देखा कि हम अपना समय कहां खर्च कर रहे हैं.

image.png

pprof में, किसी प्रोफ़ाइल के फ़्लेम ग्राफ़ का उदाहरण

pprof टूल बहुत काम का है. इसकी मदद से, हम डेटा को स्लाइस, फ़िल्टर, और सॉर्ट करके यह देख सकते हैं कि कंपाइलर के किन फ़ेज़ या तरीकों में ज़्यादा समय लग रहा है. हम pprof के बारे में ज़्यादा जानकारी नहीं देंगे. बस यह जान लें कि अगर बार बड़ा है, तो इसका मतलब है कि कंपाइल करने में ज़्यादा समय लगा.

इनमें से एक व्यू “बॉटम अप” है. इसमें यह देखा जा सकता है कि किन तरीकों में ज़्यादा समय लग रहा है. नीचे दी गई इमेज में, हम Kill नाम का एक तरीका देख सकते हैं. इसमें कंपाइल करने में लगने वाले समय का 1% से ज़्यादा समय लग रहा है. ब्लॉग पोस्ट में, टॉप पर रहने वाले कुछ अन्य तरीकों के बारे में भी बाद में बताया जाएगा.

image.png

किसी प्रोफ़ाइल का बॉटम अप व्यू

हमारे ऑप्टिमाइज़ करने वाले कंपाइलर में, ग्लोबल वैल्यू नंबरिंग (जीवीएन) नाम का एक फ़ेज़ होता है. आपको इस बारे में चिंता करने की ज़रूरत नहीं है कि यह पूरी तरह से क्या करता है. हालांकि, यह जानना ज़रूरी है कि इसमें `Kill` नाम का एक तरीका है. इसकी मदद से, फ़िल्टर के हिसाब से कुछ नोड मिटाए जाते हैं. इसमें ज़्यादा समय लगता है, क्योंकि इसे सभी नोड को बारी-बारी से देखना होता है. हमने देखा कि कुछ मामलों में हमें पहले से पता होता है कि जांच गलत होगी. भले ही, उस समय हमारे पास कितने भी नोड मौजूद हों. इन मामलों में, हम पूरी तरह से जांच को छोड़ सकते हैं. इससे जांच में लगने वाला समय 1.023% से घटकर ~0.3% हो जाता है. साथ ही, जीवीएन के रनटाइम में ~15% का सुधार होता है.

ऑप्टिमाइज़ेशन के लिए काम के विकल्प लागू करना

हमने यह बताया कि मेज़र कैसे करें और यह कैसे पता लगाएं कि समय कहां खर्च हो रहा है. हालांकि, यह सिर्फ़ शुरुआत है. अगला चरण यह है कि कंपाइल करने में लगने वाले समय को कैसे ऑप्टिमाइज़ किया जाए.

आम तौर पर, ऊपर दिए गए `Kill` जैसे मामले में, हम यह देखते हैं कि नोड को कैसे देखा जाता है. साथ ही, हम इसे तेज़ी से करने की कोशिश करते हैं. उदाहरण के लिए, हम चीज़ों को पैरलल में करते हैं या एल्गोरिदम को बेहतर बनाते हैं. दरअसल, हमने सबसे पहले यही कोशिश की. जब हमें कुछ नहीं मिला, तो हमने सोचा कि "एक मिनट रुको..." और हमें पता चला कि समाधान यह है कि (कुछ मामलों में) जांच न की जाए! इस तरह के ऑप्टिमाइज़ेशन करते समय, यह मुमकिन है कि हम ज़रूरी चीज़ों को नज़रअंदाज़ कर दें.

अन्य मामलों में, हमने कई अलग-अलग तकनीकों का इस्तेमाल किया. इनमें ये शामिल हैं:

  • यह तय करने के लिए ह्यूरिस्टिक्स का इस्तेमाल करना कि क्या ऑप्टिमाइज़ेशन से काम के नतीजे नहीं मिलेंगे. इसलिए, इसे छोड़ा जा सकता है
  • कंप्यूट किए गए डेटा को कैश करने के लिए, अतिरिक्त डेटा स्ट्रक्चर का इस्तेमाल करना
  • स्पीड बढ़ाने के लिए, मौजूदा डेटा स्ट्रक्चर में बदलाव करना
  • कुछ मामलों में साइकल से बचने के लिए, नतीजों को लेज़ीली कंप्यूट करना
  • सही ऐब्स्ट्रैक्शन का इस्तेमाल करना - गैर-ज़रूरी सुविधाओं से कोड की स्पीड कम हो सकती है
  • कई लोड के ज़रिए, अक्सर इस्तेमाल किए जाने वाले पॉइंटर को ट्रैक करने से बचना

हमें कैसे पता चलेगा कि ऑप्टिमाइज़ेशन के लिए काम करना सही है या नहीं?

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

अगर आपकी भी ऐसी ही स्थिति है, तो यह अनुमान लगाने की कोशिश करें कि कम से कम काम करके, मेट्रिक को कितना बेहतर बनाया जा सकता है. इसका मतलब है कि इस क्रम में:

  1. पहले से इकट्ठा की गई मेट्रिक या सिर्फ़ अपने अनुभव के आधार पर अनुमान लगाना
  2. जल्दबाज़ी में तैयार किए गए प्रोटोटाइप के आधार पर अनुमान लगाना
  3. कोई समाधान लागू करना.

अपने समाधान की कमियों का अनुमान लगाना न भूलें. उदाहरण के लिए, अगर आपको अतिरिक्त डेटा स्ट्रक्चर पर निर्भर रहना है, तो आप कितनी मेमोरी का इस्तेमाल करना चाहेंगे?

समस्या को गहराई से समझना

अब हम उन बदलावों के बारे में बात करते हैं जिन्हें हमने लागू किया है.

हमने 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% से ज़्यादा कम हो गए. यह कई बदलावों के ज़रिए किया गया था [123456]

अगर हम सभी ऑप्टिमाइज़ेशन के बारे में विस्तार से बात करेंगे, तो हमें पूरा दिन लग जाएगा! अगर आपको कुछ और ऑप्टिमाइज़ेशन के बारे में जानना है, तो हमने लागू किए गए कुछ अन्य बदलाव देखें:

नतीजा

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

हमारी इस कोशिश में, pprof जैसे टूल की मदद से प्रोफ़ाइलिंग करना, बार-बार बदलाव करना, और कभी-कभी कम फ़ायदे वाले तरीकों को छोड़ना शामिल था. ART की टीम की सामूहिक कोशिशों से, कंपाइल करने में लगने वाले समय को काफ़ी कम किया गया है. साथ ही, आने वाले समय में होने वाले सुधारों के लिए भी आधार तैयार किया गया है.

ये सभी सुधार, साल 2025 के आखिर में Android के अपडेट में उपलब्ध हैं. साथ ही, Android 12 और उसके बाद के वर्शन के लिए, मेनलाइन अपडेट के ज़रिए भी उपलब्ध हैं. हमें उम्मीद है कि ऑप्टिमाइज़ेशन की हमारी प्रोसेस के बारे में यह जानकारी, कंपाइलर इंजीनियरिंग की जटिलताओं और फ़ायदों के बारे में अहम जानकारी देगी!

इसे लिखा है:

पढ़ना जारी रखें