מקרים לדוגמה

‫Grab הפחיתה את שגיאות ה-ANR ב-32% ושיפרה את זמן ההפעלה ב-35% באמצעות R8 ואופטימיזציה מבוססת-פרופיל

משך הקריאה: 5 דקות
3 מחברים
Ajesh Pai, Nguyen Van Minh, Tracy Agyemang

‫Grab היא אפליקציית-העל המובילה בדרום-מזרח אסיה, שמציעה שירותי משלוחים, ניידות ושירותים פיננסיים למיליוני משתמשים ב-8 מדינות דרך אפליקציה אחת. עם בסיס קוד בן 10 שנים שמכיל יותר מ-10 מיליון שורות קוד ומאות ספריות של צד שלישי, שנבנה באמצעות הגדרה משולבת של Bazel ו-Gradle, הצוות של Grab נתקל בשלושה אתגרים שהלכו והתעצמו: שיעור מקרי ה-ANR גבוה, גודל אפליקציה שהמשיך לגדול ככל שנוספו לאפליקציית-העל שירותים חדשים, וזמן הפעלה שהלך והתארך בהשוואה לשנה הקודמת.  אחרי שהפעילו את האופטימיזציה של R8, שיפרו את כללי השמירה והטמיעו פרופילים של Baseline ופרופילים של Startup, ב-Grab הצליחו לצמצם את שיעור מקרי ה-ANR שהשפיעו על המשתמשים ב-32%, להקטין את גודל ההורדה של האפליקציה בכ-30% ולקצר את זמן ההפעלה ב-35%.

איך למנוע עלייה חדה במקרי ה-ANR אחרי השקת גרסה חדשה כדי לשפר את חוויית המשתמש

לפני שהחברה ביצעה אופטימיזציה של תהליך ה-build, היא נתקלה בעיכובים בהפעלה קרה ובעליות חדות במקרים של "האפליקציה לא מגיבה" (ANR) מיד אחרי עדכונים חדשים, בעיקר בגלל העומס של קריאת קובצי DEX רבים במהלך האתחול של האפליקציה. 

הפרופילים בענן של Google Play סיפקו אופטימיזציה לתהליכים רגילים בכל בסיס המשתמשים, אבל הם לא מבצעים אופטימיזציה לכל נתיבי האתחול שנדרשים לחוויית השימוש ההכרחית של Grab, כמו אתחול של תרשימי תלות של Dagger במהלך הפעלת האפליקציה. בנתיבי קוד שלא נכללו בהפצה הראשונית, המכשירים הסתמכו על הידור JIT ועל אופטימיזציה מקומית ברקע בימים הבאים. הטמעה של פרופיל Baseline בהתאמה אישית פתרה את הבעיה הזו. הפרופיל סיפק את חוויות השימוש ההכרחיות האלה ישירות לפרופילים ב-Cloud של Google Play, והרחיב את כיסוי האופטימיזציה שלהם כך שהנתיבים העיקריים האלה קומפלו לשפת מכונה מיד עם ההתקנה. 

ANDDM_Grab_Inline_Quote_ALT.png

"בכל שבוע שבו השקנו גרסה חדשה של האפליקציה, נתקלנו בעליות חדות בשיעור מקרי ה-ANR", מסביר נגוין ואן מין, מהנדס תוכנה ראשי בצוות Passenger App Core של Grab. 

כדי למנוע שגיאות ANR ולשפר את זמני ההפעלה, הצוות של Grab הטמיע אופטימיזציות של R8, פרופילים של Baseline ופרופילים של הפעלה. אופטימיזציה מבוססת-פרופיל בשילוב עם כלי האופטימיזציה R8 הובילה לשיפור מיידי של 12% בהפעלה קרה מ-R8 ולשיפור נוסף של 15% מפרופילים של Baseline. כך הושג זמן הפעלה מהיר יותר ב-27% ונמנעו ביעילות העליות החדות בשיעור ה-ANR לאחר ההשקה.

