שיפור ביצועי האפליקציה בעזרת פרופילים בסיסיים

1. לפני שמתחילים

ב-Codelab הזה מוסבר איך ליצור פרופילים בסיסיים כדי לשפר את ביצועי האפליקציה, ואיך לאמת את יתרונות הביצועים של השימוש בפרופילי Baseline.

מה צריך להכין

הפעולות שתבצעו:

  • מגדירים את הפרויקט כך שישתמש בגנרטורים של פרופילים בסיסיים.
  • יצירת פרופילים בסיסיים כדי לבצע אופטימיזציה של ביצועי האפליקציה בזמן ההפעלה ובזמן הגלילה.
  • אימות השיפורים בביצועים באמצעות הספרייה Jetpack Macrobenchmark.

מה תלמדו

  • פרופילים בסיסיים ואיך הם יכולים לשפר את ביצועי האפליקציה.
  • איך יוצרים פרופילי Baseline.
  • שיפור בביצועים של פרופילים Baseline.

2. תהליך ההגדרה

כדי להתחיל, משכפלים את המאגר של GitHub משורת הפקודה באמצעות הפקודה הבאה:

$ git clone https://github.com/android/codelab-android-performance.git

לחלופין, אפשר להוריד שני קובצי zip:

פתיחת הפרויקט ב-Android Studio

  1. בחלון Welcome to Android Studio (ברוכים הבאים ל-Android Studio), בוחרים באפשרות 61d0a4432ef6d396.png Open an Existing Project (פתיחת פרויקט קיים).
  2. בוחרים את התיקייה [Download Location]/codelab-android-performance/baseline-profiles. חשוב לבחור בספרייה baseline-profiles.
  3. כש-Android Studio מייבא את הפרויקט, חשוב לוודא שאפשר להריץ את המודול app כדי ליצור את אפליקציית הדוגמה שעובדים איתה בהמשך.

האפליקציה לדוגמה

בקודלאב הזה תעבדו עם אפליקציית JetSnack לדוגמה. זוהי אפליקציה וירטואלית להזמנת חטיפים שמשתמשת ב-Jetpack פיתוח נייטיב.

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

23633b02ac7ce1bc.png

3. מהם פרופילים Baseline

פרופילים בסיסיים משפרים את מהירות ביצוע הקוד בכ-30% מהפעלה הראשונה, על ידי הימנעות מהפעלת שלבי פרשנות והידור בדיוק בזמן (JIT) של נתיבים של קוד שכלולים. כששולחים פרופיל בסיס באפליקציה או בספרייה, Android Runtime‏ (ART) יכול לבצע אופטימיזציה של נתיבי הקוד הכלולים באמצעות הידור מראש (AOT), וכך לספק שיפורים בביצועים לכל משתמש חדש ובכל עדכון לאפליקציה. אופטימיזציה מבוססת-פרופיל (PGO) מאפשרת לאפליקציות לבצע אופטימיזציה בהפעלה, לצמצם את עומס האינטראקציות ולשפר את הביצועים הכוללים של זמן הריצה למשתמשי הקצה כבר מההשקה הראשונה.

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

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

כשלא משתמשים בפרופיל Baseline, כל קודי האפליקציה עוברים הידור JIT בזיכרון אחרי שמפענחים אותם, או לקובץ odex ברקע כשהמכשיר לא פעיל. כתוצאה מכך, יכול להיות שהמשתמשים ייהנו מחוויית שימוש לא אופטימלית כשהם מריצים אפליקציה אחרי ההתקנה או העדכון שלה בפעם הראשונה, לפני שהנתיבים החדשים יעברו אופטימיזציה.

4. הגדרה של המודול ליצירת פרופיל בסיס

אפשר ליצור פרופילים בסיסיים באמצעות כיתה של בדיקת מכשור, שצריך להוסיף לפרויקט מודול Gradle חדש. הדרך הקלה ביותר להוסיף אותו לפרויקט היא באמצעות האשף של המודול ב-Android Studio שמגיע עם Android Studio Hedgehog ואילך.

כדי לפתוח את חלון האשף ליצירת מודול חדש, לוחצים לחיצה ימנית על הפרויקט או המודול בחלונית Project ובוחרים באפשרות New > Module.

232b04efef485e9c.png

בחלון שנפתח, בוחרים באפשרות Baseline Profile Generator בחלונית Templates (תבניות).

b191fe07969e8c26.png

בנוסף לפרמטרים הרגילים כמו שם המודול, שם החבילה, השפה או שפת תצורת ה-build, יש שני מקורות קלט שאינם רגילים למודול חדש: Target application ו-Use Gradle Managed Device.

אפליקציית היעד היא מודול האפליקציה שמשמש ליצירת פרופילים של ערך הבסיס. אם יש בפרויקט יותר ממודול אחד של אפליקציות, בוחרים לאיזה מודול רוצים להפעיל את המחוללים.

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

לאחר הגדרת כל הפרטים על המודול החדש, לוחצים על סיום כדי להמשיך ביצירת המודול.

שינויים שביצע אשף המודול

האשף של המודול מבצע כמה שינויים בפרויקט.

הוא מוסיף מודול Gradle בשם baselineprofile או השם שבחרתם באשף.

המודול הזה משתמש בפלאגין com.android.test, שמורה ל-Gradle לא לכלול אותו באפליקציה, כך שהוא יכול להכיל רק קוד בדיקה או מדדי ביצועים. הוא גם מחיל את הפלאגין androidx.baselineprofile, שמאפשר אוטומציה של יצירת פרופילים בסיסיים.

האשף מבצע גם שינויים במודול של אפליקציית היעד שבחרתם. באופן ספציפי, הוא מחיל את הפלאגין androidx.baselineprofile, מוסיף את התלות androidx.profileinstaller ומוסיף את התלות baselineProfile למודול החדש שנוצר build.gradle(.kts):

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

הוספת התלות של androidx.profileinstaller מאפשרת לך לעשות את הפעולות הבאות:

  • אימות מקומי של שיפורי הביצועים של פרופילי הבסיס שנוצרו.
  • שימוש בפרופילים בסיסיים ב-Android 7‏ (API ברמה 24) וב-Android 8‏ (API ברמה 26), שלא תומכים בפרופילים ב-Cloud.
  • שימוש בפרופילים בסיסיים במכשירים שלא מותקנת בהם מערכת Google Play Services.

יחסי התלות ב-baselineProfile(project(":baselineprofile")) מאפשרים ל-Gradle לדעת מאיזה מודול צריך לקחת את פרופילי הבסיס שנוצרו.

עכשיו, אחרי שהגדרתם את הפרויקט, עליכם לכתוב מחלקה של גנרטור לפרופילים של Baseline.

5. כתיבה של מחולל פרופיל Baseline

בדרך כלל יוצרים פרופילים בסיסיים לתהליכים הטיפוסיים שעוברים המשתמשים באפליקציה.

אשף המודול יוצר מחלקה בסיסית של BaselineProfileGenerator שמסוגלת ליצור את פרופיל Baseline לסטארט-אפ של האפליקציה. היא נראית כך:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

בכיתה הזו נעשה שימוש בכלל בדיקה מסוג BaselineProfileRule, והיא מכילה שיטת בדיקה אחת ליצירת הפרופיל. נקודת הכניסה ליצירת הפרופיל היא הפונקציה collect(). נדרשים רק שני פרמטרים:

  • packageName: החבילה של האפליקציה.
  • profileBlock: הפרמטר האחרון של lambda.

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

כברירת מחדל, סוג ה-generator שנוצר מכיל אינטראקציות להפעלת Activity שמוגדרת כברירת מחדל, וממתין עד שהפריים הראשון של האפליקציה יומר באמצעות השיטה startActivityAndWait().

הרחבת המחולל עם מסלולים מותאמים אישית

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

באפליקציית הדוגמה שלנו, אפשר לזהות את המסלולים האלה באופן הבא:

  1. מפעילים את האפליקציה. הנושא הזה כבר נכלל באופן חלקי על ידי הכיתה שנוצרה.
  2. ממתינים עד שהתוכן ייטען באופן אסינכרוני.
  3. גוללים ברשימת החטיפים.
  4. עוברים לפרטי החטיף.

משנים את המחולל כך שיכלול את הפונקציות המפורטות שמכסות את התהליכים הטיפוסיים בקטע הקוד הבא:

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

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

המתנה לתוכן אסינכרוני

