זמן הריצה של Android (ART) שופר באופן משמעותי בגרסה Android 8.0. הרשימה הבאה מסכמת את השיפורים שיצרני מכשירים יכולים לצפות להם ב-ART.
Concurrent compacting garbage collector
כפי שהודע בכנס Google I/O, ART כולל אוסף חדש של אשפה (GC) דחוס ומקביל ב-Android 8.0. הכלי הזה מכווץ את ה-heap בכל פעם ש-GC מופעל, ובזמן שהאפליקציה פועלת, עם השהיה קצרה אחת בלבד לעיבוד שורשי השרשור. אלה היתרונות שלו:
- ה-GC תמיד מצמצם את ה-heap: גודלי ה-heap קטנים ב-32% בממוצע בהשוואה ל-Android 7.0.
- דחיסה מאפשרת הקצאת אובייקטים של מצביעים מקומיים לשרשור: ההקצאות מהירות ב-70% בהשוואה ל-Android 7.0.
- זמני ההשהיה קצרים ב-85% בבדיקת הביצועים H2 בהשוואה ל-GC ב-Android 7.0.
- השהיות לא משתנות יותר בהתאם לגודל הערימה. אפליקציות יכולות להשתמש בערימות גדולות בלי לדאוג לגבי תקלות.
- פרט הטמעה של GC – מחסומי קריאה:
- מחסומי קריאה הם כמות קטנה של עבודה שמתבצעת עבור כל קריאה של שדה אובייקט.
- האופטימיזציה שלהם מתבצעת בקומפיילר, אבל הם עלולים להאט חלק מתרחישי השימוש.
אופטימיזציות של לולאות
ART בגרסת Android 8.0 משתמש במגוון רחב של אופטימיזציות של לולאות:
- הסרת בדיקות של גבולות
- סטטי: הטווחים מוכחים כנמצאים בגבולות בזמן ההידור
- דינמי: בדיקות בזמן ריצה מוודאות שהלולאות נשארות בגבולות (אחרת מתבצעת אופטימיזציה הפוכה)
- הסרת משתני אינדוקציה
- הסרת אינדוקציה מתה
- החלפת אינדוקציה שמשמשת רק אחרי הלולאה בביטויים בצורה סגורה expressions
- הסרת קוד מת בתוך גוף הלולאה, הסרה של לולאות שלמות שהופכות לקוד מת
- הפחתת החוזק
- טרנספורמציות של לולאות: היפוך, החלפה, פיצול, פריסה, חד-מודולריות וכו'.
- SIMDization (נקראת גם וקטוריזציה)
האופטימיזציה של הלולאה נמצאת בשלב אופטימיזציה משלה בקומפיילר ART. רוב האופטימיזציות של לולאות דומות לאופטימיזציות ולפישוטים במקומות אחרים. בעיות מתעוררות עם אופטימיזציות מסוימות שכותבות מחדש את ה-CFG בצורה מורכבת יותר מהרגיל, כי רוב כלי ה-CFG (ראו nodes.h) מתמקדים בבניית CFG, ולא בכתיבה מחדש שלו.
ניתוח היררכיית הכיתות
ART ב-Android 8.0 משתמש ב-Class Hierarchy Analysis (CHA), אופטימיזציה של קומפיילר שמבטלת את הווירטואליזציה של קריאות וירטואליות לקריאות ישירות על סמך המידע שנוצר בניתוח היררכיות של מחלקות. שיחות וירטואליות יקרות כי הן מיושמות סביב חיפוש בטבלת פונקציות וירטואליות, והן דורשות כמה טעינות תלויות. בנוסף, אי אפשר להוסיף שיחות וירטואליות בתוך שורות.
סיכום של שיפורים קשורים:
- עדכון דינמי של הסטטוס של שיטת הטמעה יחידה – בסוף זמן הקישור של המחלקה, כש-vtable מאוכלס, ART מבצע השוואה של כל רשומה ב-vtable של מחלקת העל.
- אופטימיזציה של קומפיילר – הקומפיילר ינצל את המידע על הטמעה יחידה של שיטה. אם לשיטה A.foo מוגדר דגל של הטמעה יחידה, מהדר יבטל את הוירטואליזציה של השיחה הוירטואלית לשיחה ישירה, וינסה להטמיע את השיחה הישירה כתוצאה מכך.
- ביטול תוקף של קוד שעבר קומפילציה – גם בסוף זמן הקישור של המחלקה, כשמידע על הטמעה יחידה מתעדכן. אם לשיטה A.foo הייתה בעבר הטמעה יחידה, אבל הסטטוס הזה בוטל, צריך לבטל את התוקף של כל הקוד שעבר קומפילציה שמסתמך על ההנחה שלשיטה A.foo יש הטמעה יחידה.
- ביטול אופטימיזציה – עבור קוד שעבר קומפילציה בזמן אמת ונמצא במחסנית, תתבצע דה-אופטימיזציה כדי לאלץ את הקוד שעבר קומפילציה ולא תקף לעבור למצב של מתורגמן, כדי להבטיח את נכונותו. ייעשה שימוש במנגנון חדש של ביטול אופטימיזציה, שהוא שילוב של ביטול אופטימיזציה סינכרוני ואסינכרוני.
מטמון מוטמע בקובצי .oat
ART משתמשת עכשיו במטמון מוטבע ומבצעת אופטימיזציה של אתרי הקריאה שקיימים עבורם מספיק נתונים. התכונה 'מטמון מוטבע' מתעדת מידע נוסף על זמן הריצה בפרופילים, ומשתמשת בו כדי להוסיף אופטימיזציות דינמיות לקימפול מראש.
Dexlayout
Dexlayout היא ספריה שהוצגה ב-Android 8.0 כדי לנתח קובצי dex ולסדר אותם מחדש בהתאם לפרופיל. הכלי Dexlayout נועד להשתמש במידע של פרופילים בזמן ריצה כדי לסדר מחדש את החלקים של קובץ ה-DEX במהלך קומפילציה של תחזוקה במצב המתנה במכשיר. על ידי קיבוץ של חלקים בקובץ ה-dex שאליהם ניגשים לעיתים קרובות יחד, התוכניות יכולות לקבל דפוסי גישה טובים יותר לזיכרון ממיקום משופר, לחסוך בזיכרון RAM ולקצר את זמן ההפעלה.
מאחר שפרטי הפרופיל זמינים כרגע רק אחרי הפעלת האפליקציות, הכלי dexlayout משולב בהידור במכשיר של dex2oat במהלך תחזוקה במצב המתנה.
הסרת מטמון Dex
עד Android 7.0, לאובייקט DexCache היו ארבע מערכים גדולים, שהגודל שלהם היה יחסי למספר מסוים של רכיבים ב-DexFile, כלומר:
- מחרוזות (הפניה אחת לכל DexFile::StringId),
- types (הפניה אחת לכל DexFile::TypeId),
- methods (מצביע מקורי אחד לכל DexFile::MethodId),
- שדות (מצביע מקורי אחד לכל DexFile::FieldId).
המערכים האלה שימשו לאחזור מהיר של אובייקטים שפתרנו בעבר. ב-Android 8.0, כל המערכים הוסרו מלבד מערך השיטות.
ביצועי התרגום
הביצועים של המפרש שופרו באופן משמעותי בגרסה Android 7.0 עם ההשקה של mterp – מפרש עם מנגנון ליבה של אחזור/פענוח/פרשנות שנכתב בשפת Assembly. Mterp מבוסס על המודל של המהדר המהיר Dalvik, והוא תומך בארכיטקטורות arm, arm64, x86, x86_64, mips ו-mips64. במקרה של קוד חישובי, הביצועים של mterp ב-Art דומים בערך לביצועים של המהדר המהיר של Dalvik. עם זאת, במקרים מסוימים, המהירות יכולה להיות איטית משמעותית, ואפילו באופן דרמטי:
- הפעלת הביצועים.
- מניפולציה של מחרוזות ושימוש כבד בשיטות שמזוהות כ-intrinsics ב-Dalvik.
- שימוש גבוה יותר בזיכרון המחסנית.
ב-Android 8.0 הבעיות האלה נפתרו.
הוספת עוד תמונות וסרטונים בתוך הטקסט
החל מ-Android 6.0, ART יכול להטביע כל קריאה באותם קובצי dex, אבל יכול להטביע רק שיטות עלים מקובצי dex שונים. היו שתי סיבות למגבלה הזו:
- כשמבצעים הטמעה מקובץ dex אחר, צריך להשתמש במטמון dex של הקובץ האחר הזה. זאת בניגוד להטמעה מאותו קובץ dex, שבה אפשר פשוט להשתמש שוב במטמון dex של הקובץ שמבצע את הקריאה. ה-dex cache נדרש בקוד שעבר קומפילציה לכמה הוראות, כמו קריאות סטטיות, טעינת מחרוזות או טעינת מחלקות.
- מפות ה-stack מקודדות רק אינדקס של שיטה בקובץ ה-dex הנוכחי.
כדי לפתור את הבעיות האלה, ב-Android מגרסה 8.0:
- הסרת גישה למטמון dex מקוד שעבר קומפילציה (ראו גם את הקטע 'הסרת מטמון dex')
- הרחבת הקידוד של מפת הערימה.
שיפורים בסנכרון
צוות ART כוונן את נתיבי הקוד MonitorEnter/MonitorExit, והפחית את ההסתמכות שלנו על מחסומי זיכרון מסורתיים ב-ARMv8, והחליף אותם בהוראות חדשות יותר (acquire/release) איפה שאפשר.
שיטות מובנות מהירות יותר
אפשר לבצע קריאות מהירות יותר ל-Java Native Interface (JNI) באמצעות ההערות @FastNative ו-@CriticalNative. האופטימיזציות המובנות האלה של זמן הריצה של ART מאיצות את המעברים של JNI ומחליפות את הסימון !bang JNI שכבר לא בשימוש. להערות אין השפעה על שיטות שאינן מקוריות, והן זמינות רק לקוד של שפת Java בפלטפורמה ב-bootclasspath (אין עדכונים בחנות Play).
ההערה @FastNative תומכת בשיטות לא סטטיות. משתמשים בערך הזה אם שיטה ניגשת אל jobject כפרמטר או כערך החזרה.
ההערה @CriticalNative מאפשרת להריץ שיטות מותאמות אפילו מהר יותר, עם ההגבלות הבאות:
-
השיטות צריכות להיות סטטיות – ללא אובייקטים לפרמטרים, לערכים מוחזרים או ל-
thisמרומז. - רק סוגים פרימיטיביים מועברים לשיטה המקורית.
-
השיטה המקורית לא משתמשת בפרמטרים
JNIEnvו-jclassבהגדרת הפונקציה שלה. -
השיטה צריכה להיות רשומה ב-
RegisterNativesבמקום להסתמך על קישור JNI דינמי.
@FastNative יכול לשפר את הביצועים של שיטות מותאמות עד פי 3, ו-@CriticalNative עד פי 5. לדוגמה, מעבר JNI שנמדד במכשיר Nexus 6P:
| הפעלה של Java Native Interface (JNI) | זמן הביצוע (בננו-שניות) |
|---|---|
| Regular JNI | 115 |
| !bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |