סביבת זמן הריצה של Android (ART) שופרה באופן משמעותי בגרסה Android 8.0. הרשימה הבאה מסכמת את השיפורים שיצרני מכשירים יכולים לצפות להם ב-ART.
מנגנון garbage collection מקביל לדחיסה
כפי שהודענו ב-Google I/O, ART כולל אוסף חדש של אשפה (GC) דחוסה במקביל ב-Android 8.0. האיסוף הזה דוחס את ה-heap בכל פעם ש-GC מופעל, ובזמן שהאפליקציה פועלת, עם השהיה קצרה אחת בלבד לעיבוד שורשי השרשור. אלה היתרונות שלו:
- איסוף האשפה תמיד דוחס את ה-heap: גודלי ה-heap קטנים ב-32% בממוצע בהשוואה ל-Android 7.0.
- דחיסה מאפשרת הקצאת אובייקטים של מצביעים מקומיים לשרשור: ההקצאות מהירות ב-70% בהשוואה ל-Android 7.0.
- הפסקות ההפעלה קצרות ב-85% בבדיקת הביצועים H2 בהשוואה ל-GC ב-Android 7.0.
- זמני ההשהיה לא משתנים יותר בהתאם לגודל הערימה. אפליקציות יכולות להשתמש בערימות גדולות בלי לדאוג לגבי תקלות.
- פרט הטמעה של GC – מחסומי קריאה:
- מחסומי קריאה הם כמות קטנה של עבודה שמתבצעת עבור כל קריאה של שדה אובייקט.
- האופטימיזציה שלהם מתבצעת בקומפיילר, אבל הם עלולים להאט חלק מתרחישי השימוש.
אופטימיזציות של לולאות
ART בגרסת Android 8.0 משתמש במגוון רחב של אופטימיזציות של לולאות:
- הסרת בדיקות של גבולות
- סטטי: הטווחים מוכחים כנמצאים בגבולות בזמן ההידור
- דינמי: בדיקות בזמן ריצה מוודאות שהלולאות נשארות בגבולות (אחרת מתבצעת אופטימיזציה הפוכה)
- הסרת משתני אינדוקציה
- הסרת אינדוקציה לא פעילה
- החלפת אינדוקציה שמשמשת רק אחרי הלולאה בביטויים בצורה סגורה
- הסרת קוד מת בתוך גוף הלולאה, הסרת לולאות שלמות שהופכות לקוד מת
- הפחתת העוצמה
- טרנספורמציות של לולאות: היפוך, החלפה, פיצול, פריסה, חד-מודולריות וכו'.
- SIMDization (נקרא גם וקטוריזציה)
הכלי לאופטימיזציה של לולאות נמצא בשלב אופטימיזציה משלו בקומפיילר ART. רוב האופטימיזציות של לולאות דומות לאופטימיזציות ולפישוטים במקומות אחרים. בעיות מתעוררות עם אופטימיזציות מסוימות שכותבות מחדש את ה-CFG בצורה מורכבת יותר מהרגיל, כי רוב כלי ה-CFG (ראו nodes.h) מתמקדים בבניית CFG, ולא בכתיבה מחדש שלו.
ניתוח היררכיית הכיתות
ART ב-Android 8.0 משתמש ב-Class Hierarchy Analysis (CHA), אופטימיזציה של קומפיילר שמבטלת את הווירטואליזציה של שיחות וירטואליות לשיחות ישירות על סמך המידע שנוצר בניתוח היררכיות של מחלקות. שיחות וירטואליות הן יקרות כי הן מיושמות סביב חיפוש ב-vtable, והן דורשות כמה טעינות תלויות. בנוסף, אי אפשר להוסיף שיחות וירטואליות בתוך הטקסט.
הנה סיכום של שיפורים קשורים:
- עדכון דינמי של הסטטוס של שיטת הטמעה יחידה – בסוף זמן הקישור של המחלקה, כש-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 (one reference per 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) איפה שאפשר.
שיטות מובנות מהירות יותר
אפשר להשתמש בהערות @FastNative ו-@CriticalNative כדי לבצע קריאות מהירות יותר ל-Java Native Interface (JNI). האופטימיזציות המובנות האלה של זמן הריצה של ART מאיצות את המעברים של JNI ומחליפות את הסימון !bang JNI שכבר לא בשימוש. להערות אין השפעה על שיטות שאינן מקוריות, והן זמינות רק לקוד בשפת Java של הפלטפורמה ב-bootclasspath (ללא עדכונים בחנות Play).
ההערה @FastNative תומכת בשיטות לא סטטיות. משתמשים בערך הזה אם method ניגשת ל-jobject כפרמטר או כערך החזרה.
ההערה @CriticalNative מאפשרת להריץ שיטות מותאמות אפילו מהר יותר, עם ההגבלות הבאות:
-
השיטות צריכות להיות סטטיות – ללא אובייקטים לפרמטרים, לערכים מוחזרים או ל-
thisמרומז. - רק סוגים פרימיטיביים מועברים לשיטה המקורית.
-
השיטה המקורית לא משתמשת בפרמטרים
JNIEnvו-jclassבהגדרת הפונקציה שלה. -
השיטה צריכה להיות רשומה ב-
RegisterNativesבמקום להסתמך על קישור JNI דינמי.
@FastNative יכול לשפר את הביצועים של שיטות מותאמות עד פי 3, ו-@CriticalNative עד פי 5. לדוגמה, מעבר JNI שנמדד במכשיר Nexus 6P:
| הפעלה של Java Native Interface (JNI) | זמן ביצוע (בננו-שניות) |
|---|---|
| JNI רגיל | 115 |
| !bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |