Android 8.0 में ART के लिए किए गए सुधार

Android 8.0 रिलीज़ में, Android रनटाइम (ART) को काफ़ी बेहतर बनाया गया है. यहां दी गई सूची में, डिवाइस बनाने वाली कंपनियों को ART में मिलने वाले बेहतर अनुभव के बारे में बताया गया है.

कॉम्पैक्ट करने वाला गारबेज कलेक्टर

Google I/O में यह एलान किया गया था कि Android 8.0 में ART, एक साथ काम करने वाला नया कॉम्पैक्टिंग गै़रबेज कलेक्टर (GC) पेश करेगा. यह कलेक्टर, हर बार जीसी के चलने पर और ऐप्लिकेशन के चलने के दौरान, हेप को कॉम्पैक्ट करता है. हालांकि, थ्रेड रूट को प्रोसेस करने के लिए, इसमें सिर्फ़ एक छोटा सा विराम लिया जाता है. इसके फ़ायदे यहां दिए गए हैं:

  • जीसी हमेशा हीप को कॉम्पैक्ट करता है: Android 7.0 की तुलना में, हेप का साइज़ औसतन 32% कम होता है.
  • कॉम्पैक्शन की सुविधा से, थ्रेड लोकल बंप पॉइंटर ऑब्जेक्ट को असाइन करने की सुविधा चालू होती है: असाइनमेंट, Android 7.0 के मुकाबले 70% तेज़ होते हैं.
  • Android 7.0 GC की तुलना में, H2 बेंचमार्क के लिए रोकने का समय 85% कम है.
  • अब हेप के साइज़ के हिसाब से, ऐप्लिकेशन के रुकने का समय नहीं तय होता. ऐप्लिकेशन, हेप के बड़े साइज़ का इस्तेमाल, बिना किसी रुकावट के कर सकते हैं.
  • जीसी लागू करने की जानकारी - रीड बैरियर:
    • रीड बैरियर, हर ऑब्जेक्ट फ़ील्ड को पढ़ने के लिए किया जाने वाला छोटा सा काम होता है.
    • इन्हें कंपाइलर में ऑप्टिमाइज़ किया जाता है, लेकिन कुछ इस्तेमाल के उदाहरणों में ये धीमे हो सकते हैं.

लूप ऑप्टिमाइज़ेशन

Android 8.0 रिलीज़ में, ART कई तरह के लूप ऑप्टिमाइज़ेशन का इस्तेमाल करता है:

  • सीमाओं की जांच से जुड़ी गड़बड़ियां ठीक करना
    • स्टैटिक: कंपाइल करने के समय, रेंज की वैल्यू तय सीमा के अंदर होती हैं
    • डाइनैमिक: रन-टाइम टेस्ट यह पक्का करते हैं कि लूप तय सीमाओं के अंदर रहें (अन्यथा, उन्हें हटाएं)
  • इंडक्शन वैरिएबल को हटाना
    • डेड इंडक्शन हटाएं
    • सिर्फ़ लूप के बाद इस्तेमाल किए जाने वाले इंडक्शन को क्लोज़्ड-फ़ॉर्म एक्सप्रेशन से बदलें
  • लूप-बॉडी में मौजूद डेड कोड को हटाना, ऐसे पूरे लूप को हटाना जो काम नहीं करते
  • स्ट्रेंथ में कमी
  • लूप ट्रांसफ़ॉर्मेशन: रिवर्सल, इंटरचेंजिंग, स्प्लिटिंग, अनरोलिंग, यूनीमॉड्यूलर वगैरह.
  • SIMDization (इसे वेक्टराइज़ेशन भी कहा जाता है)

लूप ऑप्टिमाइज़र, ART कंपाइलर में अपने ऑप्टिमाइज़ेशन पास में मौजूद होता है. ज़्यादातर लूप ऑप्टिमाइज़ेशन, अन्य जगहों पर ऑप्टिमाइज़ेशन और आसान बनाने के तरीके से मिलते-जुलते होते हैं. कुछ ऑप्टिमाइज़ेशन में समस्याएं आती हैं, जो सीएफ़जी को सामान्य से ज़्यादा बारीक तरीके से फिर से लिखते हैं. ऐसा इसलिए होता है, क्योंकि ज़्यादातर सीएफ़जी यूटिलिटी (nodes.h देखें), सीएफ़जी बनाने पर फ़ोकस करती हैं, न कि उसे फिर से लिखने पर.

क्लास के लेआउट का क्रम