באפליקציות רבות יש סוג כלשהו של טעינה אסינכררונית בזמן הפעלת האפליקציה, שנקראת גם מצב תצוגה מלא. המצב הזה מאפשר למערכת לדעת מתי התוכן נטען ועבר רינדור, ומתי המשתמש יכול לקיים איתו אינטראקציה. ממתינים למצב במחולל (waitForAsyncContent) עם האינטראקציות האלה:

  1. מאתרים את רשימת החטיפים של הפיד.
  2. ממתינים עד שחלק מהפריטים ברשימה יהיו גלויים במסך.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

תהליך השימוש ברשימת גלילה

בתהליך שבו משתמשים גוללים ברשימת חטיפים (scrollSnackListJourney), אפשר לעקוב אחרי האינטראקציות הבאות:

  1. מאתרים את הרכיב בממשק המשתמש של רשימת הפידים.
  2. מגדירים את שולי התנועות כך שלא יפעילו את הניווט במערכת.
  3. גוללים ברשימה וממתינים עד שחלון הממשק המשתמש ייצב.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

מעבר לדף הפרטים של התהליך

המסלול האחרון (goToSnackDetailJourney) מטמיע את האינטראקציות הבאות:

  1. כאן תמצאו את רשימת החטיפים ואת כל החטיפים שאפשר להשתמש בהם.
  2. בוחרים פריט מהרשימה.
  3. לוחצים על הפריט ומחכים עד שמסך הפרטים ייטען. אתם יכולים לנצל את העובדה שרשימה של החטיפים לא תופיע יותר במסך.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

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

6. הכנת מכשיר להפעלת הגנרטור

כדי ליצור פרופילים בסיסיים, מומלץ להשתמש באמולטור כמו מכשיר מנוהל של Gradle, או במכשיר עם Android 13 (API 33) ואילך.

כדי שתוכלו לשחזר את התהליך וליצור פרופילים בסיסיים באופן אוטומטי, תוכלו להשתמש במכשירים בניהול Gradle. מכשירים מנוהלים של Gradle מאפשרים לכם להריץ בדיקות באמולטור Android בלי שתצטרכו להפעיל אותו באופן ידני ולפרק אותו. מידע נוסף על מכשירים בניהול Gradle זמין במאמר התאמת הבדיקות לעומס באמצעות מכשירים בניהול Gradle.

כדי להגדיר מכשיר בניהול Gradle, מוסיפים את ההגדרה שלו לקובץ build.gradle.kts של המודול :baselineprofile, כפי שמוצג בקטע הקוד הבא:

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  } 
}

במקרה הזה, אנחנו משתמשים ב-Android 11 (רמת API 31) ותמונת המערכת aosp מסוגלת לגשת ל-root.

בשלב הבא, מגדירים את הפלאגין של Gradle לפרופיל הבסיס כך שישתמש במכשיר המנוהל של Gradle שהוגדר. לשם כך, מוסיפים את שם המכשיר לנכס managedDevices ומשביתים את useConnectedDevices כפי שמוצג בקטע הקוד הבא:

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

בשלב הבא יוצרים את פרופיל Baseline.

7. יצירת פרופיל הבסיס

כשהמכשיר יהיה מוכן, תוכלו ליצור את פרופיל הבקרה. הפלאגין Baseline Profile Gradle יוצר משימות Gradle כדי לבצע אוטומציה של כל התהליך של הפעלת כיתה הבדיקה של הגנרטור והחלה של פרופילי הבסיס שנוצרו באפליקציה.

האשף החדש של המודול יצר הגדרת הפעלה כדי שתוכלו להריץ במהירות את המשימה של Gradle עם כל הפרמטרים הנדרשים להפעלה, בלי שתצטרכו לעבור בין מסוף Android Studio.

כדי להריץ אותו, מאתרים את הגדרת ההרצה Generate Baseline Profile ולוחצים על לחצן ההרצה 599be5a3531f863b.png.

6911ecf1307a213f.png

המשימה מפעילה את קובץ האימג' של המהדר שהוגדר קודם. מריצים את האינטראקציות מכיתה הבדיקה BaselineProfileGenerator כמה פעמים, ולאחר מכן מפרקים את הסימולטור ומספקים את הפלט ל-Android Studio.

