Android रनटाइम (एआरटी) टीम ने कंपाइल करने में लगने वाले समय को 18% कम कर दिया है. इसके लिए, कंपाइल किए गए कोड या पीक मेमोरी रिग्रेशन से कोई समझौता नहीं किया गया है. यह सुधार, साल 2025 में शुरू की गई हमारी उस पहल का हिस्सा था जिसका मकसद कंपाइल के समय को कम करना था. हालांकि, इसके लिए मेमोरी के इस्तेमाल या कंपाइल किए गए कोड की क्वालिटी से समझौता नहीं किया गया.
एआरटी के लिए, कंपाइल के समय को ऑप्टिमाइज़ करना ज़रूरी है. उदाहरण के लिए, जस्ट-इन-टाइम (जेआईटी) कंपाइलेशन के दौरान, इसका सीधा असर ऐप्लिकेशन की परफ़ॉर्मेंस और डिवाइस की परफ़ॉर्मेंस पर पड़ता है. तेज़ी से कंपाइल होने की वजह से, ऑप्टिमाइज़ेशन शुरू होने में कम समय लगता है. इससे उपयोगकर्ताओं को बेहतर और ज़्यादा रिस्पॉन्सिव अनुभव मिलता है. इसके अलावा, जेआईटी और अहेड-ऑफ़-टाइम (एओटी), दोनों के लिए कंपाइल के समय में सुधार होने से, कंपाइलेशन प्रोसेस के दौरान कम संसाधनों का इस्तेमाल होता है. इससे बैटरी लाइफ़ और डिवाइस के तापमान को मैनेज करने में मदद मिलती है. खास तौर पर, कम सुविधाओं वाले डिवाइसों पर.
कंपाइल के समय को कम करने वाली कुछ सुविधाएं, जून 2025 में Android के रिलीज़ के साथ लॉन्च की गई हैं. बाकी सुविधाएं, साल के आखिर में Android के रिलीज़ के साथ उपलब्ध होंगी. इसके अलावा, Android 12 और इसके बाद के वर्शन इस्तेमाल करने वाले सभी लोगों को मेनलाइन अपडेट के ज़रिए ये सुधार मिलेंगे.
ऑप्टिमाइज़ करने वाले कंपाइलर को ऑप्टिमाइज़ करना
कंपाइलर को ऑप्टिमाइज़ करना हमेशा एक मुश्किल काम होता है. तेज़ स्पीड पाने के लिए, आपको कुछ न कुछ समझौता करना पड़ता है. हमने खुद के लिए एक बहुत ही साफ़ और मुश्किल लक्ष्य तय किया: कंपाइलर को तेज़ बनाना, लेकिन ऐसा करते समय मेमोरी रिग्रेशन को शामिल न करना. साथ ही, यह भी ध्यान रखना कि इससे जनरेट होने वाले कोड की क्वालिटी खराब न हो. अगर कंपाइलर तेज़ी से काम करता है, लेकिन ऐप्लिकेशन धीरे-धीरे चलते हैं, तो इसका मतलब है कि हम अपने मकसद में कामयाब नहीं हुए.
हमारा मकसद था कि हम अपना समय, गहराई से रिसर्च करने, जांच करने, और इन सख्त शर्तों को पूरा करने वाले बेहतर समाधान ढूंढने में लगाएं. आइए, इस बारे में ज़्यादा जानें कि हम सुधार के लिए ज़रूरी पहलुओं का पता लगाने के साथ-साथ, अलग-अलग समस्याओं के सही समाधान ढूंढने के लिए कैसे काम करते हैं.
काम के संभावित ऑप्टिमाइज़ेशन ढूंढना
किसी मेट्रिक को ऑप्टिमाइज़ करने से पहले, आपको उसे मेज़र करना होगा. ऐसा न करने पर, आपको कभी भी यह पता नहीं चल पाएगा कि आपने अपनी साइट को बेहतर बनाया है या नहीं. खुशकिस्मती से, कंपाइल का समय काफ़ी हद तक एक जैसा रहता है. हालांकि, इसके लिए आपको कुछ सावधानियां बरतनी होंगी. जैसे, बदलाव करने से पहले और बाद में मेज़र करने के लिए एक ही डिवाइस का इस्तेमाल करना. साथ ही, यह पक्का करना कि आपके डिवाइस का तापमान ज़्यादा न हो. इसके अलावा, हमारे पास डिटरमिनिस्टिक मेज़रमेंट भी हैं. जैसे, कंपाइलर के आंकड़े. इनसे हमें यह समझने में मदद मिलती है कि बैकग्राउंड में क्या हो रहा है.
इन सुधारों के लिए हम जो संसाधन खर्च कर रहे थे वह हमारा डेवलपमेंट टाइम था. इसलिए, हम जितनी जल्दी हो सके उतनी जल्दी बदलाव करना चाहते थे. इसका मतलब है कि हमने समाधानों के प्रोटोटाइप बनाने के लिए, कुछ चुनिंदा ऐप्लिकेशन (पहले पक्ष के ऐप्लिकेशन, तीसरे पक्ष के ऐप्लिकेशन, और Android ऑपरेटिंग सिस्टम) का इस्तेमाल किया. इसके बाद, हमने मैन्युअल और ऑटोमेटेड टेस्टिंग, दोनों तरीकों से बड़े पैमाने पर जांच करके यह पुष्टि की कि आखिरी बार लागू किया गया बदलाव सही था.
चुने गए APKs के सेट के साथ, हम स्थानीय तौर पर मैन्युअल कंपाइल को ट्रिगर करेंगे. साथ ही, कंपाइलेशन की प्रोफ़ाइल हासिल करेंगे और pprof का इस्तेमाल करके यह देखेंगे कि हम अपना समय कहां खर्च कर रहे हैं.
pprof में प्रोफ़ाइल के फ़्लेम ग्राफ़ का उदाहरण
pprof टूल बहुत असरदार है. इसकी मदद से, डेटा को स्लाइस, फ़िल्टर, और क्रम से लगाया जा सकता है. उदाहरण के लिए, यह देखा जा सकता है कि कंपाइलर के किन फ़ेज़ या तरीकों में सबसे ज़्यादा समय लग रहा है. हम pprof के बारे में ज़्यादा जानकारी नहीं देंगे. बस इतना जान लें कि अगर बार बड़ा है, तो इसका मतलब है कि कंपाइलेशन में ज़्यादा समय लगा.
इनमें से एक व्यू “बॉटम अप” है. इसमें यह देखा जा सकता है कि कौनसे तरीके सबसे ज़्यादा समय ले रहे हैं. नीचे दी गई इमेज में, हमें Kill नाम का एक तरीका दिख रहा है. यह कंपाइल के समय का 1% से ज़्यादा हिस्सा लेता है. ब्लॉग पोस्ट में, कुछ अन्य तरीकों के बारे में भी बताया जाएगा.
प्रोफ़ाइल का बॉटम अप व्यू
हमारे ऑप्टिमाइज़िंग कंपाइलर में, ग्लोबल वैल्यू नंबरिंग (जीवीएन) नाम का एक फ़ेज़ होता है. आपको यह चिंता करने की ज़रूरत नहीं है कि यह पूरी तरह से क्या करता है. हालांकि, काम की बात यह है कि इसमें `Kill` नाम का एक तरीका है, जो फ़िल्टर के हिसाब से कुछ नोड मिटा देगा. इसमें समय लगता है, क्योंकि इसमें सभी नोड को एक-एक करके जांचना पड़ता है. हमने देखा है कि कुछ मामलों में हमें पहले से पता होता है कि जांच गलत होगी. भले ही, उस समय हमारे पास कितने भी नोड मौजूद हों. इन मामलों में, हम इटरेशन को पूरी तरह से स्किप कर सकते हैं. इससे यह 1.023% से घटकर ~0.3% हो जाता है और GVN का रनटाइम ~15% बेहतर हो जाता है.
काम के ऑप्टिमाइज़ेशन लागू करना
हमने आपको बताया कि समय को कैसे मेज़र किया जाए और यह कैसे पता लगाया जाए कि समय कहां खर्च हो रहा है. हालांकि, यह सिर्फ़ शुरुआत है. अगला चरण यह है कि कंपाइल करने में लगने वाले समय को कैसे ऑप्टिमाइज़ किया जाए.
आम तौर पर, ऊपर दिए गए `Kill` जैसे मामले में, हम यह देखते हैं कि नोड को कैसे दोहराया जाए और उसे तेज़ी से कैसे किया जाए. उदाहरण के लिए, चीज़ों को एक साथ करना या एल्गोरिदम को बेहतर बनाना. हमने शुरुआत में यही तरीका अपनाया था. हालांकि, जब हमें कोई समाधान नहीं मिला, तो हमने सोचा कि “एक मिनट रुकें…” और देखा कि कुछ मामलों में, समाधान यह था कि हमें कुछ भी नहीं करना था! इस तरह के ऑप्टिमाइज़ेशन करते समय, छोटी-छोटी चीज़ों पर ध्यान देने की वजह से बड़ी चीज़ों को नज़रअंदाज़ करना आसान होता है.
अन्य मामलों में, हमने कई अलग-अलग तकनीकों का इस्तेमाल किया है. इनमें ये शामिल हैं:
- ह्यूरिस्टिक का इस्तेमाल करके यह तय करना कि कोई ऑप्टिमाइज़ेशन, काम के नतीजे देने में नाकाम रहेगा या नहीं. अगर ऐसा होता है, तो उसे छोड़ा जा सकता है
- कंप्यूट किए गए डेटा को कैश मेमोरी में सेव करने के लिए, अतिरिक्त डेटा स्ट्रक्चर का इस्तेमाल करना
- मौजूदा डेटा स्ट्रक्चर में बदलाव करना, ताकि स्पीड बूस्ट की सुविधा मिल सके
- कुछ मामलों में साइकल से बचने के लिए, नतीजों को लेज़ी तरीके से कंप्यूट किया जा रहा है
- सही एब्स्ट्रैक्शन का इस्तेमाल करें - गैर-ज़रूरी सुविधाएं, कोड को धीमा कर सकती हैं
- अक्सर इस्तेमाल किए जाने वाले पॉइंटर को कई बार लोड करने से बचें
हमें कैसे पता चलेगा कि ऑप्टिमाइज़ेशन की प्रोसेस को जारी रखना फ़ायदेमंद है या नहीं?
सबसे अच्छी बात यह है कि आपको ऐसा करने की ज़रूरत नहीं है. कभी-कभी ऐसा होता है कि किसी हिस्से को कंपाइल करने में ज़्यादा समय लगता है. इसके बाद, उसे बेहतर बनाने के लिए डेवलपमेंट में समय देने के बाद भी, आपको कोई समाधान नहीं मिलता. ऐसा हो सकता है कि कुछ करने की ज़रूरत न हो, इसे लागू करने में बहुत ज़्यादा समय लगे, इससे कोई दूसरी मेट्रिक काफ़ी कम हो जाए, कोड बेस की जटिलता बढ़ जाए वगैरह. इस ब्लॉग पोस्ट में आपको ऑप्टिमाइज़ेशन के जितने भी सफल उदाहरण दिख रहे हैं उनके अलावा, ऐसे अनगिनत उदाहरण भी हैं जो सफल नहीं हो पाए.
अगर आपकी स्थिति भी ऐसी ही है, तो कम से कम काम करके मेट्रिक को बेहतर बनाने का अनुमान लगाएं. इसका मतलब है कि:
- पहले से इकट्ठा की गई मेट्रिक या सिर्फ़ अनुमान के आधार पर
- जल्दी और रफ़ प्रोटोटाइप बनाकर अनुमान लगाना
- कोई समाधान लागू करें.
अपने समाधान की कमियों का अनुमान लगाना न भूलें. उदाहरण के लिए, अगर आपको अतिरिक्त डेटा स्ट्रक्चर पर भरोसा करना है, तो आपको कितनी मेमोरी इस्तेमाल करनी है?
समस्या को गहराई से समझना
देर न करते हुए, चलिए कुछ बदलावों के बारे में बात करते हैं.
हमने FindReferenceInfoOf नाम के तरीके को ऑप्टिमाइज़ करने के लिए बदलाव किया. इस तरीके से, किसी एंट्री को ढूंढने के लिए वेक्टर की लीनियर सर्च की जाती थी. हमने उस डेटा स्ट्रक्चर को अपडेट किया, ताकि उसे निर्देश के आईडी के हिसाब से इंडेक्स किया जा सके. इससे FindReferenceInfoOf, O(n) के बजाय O(1) हो जाएगा. साथ ही, हमने वेक्टर को पहले से ही तय कर दिया है, ताकि साइज़ बदलने की ज़रूरत न पड़े. हमने मेमोरी को थोड़ा बढ़ाया, क्योंकि हमें एक अतिरिक्त फ़ील्ड जोड़ना था. इसमें यह गिना जाता था कि हमने वेक्टर में कितनी एंट्री डाली हैं. हालांकि, यह एक छोटा सा समझौता था, क्योंकि पीक मेमोरी नहीं बढ़ी. इससे हमारे LoadStoreAnalysis फ़ेज़ की स्पीड 34 से 66% तक बढ़ गई. इससे कंपाइल करने के समय में ~0.5 से 1.8% तक की कमी आई.
हमारे पास HashSet का एक कस्टम वर्शन है, जिसका इस्तेमाल हम कई जगहों पर करते हैं. इस डेटा स्ट्रक्चर को बनाने में काफ़ी समय लग रहा था. हमें इसकी वजह पता चल गई है. कई साल पहले, इस डेटा स्ट्रक्चर का इस्तेमाल सिर्फ़ कुछ जगहों पर किया जाता था. ये ऐसी जगहें थीं जहां बहुत बड़े HashSet का इस्तेमाल किया जाता था. साथ ही, इसे HashSet के लिए ऑप्टिमाइज़ किया गया था. हालांकि, आजकल इसका इस्तेमाल उल्टी दिशा में किया जाता है. इसमें सिर्फ़ कुछ एंट्री होती हैं और यह कम समय के लिए होता है. इसका मतलब है कि हम इस बड़े 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 क्लास को स्टोरेज के टाइप के आधार पर टेंप्लेट किया गया था. इसमें uint32_t को पुराने BitVector के तौर पर हमेशा इस्तेमाल करने के बजाय, स्टोरेज के टाइप के आधार पर टेंप्लेट किया गया था. इससे कुछ कार्रवाइयां, 64-बिट वाले प्लैटफ़ॉर्म पर एक साथ दोगुनी बिट प्रोसेस कर सकती हैं. उदाहरण के लिए, Union(). Android OS को कंपाइल करते समय, जिन फ़ंक्शन पर असर पड़ा था उनके सैंपल में कुल मिलाकर 1% से ज़्यादा की कमी आई. ऐसा कई बदलावों के ज़रिए किया गया [1, 2, 3, 4, 5, 6]
अगर हम सभी ऑप्टिमाइज़ेशन के बारे में विस्तार से बात करें, तो हमें पूरा दिन लग जाएगा! अगर आपको कुछ और ऑप्टिमाइज़ेशन में दिलचस्पी है, तो हमारे लागू किए गए कुछ अन्य बदलाव देखें:
- कंपाइल करने में लगने वाले समय को करीब 0.6 से 1.6% तक कम करने के लिए, बुककीपिंग जोड़ें.
- अगर हो सके, तो साइकल से बचने के लिए, डेटा को लेज़ी तरीके से कंप्यूट करें.
- अपने कोड को रीफ़ैक्टर करें, ताकि प्रीकंप्यूटिंग के काम को स्किप किया जा सके. ऐसा तब करें, जब इसका इस्तेमाल न किया जा रहा हो.
- जब ऐलोकेटर को अन्य जगहों से आसानी से हासिल किया जा सकता हो, तब डिपेंडेंट लोड चेन से बचें.
- गैर-ज़रूरी काम से बचने के लिए, जांच जोड़ने का एक और उदाहरण.
- रजिस्टर ऐलोकेटर में रजिस्टर टाइप (कोर/एफ़पी) पर, बार-बार ब्रांचिंग करने से बचें.
- पक्का करें कि कुछ कलेक्शन, कंपाइल के समय शुरू किए गए हों. इसके लिए, Clang पर भरोसा न करें.
- कुछ लूप मिटाएं. रेंज लूप का इस्तेमाल करें, जिन्हें clang बेहतर तरीके से ऑप्टिमाइज़ कर सकता है. ऐसा इसलिए, क्योंकि लूप के खराब असर की वजह से, इसे कंटेनर के इंटरनल पॉइंटर को फिर से लोड करने की ज़रूरत नहीं होती. हर इनपुट के लिए, इनलाइन किए गए `InputAt(.)` के ज़रिए लूप में वर्चुअल फ़ंक्शन `HInstruction::GetInputRecords()` को कॉल करने से बचें.
- विज़िटर पैटर्न के लिए Accept() फ़ंक्शन का इस्तेमाल करने से बचें. इसके लिए, कंपाइलर ऑप्टिमाइज़ेशन का इस्तेमाल करें.
नतीजा
हमने एआरटी के कंपाइल के समय को कम करने के लिए काफ़ी मेहनत की है. इससे Android को ज़्यादा बेहतर और असरदार बनाने में मदद मिली है. साथ ही, बैटरी लाइफ़ और डिवाइस के तापमान को बेहतर बनाने में भी मदद मिली है. ऑप्टिमाइज़ेशन की पहचान करके और उन्हें लागू करके, हमने यह दिखाया है कि मेमोरी के इस्तेमाल या कोड की क्वालिटी से समझौता किए बिना, कंपाइल के समय में काफ़ी सुधार किया जा सकता है.
इस सफ़र में, हमने pprof जैसे टूल की मदद से प्रोफ़ाइलिंग की. साथ ही, हमने बार-बार कोशिश करने और कभी-कभी कम फ़ायदेमंद तरीकों को छोड़ने की इच्छा दिखाई. एआरटी टीम की कोशिशों से, कंपाइल के समय में काफ़ी कमी आई है. साथ ही, आने वाले समय में होने वाले सुधारों के लिए भी एक मज़बूत बुनियाद तैयार हुई है.
ये सभी सुधार, 2025 के आखिर में Android के लिए उपलब्ध कराए गए अपडेट में शामिल हैं. साथ ही, ये Android 12 और इसके बाद के वर्शन के लिए, मेनलाइन अपडेट के ज़रिए उपलब्ध हैं. हमें उम्मीद है कि ऑप्टिमाइज़ेशन की प्रोसेस के बारे में दी गई इस ज़्यादा जानकारी से, आपको कंपाइलर इंजीनियरिंग की जटिलताओं और फ़ायदों के बारे में अहम जानकारी मिली होगी!
-
प्रॉडक्ट से जुड़ी खबरेंAndroid के आने वाले वर्शन को टारगेट करने वाले ऐप्लिकेशन, WRITE_CONTACTS अनुमति का इस्तेमाल करके संपर्कों में सीधे तौर पर बदलाव नहीं कर पाएंगे.
Roxanna Aliabadi Walker • इसे पढ़ने में छह मिनट लगेंगे -
प्रॉडक्ट से जुड़ी खबरेंएजेंटिक डेवलपमेंट में तेज़ी आने के साथ-साथ, Android इंजीनियरिंग एक रोमांचक नए मुकाम पर पहुंच रही है: कोड पूरा करने में मदद करने से लेकर, आपके निर्देश पर एंड-टू-एंड काम करने वाले एजेंट तक. आपके पास अपनी पसंद के मुताबिक मॉडल और एजेंट चुनने और इस्तेमाल करने की पूरी सुविधा होती है.
Jason Buchel • तीन मिनट में पढ़ा जा सकता है -
प्रॉडक्ट से जुड़ी खबरेंआज हमने बताया कि हम “सिर्फ़ एक स्टोर नहीं” के अपने विज़न को अगले लेवल पर कैसे ले जा रहे हैं. 'आप' टैब से लेकर हमारे यूनिफ़ाइड गेमिंग प्लैटफ़ॉर्म तक, Google Play हर जगह उपयोगकर्ताओं की मदद कर रहा है. 'आप' टैब, हर महीने 25 करोड़ से ज़्यादा सक्रिय उपयोगकर्ताओं के लिए एक पर्सनल होम बेस है. वहीं, हमारा यूनिफ़ाइड गेमिंग प्लैटफ़ॉर्म, 70 करोड़ से ज़्यादा गेमर प्रोफ़ाइलों को जोड़ता है.
Paul Feng • तीन मिनट में पढ़ा जा सकता है
Android डेवलपमेंट से जुड़ी नई अहम जानकारी, हर हफ़्ते अपने इनबॉक्स में पाएं.