Android 8.0 में ART, क्लास हैरारकी विश्लेषण (CHA) का इस्तेमाल करता है. यह एक कंपाइलर ऑप्टिमाइज़ेशन है, जो क्लास हैरारकी का विश्लेषण करके जनरेट की गई जानकारी के आधार पर, वर्चुअल कॉल को डायरेक्ट कॉल में बदल देता है. वर्चुअल कॉल की लागत ज़्यादा होती है, क्योंकि इन्हें vtable लुकअप के हिसाब से लागू किया जाता है. साथ ही, इनमें कुछ डिपेंडेंट लोड लगते हैं. साथ ही, वर्चुअल कॉल को इनलाइन नहीं किया जा सकता.

इससे जुड़े बेहतर अनुभवों की खास जानकारी यहां दी गई है:

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

.oat फ़ाइलों में इनलाइन कैश

ART अब इनलाइन कैश का इस्तेमाल करता है और उन कॉल साइटों को ऑप्टिमाइज़ करता है जिनके लिए ज़रूरत के मुताबिक डेटा मौजूद है. इनलाइन कैश मेमोरी की सुविधा, प्रोफ़ाइलों में रनटाइम की अतिरिक्त जानकारी रिकॉर्ड करती है और इसका इस्तेमाल, पहले से कंपाइल किए गए कॉन्टेंट में डाइनैमिक ऑप्टिमाइज़ेशन जोड़ने के लिए करती है.

Dexlayout

Dexlayout, Android 8.0 में लॉन्च की गई एक लाइब्रेरी है. इसका इस्तेमाल, dex फ़ाइलों का विश्लेषण करने और उन्हें किसी प्रोफ़ाइल के हिसाब से फिर से व्यवस्थित करने के लिए किया जाता है. Dexlayout का मकसद, डिवाइस पर रखरखाव के लिए, डिफ़ॉल्ट रूप से बंद रहने के दौरान, डीईएक्स फ़ाइल के सेक्शन का क्रम फिर से तय करने के लिए, रनटाइम प्रोफ़ाइलिंग की जानकारी का इस्तेमाल करना है. अक्सर एक साथ ऐक्सेस की जाने वाली dex फ़ाइल के हिस्सों को एक साथ ग्रुप करने से, प्रोग्राम में बेहतर मेमोरी ऐक्सेस पैटर्न हो सकते हैं. इससे, बेहतर लोकलिटी, रैम की बचत, और स्टार्ट अप में लगने वाले समय में कमी आती है.

फ़िलहाल, प्रोफ़ाइल की जानकारी सिर्फ़ ऐप्लिकेशन के चलने के बाद उपलब्ध होती है. इसलिए, डिवाइस के रखरखाव के दौरान, dexlayout को dex2oat के डिवाइस पर होने वाले कंपाइलेशन में इंटिग्रेट किया जाता है.

Dex की कैश मेमोरी हटाना

Android 7.0 तक, DexCache ऑब्जेक्ट के पास चार बड़े कलेक्शन थे. ये कलेक्शन, DexFile में मौजूद कुछ एलिमेंट की संख्या के हिसाब से होते थे. जैसे:

  • स्ट्रिंग (हर DexFile::StringId के लिए एक रेफ़रंस),
  • टाइप (हर DexFile::TypeId के लिए एक रेफ़रंस),
  • तरीकों (हर DexFile::MethodId के लिए एक नेटिव पॉइंटर),
  • फ़ील्ड (हर DexFile::FieldId के लिए एक नेटिव पॉइंटर).

इन ऐरे का इस्तेमाल, उन ऑब्जेक्ट को तुरंत वापस पाने के लिए किया जाता था जिन्हें हमने पहले ठीक कर दिया था. Android 8.0 में, तरीकों के कलेक्शन को छोड़कर सभी कलेक्शन हटा दिए गए हैं.

अनुवादक की परफ़ॉर्मेंस

Android 7.0 रिलीज़ में, "mterp" के ज़रिए इंटरप्रेटर की परफ़ॉर्मेंस काफ़ी बेहतर हुई है. यह एक ऐसा इंटरप्रेटर है जिसमें असेंबली भाषा में लिखा गया कोर फ़ेच/डिकोड/इंटरप्रेट मैकेनिज्म है. Mterp को फ़ास्ट Dalvik इंटरप्रेटर के हिसाब से बनाया गया है. यह arm, arm64, x86, x86_64, mips, और mips64 के साथ काम करता है. कंप्यूटेशनल कोड के लिए, Art का mterp, Dalvik के फ़ास्ट इंटरप्रेटर के बराबर है. हालांकि, कुछ मामलों में यह बहुत धीमा हो सकता है:

  1. परफ़ॉर्मेंस को लागू करें.
  2. स्ट्रिंग में बदलाव करना और Dalvik में, इंट्रिन्सिक के तौर पर पहचाने गए तरीकों का ज़्यादा इस्तेमाल करना.
  3. स्टैक मेमोरी का ज़्यादा इस्तेमाल.

Android 8.0 में इन समस्याओं को ठीक किया गया है.

ज़्यादा इनलाइन

Android 6.0 के बाद, ART एक ही dex फ़ाइल में किसी भी कॉल को इनलाइन कर सकता है. हालांकि, वह अलग-अलग dex फ़ाइलों से सिर्फ़ लीफ़ मेथड को इनलाइन कर सकता है. इस पाबंदी की दो वजहें थीं:

  1. किसी दूसरी dex फ़ाइल से इनलाइन करने के लिए, उस दूसरी dex फ़ाइल के dex कैश का इस्तेमाल करना ज़रूरी है. वहीं, एक ही dex फ़ाइल को इनलाइन करने पर, कॉलर के dex कैश का फिर से इस्तेमाल किया जा सकता है. कंपाइल किए गए कोड में, कुछ निर्देशों के लिए dex कैश की ज़रूरत होती है. जैसे, स्टैटिक कॉल, स्ट्रिंग लोड या क्लास लोड.
  2. स्टैक मैप, मौजूदा dex फ़ाइल में सिर्फ़ एक मेथड इंडेक्स को कोड में बदल रहे हैं.

इन सीमाओं को दूर करने के लिए, Android 8.0:

  1. कंपाइल किए गए कोड से dex कैश ऐक्सेस हटाता है. "Dex कैश हटाना" सेक्शन भी देखें
  2. स्टैक मैप को कोड में बदलने की सुविधा को बेहतर बनाता है.

सिंक करने की सुविधा में सुधार

ART टीम ने MonitorEnter/MonitorExit कोड पाथ को ट्यून किया है. साथ ही, ARMv8 पर पारंपरिक मेमोरी बैरियर पर हमारी निर्भरता को कम किया है. जहां भी संभव हो, वहां उन्हें नए (अधिग्रहण/रिलीज़) निर्देशों से बदल दिया है.

तेज़ी से काम करने वाले नेटिव तरीके

@FastNative और @CriticalNative एनोटेशन का इस्तेमाल करके, Java नेटिव इंटरफ़ेस (JNI) को तेज़ी से नेटिव कॉल किए जा सकते हैं. ART रनटाइम में पहले से मौजूद ये ऑप्टिमाइज़ेशन, JNI ट्रांज़िशन को तेज़ करते हैं. साथ ही, अब इस्तेमाल नहीं किए जाने वाले !bang JNI नोटेशन की जगह ले लेते हैं. एनोटेशन का असर, नॉन-नेटिव तरीकों पर नहीं पड़ता. ये सिर्फ़ bootclasspath पर, प्लैटफ़ॉर्म के Java Language कोड के लिए उपलब्ध हैं. इनसे Play Store के अपडेट पर कोई असर नहीं पड़ता.

@FastNative एनोटेशन, नॉन-स्टैटिक तरीकों के साथ काम करता है. इसका इस्तेमाल तब करें, जब कोई तरीका jobject को पैरामीटर या रिटर्न वैल्यू के तौर पर ऐक्सेस करता हो.

@CriticalNative एनोटेशन, नेटिव तरीकों को चलाने का एक और तेज़ तरीका उपलब्ध कराता है. हालांकि, इसके लिए ये पाबंदियां लागू होती हैं:

  • तरीके स्टैटिक होने चाहिए—पैरामीटर, रिटर्न वैल्यू या किसी के लिए कोई ऑब्जेक्ट नहीं होना चाहिए this.
  • नेटिव तरीके में सिर्फ़ प्राइमटिव टाइप इस्तेमाल किए जा सकते हैं.
  • नेटिव तरीका, फ़ंक्शन की परिभाषा में JNIEnv और jclass पैरामीटर का इस्तेमाल नहीं करता.
  • डाइनैमिक JNI लिंकिंग पर भरोसा करने के बजाय, इस तरीके को RegisterNatives के साथ रजिस्टर करना ज़रूरी है.

@FastNative से नेटिव तरीके की परफ़ॉर्मेंस तीन गुना तक और @CriticalNative से पांच गुना तक बेहतर हो सकती है. उदाहरण के लिए, Nexus 6P डिवाइस पर मेज़र किया गया JNI ट्रांज़िशन:

Java नेटिव इंटरफ़ेस (JNI) को कॉल करना लागू करने में लगने वाला समय (नैनोसेकंड में)
रेगुलर JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25