האופטימיזציה של האפליקציה בוצעה בשלושה שלבים אסטרטגיים: הפעלת אופטימיזציה של R8 במצב תאימות, מעבר למצב מלא של R8, ואז שיפור של כללי השמירה הספציפיים (keep rule) באמצעות Configuration Analyzer והוספת פיקוח על CI/CD.

שלב 1: הפעלת אופטימיזציה של R8  (הושק ב-2025)

בשלב הראשון, הצוות של Grab הפסיק להשתמש בקובץ Proguard הקודם שהיה מוגדר כברירת מחדל, והתחיל להשתמש בקובץ proguard-android-optimize.txt, שמאפשר אופטימיזציות של R8.  בגלל שבסיס הקוד היה בן יותר מעשור והכיל יותר מ-10 מיליון שורות קוד, כדי להפעיל את הכלי לאופטימיזציה היה צריך לטפל בהגדרות קודמות ובשימוש ברפלקציה.

כדי למקסם את ההשפעה של השלב הזה, חברת Grab פרסמה את העדכון לאפליקציה עם מצב תאימות R8 מופעל, עם פרופילים של Baseline ופרופילים של Startup. המאמץ המשולב הזה הוביל להפחתה מיידית של 25% בשיעור מקרי ה-ANR שהשפיעו על המשתמשים, להפחתה של 16% בגודל ההורדה של האפליקציה ולשיפור כולל של 27% בזמני ההפעלה במצב התחלתי.

שלב 2: מצב מלא והעברה של R8 (הושק ב-2026)

השלב הזה הושק תשעה חודשים אחרי ההשקה הראשונה, ובמסגרתו האפליקציה הועברה למצב מלא של R8, שפתח אפשרויות נוספות לאופטימיזציה. ספריות סטנדרטיות כמו Retrofit, ‏ Firebase ו-Jetpack Compose פעלו בצורה חלקה עם R8 באמצעות הכללים המובנים שלהן, אבל כדי להעביר את שאר בסיס הקוד העצום, היה צריך לפתור נתיבי קוד ספציפיים עם הרבה רפלקציות:

כדי לעבור למצב R8 Full, הצוות אימץ את כללי השמירה של הצרכן מהגרסה האחרונה של Gson כדי להבטיח ניתוח חלק של JSON. הם גם טיפלו באתגרי אופטימיזציה באמצעות שרתי proxy דינמיים של Java, שמשתמשים ברפלקציה כדי ליצור הטמעות של ממשקים בזמן ריצה. מכיוון ש-R8 לא יכול לזהות את השימוש הזה בזמן ריצה במהלך ההידור, הוא מסיר את המחלקות הנחוצות או משנה את השם שלהן, מה שגורם לאפליקציה לקרוס. במקום להחיל חריגים רחבים שהיו מגדילים את נפח האפליקציה, הצוות בודד את המחלקות הספציפיות שמסתמכות על רפלקציה והטמיע כללי שמירה ממוקדים מאוד, וכך שמר על היציבות של האפליקציה ועל יעילות האופטימיזציה.

בסופו של דבר, מצב R8 Full Mode סיפק שיפורים משמעותיים ומצטברים, כולל הפחתה נוספת של 14% בגודל ההורדה של האפליקציה, שיפור נוסף של 10% בזמן ההפעלה וירידה נוספת של 10% בשגיאות ANR שהשפיעו על המשתמשים. אחרי המעבר למצב המלא של R8, בסיס הקוד של Grab השיג את ציוני האופטימיזציה הבאים של R8. 

  • ציון האופטימיזציה: 72%
  • ציון הכיווץ: 73%
  • ציון ערפול הקוד: 79.6%

הציונים האלה סיפקו משוב על הגדרת R8 ועל הצורך בשיפור כללי השמירה הספציפיים (keep rule) כדי למקסם את האופטימיזציה של R8. 

שלב 3: שיפור כללי השמירה 

