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

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

कॉन्करेंट कंपैक्टिंग गार्बेज कलेक्टर

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

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

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

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

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

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

क्लास के लेआउट के क्रम का विश्लेषण

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

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

  • डाइनैमिक सिंगल-इंप्लीमेंटेशन के तरीके की स्थिति अपडेट करना - क्लास लिंक करने के समय के आखिर में, जब vtable भर जाता है, तो ART, सुपर क्लास के vtable से एंट्री-बाय-एंट्री तुलना करता है.
  • कंपाइलर ऑप्टिमाइज़ेशन - कंपाइलर, किसी तरीके को लागू करने की एक ही जानकारी का फ़ायदा उठाएगा. अगर किसी A.foo तरीके में single-implementation फ़्लैग सेट है, तो कंपाइलर वर्चुअल कॉल को डायरेक्ट कॉल में बदल देगा. इसके बाद, वह डायरेक्ट कॉल को इनलाइन करने की कोशिश करेगा.
  • कंपाइल किए गए कोड को अमान्य करना - क्लास लिंक करने के समय के आखिर में भी ऐसा होता है. जब सिंगल-इंप्लीमेंटेशन की जानकारी अपडेट की जाती है, तब अगर पहले सिंगल-इंप्लीमेंटेशन वाली 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 में, methods ऐरे को छोड़कर सभी ऐरे हटा दिए गए हैं.

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

Android 7.0 रिलीज़ में इंटरप्रेटर की परफ़ॉर्मेंस में काफ़ी सुधार हुआ है. इसमें "mterp" को शामिल किया गया है. यह एक इंटरप्रेटर है, जिसमें असेंबली भाषा में लिखा गया फ़ेच/डिकोड/इंटरप्रेट करने का मुख्य तरीका शामिल है. Mterp को फ़ास्ट डेलविक इंटरप्रेटर के हिसाब से बनाया गया है. यह 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 कैश मेमोरी का दोबारा इस्तेमाल किया जा सकता है. संकलित कोड में, कुछ निर्देशों के लिए डेक्स कैश मेमोरी की ज़रूरत होती है. जैसे, स्टैटिक कॉल, स्ट्रिंग लोड या क्लास लोड.
  2. स्टैक मैप, सिर्फ़ मौजूदा DEX फ़ाइल में किसी तरीके के इंडेक्स को एन्कोड कर रहे हैं.

इन सीमाओं को ठीक करने के लिए, Android 8.0 में ये बदलाव किए गए हैं:

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

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

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

तेज़ नेटिव तरीके

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

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

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

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

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

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