סביבת זמן הריצה של Android (ART) שופר באופן משמעותי בגרסה 8.0 של Android. ברשימה הבאה מפורטים השיפורים שמייצגים יצרני מכשירים ב-ART.
Concurrent compacting garbage collector
כפי שהודענו ב-Google I/O, ב-ART יש אמצעי איסוף אשפה (GC) חדש ומצטבר במקביל ב-Android 8.0. האוסף הזה מצמצם את האשפה בכל פעם ש-GC פועל ובזמן שהאפליקציה פועלת, עם הפסקה קצרה אחת בלבד לעיבוד של שורשי השרשור. אלו היתרונות של הכלי:
- GC תמיד מצמצם את אשכול הזיכרון: גודל האשכולות קטן ב-32% בממוצע בהשוואה ל-Android 7.0.
- דחיסה מאפשרת הקצאה של אובייקטים של נקודות מפנה לעלייה מקומית לשרשור: ההקצאות מהירות ב-70% יותר מאשר ב-Android 7.0.
- זמני ההשהיה של מדד הביצועים H2 קצרים ב-85% בהשוואה ל-GC של Android 7.0.
- זמני ההשהיה לא משתנים יותר בהתאם לגודל האוסף. אפליקציות אמורות להיות מסוגלות להשתמש באוספים גדולים בלי לדאוג לתנודות חדות.
- פרטי הטמעת GC – חסמי קריאה:
- חסמי קריאה הם כמות קטנה של עבודה שמתבצעת בכל קריאה של שדה אובייקט.
- המערכת מבצעת אופטימיזציה שלהם במהלך הידור הקוד, אבל הם עלולים להאט תרחישי שימוש מסוימים.
אופטימיזציות של לולאות
ב-ART בגרסת Android 8.0 נעשה שימוש במגוון רחב של אופטימיזציות של לולאות:
- חיסכון בבדיקות גבולות
- סטטי: הטווח נמצא בגבולות בזמן הידור
- דינמי: בדיקות בזמן ריצה מוודאות שהלולאות נשארות בגבולות (deopt אחרת)
- חיסול משתני אינדוקציה
- הסרת אינדוקציה של נתונים לא תקינים
- החלפת אינדוקציה שמשמשת רק אחרי הלולאה בביטויים בפורמט סגור
- חיסול קוד לא פעיל בתוך גוף הלולאה, הסרה של לולאות שלמות שהפכו ללא פעילות
- הפחתת העוצמה
- טרנספורמציות של לולאות: היפוך, החלפה, פיצול, ביטול גלגול, טרנספורמציה יונימודולרית וכו'.
- SIMDization (נקראת גם vectorization)
אופטימיזטור הלולאות נמצא במעבר אופטימיזציה משלו במהדר של ART. רוב האופטימיזציות של לולאות דומות לאופטימיזציות ולפישוט של קוד במקומות אחרים. יש אתגרים באופטימיזציות מסוימות שמעבירות מחדש את ה-CFG בצורה מורכבת יותר מהרגיל, כי רוב הכלים ל-CFG (ראו nodes.h) מתמקדים ביצירת CFG, ולא בכתיבה מחדש של CFG.
ניתוח היררכיית הכיתה
ב-ART ב-Android 8.0 נעשה שימוש בניתוח היררכיית הכיתות (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),
- סוגים (הפניה אחת לכל DexFile::TypeId),
- שיטות (מצביע מקורי אחד לכל DexFile::MethodId),
- שדות (מצביע מקורי אחד לכל DexFile::FieldId).
המערכי המשנה האלה שימשו לאחזור מהיר של אובייקטים שכבר תיקננו. ב-Android 8.0, כל המערכים הוסרו מלבד מערך השיטות.
ביצועי המתורגמן
הביצועים של המתורגם השתפרו באופן משמעותי במהדורה של Android 7.0 עם ההשקה של mterp – מתרגם עם מנגנון ליבה של אחזור/פענוח/תרגום שנכתב בשפת האסמבלי. Mterp מבוסס על המתורגם המהיר של Dalvik, ותומך ב-arm, arm64, x86, x86_64, mips ו-mips64. בקוד מתמטי, ה-mterp של Art דומה בקירוב למפרש המהיר של Dalvik. עם זאת, בחלק מהמצבים היא יכולה להיות איטית יותר באופן משמעותי, ואפילו באופן דרסטי:
- ביצועי ההפעלה.
- מניפולציה של מחרוזות ומשתמשים כבדים אחרים בשיטות שזוהו כפנימיות ב-Dalvik.
- שימוש גבוה יותר בזיכרון הערימה.
הבעיות האלה נפתרות ב-Android 8.0.
עוד הטמעה בקוד
החל מגרסה 6.0 של Android, ה-ART יכול להטמיע בקוד כל קריאה בתוך אותם קובצי dex, אבל יכול היה להטמיע בקוד רק שיטות עלים מקובצי dex שונים. היו שתי סיבות למגבלה הזו:
- כדי להטמיע בקוד קובץ dex אחר, צריך להשתמש במטמון ה-dex של אותו קובץ dex, בניגוד להטמעה בקוד של אותו קובץ dex, שבה אפשר פשוט לעשות שימוש חוזר במטמון ה-dex של מבצע הקריאה החוזרת. מטמון ה-dex נדרש בקוד שעבר הידור לכמה הוראות, כמו קריאות סטטיות, טעינת מחרוזת או טעינת כיתה.
- מפות הערימה מקודדות רק מדד של שיטות בקובץ ה-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 Store).
ההערה @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 |