בשלב האחרון של תהליך האופטימיזציה, Grab מתמקדת בבניית בסיס קוד תקין לטווח הארוך, במעקב רציף אחרי הביצועים ובניהול קפדני של הקומפילציה. כדי למדוד באופן שיטתי את תקינות הכללים ולמנוע רגרסיות בהגדרות במחזורי ההשקה השוטפים, הצוות משלב את R8 Configuration Analyzer בתהליך העבודה של ההערכה. הכלי הזה מספק תובנות מעמיקות לגבי מידת היעילות שבה כללי השמירה הספציפיים של Grab מאפשרים ל-R8 לבצע אופטימיזציה, כיווץ וערפול קוד של האפליקציה.

הצוות של Grab התבסס על R8 Full Mode וצמצם באופן שיטתי את כללי השמירה הקודמים, וכך השיג שיפורים משמעותיים בכל שלושת הממדים העיקריים שנבדקו על ידי המנתח:

  • ציון האופטימיזציה: עולה מ-72% ל-78.2%
  • ציון הכיווץ: עולה מ-73% ל-79.1%
  • ציון ערפול הקוד (obfuscation): עולה מ-79.6% ל-81.7%

בנוסף לשיפור הציונים האלה, השלב הייעודי הזה של שיפור כללי השמירה מניב הפחתה נוספת של כ-3.6% בגודל ההורדה של האפליקציה. חשוב מכך, המעקב אחרי המדדים האלה מספק לצוות ההנדסה את הנתונים האובייקטיביים שדרושים כדי להטמיע אמצעי הגנה אוטומטיים של CI לפני המיזוג. אמצעי ההגנה האלה מבטיחים שהקוד החדש עומד בסטנדרטים מחמירים של כללי שמירה, ושהשיפורים בביצועים שהושגו בעמל רב נשמרים לאורך זמן.

תוצאות

שיפורים מרכזיים בביצועים בעקבות ההשקה של הכלי לאופטימיזציה R8:

שלב האופטימיזציהמקרי ANRגודלהפעלה במצב התחלתי (cold start)
שלב 1: R8 Optimizer (הושק ב-2025)הפחתה של כ-25%הפחתה של 16%שיפור של כ-27%
שלב 2: מצב מלא של R8 (הושק ב-2026)הפחתה של 10%ירידה של 14%שיפור של 10%
אופטימיזציה של כללי השמירה-ירידה של 3.6%-
כללי (מצטבר)הפחתה של כ-32.5%הפחתה של כ-30.4%שיפור של כ-35%

אתגרים טכניים 

הפעלת הכלי R8 Optimizer בבסיס הקוד של Grab, שקיים כבר עשור, גרמה לכמה בעיות טכניות משמעותיות בקנה מידה רחב:

  • בדיקה: עשור של השתקפות מצטברת יצר מקרי קצה באופטימיזציה מלאה. מכיוון שאי אפשר היה לבצע בדיקה מקיפה של מיליוני שורות קוד, הצוות נקט גישה הדרגתית בעדיפות גבוהה – התמקדות ספציפית בבדיקת רמת הכיסוי של הקוד שמכוסה על ידי כללי השמירה המצומצמים, ושיפור חוזר של הכללים האלה כדי לטרגט רק את המחלקות, השיטות או השדות הספציפיים שנדרשים ל-reflection כדי למקסם את האופטימיזציה של R8.
  • זמן אחזור של גרסאות build וארכיטקטורה היברידית: מערכת ה-build ההיברידית של Grab (Bazel לניפוי באגים, Gradle לגרסאות build להפצה של R8) גרמה לכך ששינויים ניסיוניים ב-R8 דרשו עד שעתיים ב-CI מרוחק. כדי לגשר על הפער במשוב, הצוות של Grab התאים את גרסאות ה-QA היומיות להתנהגות של R8 בסביבת הייצור, והפך את האיסוף של פריטי מידע חשובים של גרסאות CI לאוטומטי, כמו mapping.txt ו-configuration.txt, כדי לייעל את החקירה.
  • מחזורי ניפוי באגים איטיים: מעקב אחרי שורש הבעיה במאות מחלקות שעברו אופטימיזציה הכביד מאוד על צוות רזה. כדי לפתור את הבעיה, ב-Grab הפכו את ההעלאה של קובצי המיפוי ל-Firebase Crashlytics לאוטומטית באמצעות Gradle, וכללו מיפויים לפענוח קוד מעורפל ב-Play Console. כך הם הצליחו לפתור באופן מיידי ומדויק בעיות שקשורות לסמלים בזמן הריצה.

