השיפורים ב-Android Runtime (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 משתמש במגוון רחב של אופטימיזציות של לולאות:
- הסרת בדיקות של גבולות
- סטטי: הטווחים מוכחים כנמצאים בגבולות בזמן ההידור
- דינמי: בדיקות בזמן ריצה מוודאות שהלולאות נשארות בגבולות (אחרת מתבצעת אופטימיזציה הפוכה)
- הסרת משתני אינדוקציה
- הסרת אינדוקציה מתה
- החלפת אינדוקציה שמשמשת רק אחרי הלולאה בביטויים בצורה סגורה
- הסרת קוד מת בתוך גוף הלולאה, הסרה של לולאות שלמות שהופכות למתות
- הפחתת העוצמה
- טרנספורמציות של לולאות: היפוך, החלפה, פיצול, פריסה, חד-מודולריות וכו'.
- 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 היו 4 מערכים גדולים, שהגודל שלהם היה יחסי למספר רכיבים מסוימים ב-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) איפה שאפשר.
שיטות מובנות מהירות יותר
אפשר להשתמש בהערות @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 |