אחרי שהפעלת ה-generator מסתיימת בהצלחה, הפלאגין של Gradle מעביר באופן אוטומטי את ה-baseline-prof.txt שנוצר לאפליקציית היעד (מודול :app) בתיקייה src/release/generated/baselineProfile/.

fa0f52de5d2ce5e8.png

(אופציונלי) הפעלת הגנרטור משורת הפקודה

לחלופין, אפשר להריץ את הגנרטור משורת הפקודה. אפשר להשתמש במשימה שנוצרה על ידי מכשיר בניהול Gradle – :app:generateBaselineProfile. הפקודה הזו מפעילה את כל הבדיקות בפרויקט שהוגדרו על ידי התלות baselineProfile(project(:baselineProfile)). מכיוון שהמודול מכיל גם נקודות השוואה לאימות מאוחר יותר של שיפור הביצועים, הבדיקות האלה נכשלות ותוצג אזהרה לפני הפעלת נקודות השוואה באמולטור.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

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

הפקודה כולה נראית כך:

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

הפצת האפליקציה באמצעות פרופילים בסיסיים

אחרי שיוצרים את פרופיל הבסיס ומעתיקים אותו לקוד המקור של האפליקציה, יוצרים את גרסת הייצור של האפליקציה כרגיל. אין צורך לבצע פעולה נוספת כדי להפיץ את פרופילי הבסיס למשתמשים. הם נבחרים על ידי הפלאגין Android Gradle במהלך ה-build, ונכללים ב-AAB או ב-APK. בשלב הבא, מעלים את הגרסה היציבה ל-Google Play.

כשמשתמשים מתקינים את האפליקציה או מעדכנים אותה מהגרסה הקודמת, פרופיל הבסיס מותקן גם הוא, וכתוצאה מכך הביצועים משתפרים כבר מהפעלה הראשונה של האפליקציה.

בשלב הבא מוסבר איך לבדוק עד כמה ביצועי האפליקציה משתפרים באמצעות פרופילים של קבוצת הבסיס.

8. (אופציונלי) התאמה אישית של יצירת פרופילים Baseline

הפלאגין Baseline Profiles Gradle כולל אפשרויות להתאמה אישית של אופן היצירה של הפרופילים בהתאם לצרכים הספציפיים שלכם. אפשר לשנות את ההתנהגות באמצעות בלוק התצורה baselineProfile { } בסקריפטים ל-build.

בלוק התצורה בתוך המודול :baselineprofile משפיע על אופן ההפעלה של הגנרטורים, עם אפשרות להוסיף managedDevices ולהחליט אם להשתמש ב-useConnectedDevices או במכשירים בניהול Gradle.

בלוק ההגדרות במודול היעד :app קובע איפה הפרופילים נשמרים או איך הם נוצרים. אפשר לשנות את הפרמטרים הבאים:

  • automaticGenerationDuringBuild: אם האפשרות הזו מופעלת, אפשר ליצור את פרופיל הבסיס בזמן ה-build של גרסת הייצור. האפשרות הזו שימושית כשמפתחים ב-CI לפני ששולחים את האפליקציה.
  • saveInSrc: קובע אם פרופילי הבסיס שנוצרו מאוחסנים בתיקייה src/. לחלופין, אפשר לגשת לקובץ מתיקיית ה-build‏ :baselineprofile.
  • baselineProfileOutputDir: מגדיר איפה לשמור את פרופילי Baseline שנוצרו.
  • mergeIntoMain: כברירת מחדל, פרופילי Baseline נוצרים לכל וריאנט build (סוג המוצר וסוג ה-build). אם רוצים למזג את כל הפרופילים ל-src/main, מפעילים את הדגל הזה.
  • filter: יש לכם אפשרות לסנן את המחלקות או השיטות לכלול, או להחריג, מפרופילי הבסיס שנוצרו. האפשרות הזו יכולה להיות שימושית למפתחי ספריות שרוצים לכלול רק את הקוד מהספרייה.

9. איך מוודאים שהשיפורים בביצועים של האפליקציה מופעלים בזמן ההפעלה

אחרי שיוצרים את פרופיל הבקרה ומוסיפים אותו לאפליקציה, צריך לוודא שיש לו את ההשפעה הרצויה על ביצועי האפליקציה.

אשף המודול החדש יוצר מחלקה של נקודות השוואה בשם StartupBenchmarks. הוא מכיל מדד למדידת זמן ההפעלה של האפליקציה ומשויך לזמן ההפעלה של האפליקציה כשהיא משתמשת בפרופילים בסיסיים.

הכיתה נראית כך:

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

הוא משתמש ב-MacrobenchmarkRule שיכול להריץ מדדי ביצועים לאפליקציה שלכם ולאסוף מדדי ביצועים. נקודת הכניסה לכתיבת מדד ביצועים היא הפונקציה measureRepeated מהכלל.

יש צורך בכמה פרמטרים:

  • packageName: איזו אפליקציה למדוד.
  • metrics: סוג המידע שרוצים למדוד במהלך בדיקת הביצועים.
  • iterations: כמה פעמים יש חזרה על נקודת ההשוואה.
  • startupMode: אופן הפעלה של האפליקציה בתחילת בדיקת הביצועים.
  • setupBlock: אילו אינטראקציות עם האפליקציה צריכות להתרחש לפני המדידה.
  • measureBlock: אינטראקציות עם האפליקציה שרוצים למדוד במהלך בדיקת הביצועים.

מחלקת הבדיקה מכילה גם שתי בדיקות: startupCompilationeNone() ו-startupCompilationBaselineProfiles(), שקוראות לפונקציה benchmark() עם ערך compilationMode שונה.

CompilationMode

הפרמטר CompilationMode מגדיר את אופן הידור האפליקציה מראש לקוד מכונה. האפשרויות האלה זמינות:

  • DEFAULT: מבצעת הידור מראש חלקי של האפליקציה באמצעות פרופילים בסיסיים, אם הם זמינים. המערכת משתמשת באפשרות הזו אם לא מוגדר פרמטר compilationMode.
  • None(): איפוס מצב הידור האפליקציה, בלי לבצע הידור מראש של האפליקציה. הידור בזמן אמת (JIT) עדיין מופעל במהלך ההפעלה של האפליקציה.
  • Partial(): הידור מראש של האפליקציה באמצעות פרופילים בסיסיים או הפעלות הכנה, או שניהם.
  • Full(): קומפילציה מראש של כל קוד האפליקציה. זו האפשרות היחידה ב-Android 6 (API 23) וגרסאות קודמות.

אם ברצונך להתחיל לבצע אופטימיזציה של ביצועי האפליקציה, אפשר לבחור במצב הידור של DEFAULT, כי הביצועים דומים לביצועים שבהם האפליקציה מותקנת מ-Google Play. כדי להשוות בין יתרונות הביצועים שמספקים פרופילים של בסיס להשוואה, אפשר להשוות בין התוצאות של מצב ההידור None לבין התוצאות של Partial.

שינוי של מדד הביצועים כך שימתין לתוכן

הקריטריונים להשוואה נכתבים באופן דומה לגנרטורים של פרופילי הבסיס, על ידי כתיבת אינטראקציות עם האפליקציה. כברירת מחדל, הקריטריונים להשוואה שנוצרים ממתינים רק להצגה של הפריים הראשון – בדומה לאופן שבו BaselineProfileGenerator פעל – לכן מומלץ לשפר אותו כך שימתין לתוכן האסינכרוני.

תוכלו לעשות זאת באמצעות שימוש חוזר בפונקציות של התוספים שכתבתם במחולל. מאחר שבדק הזמן הזה מתועדים זמני ההפעלה הראשונית – באמצעות StartupTimingMetric() – מומלץ לכלול כאן רק את ההמתנה לתוכן האסינכרוני, ולאחר מכן לכתוב בדיקת ביצועים נפרדת עבור תהליכי השימוש האחרים שמוגדרים בגנרטור.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

הרצת מדדי השוואה

אתם יכולים להריץ את נקודות ההשוואה באותו אופן שבו מריצים בדיקות אינסטרומנטליות. אפשר להריץ את פונקציית הבדיקה או את כל הכיתה באמצעות הסמל של תעלת הניקוז שלצידה.

587b04d1a76d1e9d.png