ההשפעה של R8 על הקוד לטווח הארוך

מעבר לשיפור המיידי בביצועים, הפעלת הכלי R8 Optimizer הניבה יתרון מבני קבוע לבסיס הקוד העצום של Grab. הצוות של Grab השתמש בכלי הקוד הפתוח app-sizer כדי למדוד את ההשפעה של R8, וגילה ש-R8 הפחית באופן קבוע את ההשפעה של כל שורת קוד באפליקציה על גודל ההורדה.

ANDDM_Grab_Inline_Metric.png

בעידן של פיתוח בעזרת AI, שבו נפח הקוד גדל במהירות, האופטימיזציה הזו חשובה עוד יותר. בלי R8, הרחבת הקוד מגדילה באופן יחסי את גודל ההורדה. ‫R8 מבטיח שקצב הרחבת הקוד יישאר זהה, אבל הזיכרון שבשימוש יצטמצם באופן משמעותי.

הערה: מתודולוגיה: התרומה של Kotlin/Java ב-app-sizer לגודל ההורדה של האפליקציה (מכשיר מייצג), חלקי שורות הקוד (LOC) של Kotlin בסביבת הייצור (cloc; קבוצות מקורות ראשיות בלבד; לא כולל מודולים של בדיקה, מודולים מדומים ומודולים לדוגמה).

ניהול כללים ומנגנוני הגנה ב-Keep

כדי למנוע פגיעה בכללים ולשמור על האופטימיזציות של R8 לאורך זמן, Grab מתכננת לשלב אמצעי הגנה אוטומטיים לפני המיזוג ישירות בצינור עיבוד הנתונים של ה-CI. במסגרת המדיניות הזו, כללי שמירה חדשים מוגבלים באופן מחמיר, וכל כלל נוסף דורש הצדקה מפורשת ובדיקה. הצוות ב-Grab עוקב אחרי ציוני האופטימיזציה של R8 ב-CI. בהתבסס על כך, הצוות מתכנן להשתמש במדדים האלה כלולאת משוב מתמשכת, כך שרגרסיות יזוהו לפני המיזוג והיתרונות של האופטימיזציה יישמרו בכל מחזור הפצה. 

R8-rules-score (1).png

איור 1: לוח בקרה של CI שעוקב אחרי ציוני הכיווץ, האופטימיזציה וההסתרה של R8 לאורך זמן בגרסאות build של Grab, כדי לזהות ולמנוע באופן יזום רגרסיות בגרסאות שונות.

סיכום 

כדי להתמודד עם האתגרים של בסיס קוד עצום ומתפתח, הצוות של Pax App Core ב-Grab פעל באופן יזום כדי לקצר את זמני ההפעלה ולמנוע קפיצות בביצועים אחרי ההשקה. לשם כך, הצוות הטמיע פרופילים של Baseline ושיפר באופן קבוע את ההגדרות של R8. הצוות צמצם את מקרי ה-ANR שהשפיעו על המשתמשים בכ-32.5%, הקטין את גודל ההורדה של האפליקציה בכ-30.3% ושיפר את זמן ההפעלה הראשונית בכ-35%. בנוסף, הצוות שיפר באופן משמעותי את היעילות של הרחבת בסיס הקוד.

האופטימיזציות האלה סיפקו חוויית שימוש מהירה ויציבה יותר באפליקציית-העל למיליוני משתמשים בדרום-מזרח אסיה, ונתנו למהנדסים של Grab את הכלים לפתח תכונות מהר יותר בלי להגדיל במהירות את נפח ההורדה.

כדי להתחיל להשתמש ב-R8 ולקבל מידע נוסף, אפשר להיכנס לכתובת - d.android.com/r8.

כותב/ת המסמך:
להמשך קריאה