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

Android 8.0 में, Android रनटाइम (एआरटी) को बेहतर बनाया गया है. यहां दी गई सूची में, एआरटी में किए गए उन सुधारों के बारे में बताया गया है जो डिवाइस बनाने वाली कंपनियों को मिल सकते हैं.

एक साथ काम करने वाला कंपैक्टिंग गारबेज कलेक्टर

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

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

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

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

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

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

क्लास हिरारकी ऐनलिसिस

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

यहां, इससे जुड़े सुधारों की खास जानकारी दी गई है:

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

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

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

Dexlayout

Dexlayout, Android 8.0 में जोड़ी गई एक लाइब्रेरी है. इसका इस्तेमाल, dex फ़ाइलों का विश्लेषण करने और उन्हें प्रोफ़ाइल के हिसाब से फिर से क्रम में लगाने के लिए किया जाता है. Dexlayout का मकसद, डिवाइस पर आइडल मेंटेनेंस कंपाइलेशन के दौरान, dex फ़ाइल के सेक्शन को फिर से क्रम में लगाने के लिए, रनटाइम प्रोफ़ाइलिंग की जानकारी का इस्तेमाल करना है. 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 के बाद से, एआरटी, एक ही dex फ़ाइल में मौजूद किसी भी कॉल को इनलाइन कर सकता है. हालांकि, यह सिर्फ़ अलग-अलग dex फ़ाइलों से लीफ़ मेथड को इनलाइन कर सकता है. इस पाबंदी की दो वजहें थीं:

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

इन पाबंदियों को दूर करने के लिए, Android 8.0 में ये बदलाव किए गए हैं:

  1. कंपाइल किए गए कोड से dex कैश ऐक्सेस हटाया गया है (यह भी देखें "Dex कैश हटाना" सेक्शन)
  2. स्टैक मैप एनकोडिंग को बढ़ाया गया है.

सिंक्रनाइज़ेशन में सुधार

एआरटी टीम ने MonitorEnter/MonitorExit कोड पाथ को ट्यून किया है. साथ ही, ARMv8 पर पारंपरिक मेमोरी बैरियर पर हमारी निर्भरता कम की है. इसके लिए, जहां मुमकिन हो वहां उन्हें नए (acquire/release) निर्देशों से बदला गया है.

तेज़ नेटिव मेथड

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

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

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

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

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

Java Native Interface (JNI) इन्वोकेशन लागू करने का समय (नैनोसेकंड में)
रेगुलर JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25