חשוב לוודא שבחרתם מכשיר פיזי, כי הפעלת מדדי ביצועים במהלך זמן הריצה של Android Emulator נכשלת עם אזהרה על כך שמדדי הביצועים עשויים לספק תוצאות שגויות. מבחינה טכנית, אפשר להריץ אותו במהדמה, אבל בעצם מודדים את הביצועים של המכונה המארחת. אם הוא נמצא בעומס כבד, הביצועים של אמות המידה יהיו איטיים יותר והיחסים ביניהם יהיו הפוכים.

94e0da86b6f399d5.png

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

בסיום ההשוואה לשוק, אפשר לראות את התזמונים בפלט של Android Studio כפי שמוצג בצילום המסך הבא:

282f90d5f6ff5196.png

בצילום המסך אפשר לראות שזמן ההפעלה של האפליקציה שונה בכל CompilationMode. ערכי החציון מוצגים בטבלה הבאה:

timeToInitialDisplay‏ [ms]

timeToFullDisplay‏ [ms]

ללא

202.2

818.8

BaselineProfiles

193.7

637.9

שיפור

4%

28%

ההבדל בין מצבי הידור של timeToFullDisplay הוא 180 אלפיות השנייה,כלומר שיפור של כ-28% רק על ידי שימוש בפרופיל Baseline. הביצועים של CompilationNone פחות טובים, כי המכשיר צריך לבצע הכי הרבה הידור JIT במהלך ההפעלה של האפליקציה. הביצועים של CompilationBaselineProfiles טובים יותר כי הידור החלקי ב-AOT של פרופילי Baseline מבוסס על הקוד שסביר להניח שהמשתמש ישתמש בו, ולכן הקוד שלא עבר הידור מראש לא אמור להיטען מיידית.

10. (אופציונלי) מוודאים שיפור בביצועי הגלילה

בדומה לשלב הקודם, אפשר למדוד ולאמת את ביצועי הגלילה. קודם כול, יוצרים סיווג בדיקה ScrollBenchmarks עם כלל למדד ביצועים ושני שיטות בדיקה שמשתמשות במצבי הידור שונים:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

מתוך ה-method scroll, משתמשים בפונקציה measureRepeated עם הפרמטרים הנדרשים. עבור הפרמטר metrics, משתמשים ב-FrameTimingMetric, שמחשב את משך הזמן שנדרש ליצירת פריימים של ממשק המשתמש:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

הפעם, צריך לפצל את האינטראקציות בין setupBlock ל-measureBlock כדי למדוד רק את משכי הפריימים במהלך הפריסה הראשונה וגלילה בתוכן. לכן, צריך להוסיף את הפונקציות שמפעילות את מסך ברירת המחדל ל-setupBlock ואת פונקציות התוסף waitForAsyncContent() ו-scrollSnackListJourney() שכבר נוצרו ל-measureBlock:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

אחרי שהבדיקה מוכנה, אפשר להריץ אותה כמו קודם כדי לקבל את התוצאות שמוצגות בצילום המסך הבא:

84aa99247226fc3a.png

הפלט של FrameTimingMetric הוא משך הפריימים באלפיות השנייה (frameDurationCpuMs) באחוזון ה-50, ה-90, ה-95 וה-99. ב-Android 12 (רמת API 31) ואילך, הפונקציה מחזירה גם את משך הזמן שבו הפריימים חורגים מהמגבלה (frameOverrunMs). הערך יכול להיות שלילי, כלומר נותר זמן נוסף ליצירת הפריים.

לפי התוצאות, אפשר לראות שמשך רינדור הפריים של CompilationBaselineProfiles קצר יותר ב-2 אלפיות השנייה בממוצע, מה שעלול להיות לא משמעותי למשתמשים. עם זאת, לגבי האחוזונים האחרים התוצאות ברורות יותר. ב-P99, ההבדל הוא 43.5ms, כלומר יותר מ-3 פריימים מושמטים במכשיר שפועל במהירות 90FPS. לדוגמה: ב-Pixel 6 זה זמן לעיבוד פריים: 1, 000 אלפיות השנייה / 90 FPS = כ-11 אלפיות השנייה.

11. מזל טוב

כל הכבוד, השלמת את הקודלה הזה ושיפורת את ביצועי האפליקציה באמצעות פרופילים בסיסיים.

משאבים נוספים

מקורות מידע נוספים:

מסמכי עזר