שיפורים ב-ART ב-Android 8.0

זמן הריצה של Android‏ (ART) שופר באופן משמעותי בגרסה Android 8.0. הרשימה הבאה מסכמת את השיפורים שיצרני מכשירים יכולים לצפות להם ב-ART.

מנגנון garbage collection מקביל לדחיסה

כפי שהודענו ב-Google I/O, ‏ ART כולל אוסף חדש של אשפה דחוסה בו-זמנית (GC) ב-Android 8.0. האיסוף הזה מצמצם את ה-heap בכל פעם ש-GC מופעל, ובזמן שהאפליקציה פועלת, עם השהיה קצרה אחת בלבד לעיבוד שורשי השרשור. אלה היתרונות של התכונה:

  • ה-GC תמיד מצמצם את ה-heap: גודלי ה-heap קטנים ב-32% בממוצע בהשוואה ל-Android 7.0.
  • דחיסה מאפשרת הקצאת אובייקטים של מצביעים מקומיים לשרשור: ההקצאות מהירות ב-70% בהשוואה ל-Android 7.0.
  • הפסקות הפעולה ב-H2 benchmark קצרות ב-85% בהשוואה ל-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. עם זאת, במקרים מסוימים יכול להיות שהמהירות תהיה איטית משמעותית, ואפילו באופן דרמטי:

  1. הפעלת הביצועים.
  2. מניפולציה של מחרוזות ושימוש אינטנסיבי בשיטות שמזוהות כ-intrinsics ב-Dalvik.
  3. שימוש גבוה יותר בזיכרון המחסנית.

הבעיות האלה נפתרו ב-Android 8.0.

הוספת עוד תמונות וסרטונים בתוך הטקסט

החל מ-Android 6.0, ‏ ART יכול להטביע כל קריאה בתוך אותם קובצי dex, אבל יכול להטביע רק שיטות עלים מקובצי dex שונים. היו שתי סיבות למגבלה הזו:

  1. כשמבצעים inline מקובץ dex אחר, צריך להשתמש במטמון dex של הקובץ האחר הזה. זאת בניגוד לביצוע inline מאותו קובץ dex, שבו אפשר פשוט להשתמש מחדש במטמון dex של הקובץ שמבצע את הקריאה. ה-dex cache נדרש בקוד שעבר קומפילציה לכמה הוראות, כמו קריאות סטטיות, טעינת מחרוזות או טעינת מחלקות.
  2. מפות ה-stack מקודדות רק אינדקס של שיטה בקובץ ה-dex הנוכחי.

כדי לפתור את הבעיות האלה, ב-Android מגרסה 8.0:

  1. הסרת גישה למטמון dex מקוד שעבר קומפילציה (ראו גם את הקטע 'הסרת מטמון dex')
  2. הרחבת קידוד מפת הערימה.

שיפורים בסנכרון

צוות 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