פרופילים בסיסיים של ניפוי באגים

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

בעיות ב-build

אם העתקת את הדוגמה לפרופיל בסיס באפליקציית הדוגמה Now in Android, יכול להיות שתקבלו הודעות על כישלונות בבדיקות במהלך המשימה של פרופיל הבסיס, עם ההודעה "לא ניתן להריץ את הבדיקות במהדמ":

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

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

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

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

הוספת הגדרת הפעלה מותאמת אישית ליצירת פרופילים בסיסיים ב-Now ב-Android
איור 1. מוסיפים הגדרת הפעלה מותאמת אישית כדי ליצור פרופילים של נתוני בסיס ב-Now ב-Android.

בעיות בהתקנה

בודקים ש-APK או ה-AAB שבודקים מגיעים מווריאנט build שכולל פרופילים בסיסיים:

  1. ב-Android Studio, בוחרים באפשרות Build (יצירת build) > Analyze APK (ניתוח APK).
  2. פותחים את קובץ ה-AAB או ה-APK.
  3. אם בודקים קובץ AAB, הפרופיל נמצא ב-/BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof. אם בודקים קובץ APK, הפרופיל נמצא ב-/assets/dexopt/baseline.prof.
איך בודקים אם יש פרופיל בסיס באמצעות APK Viewer ב-Android Studio
איור 2. בודקים אם יש פרופיל בסיס באמצעות APK Viewer ב-Android Studio.

צריך לבצע הידור של פרופילים בסיסיים במכשיר שבו פועלת האפליקציה. כשמתקינים את האפליקציה באמצעות Play Store, Android Studio או הכלי של שורת הפקודה Gradle Wrapper, ההידור במכשיר מתבצע באופן אוטומטי. כשהאפליקציה מותקנת באמצעות כלים אחרים, ספריית Jetpack‏ ProfileInstaller אחראית להוספת הפרופילים לתור לצורך הידור במהלך תהליך האופטימיזציה הבא של DEX ברקע. במקרים כאלה, כדי לוודא שנעשה שימוש בפרופילים הבסיסיים, יכול להיות שתצטרכו לאלץ את הידור הפרופילים הבסיסיים. בעזרת ProfileVerifier אפשר לשלוח שאילתה לגבי סטטוס ההתקנה וההדרכה של הפרופיל, כפי שמוצג בדוגמה הבאה:

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java

public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

קודי התוצאות הבאים מספקים רמזים לגבי הסיבה לבעיות מסוימות:

RESULT_CODE_COMPILED_WITH_PROFILE
הפרופיל מותקן, מופעל ומשומש בכל פעם שמריצים את האפליקציה. זו התוצאה שרוצים לראות.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
לא נמצא פרופיל בקובץ ה-APK שפועל. אם השגיאה הזו מופיעה, צריך לוודא שאתם משתמשים בגרסה של build שכוללת פרופילים בסיסיים, ושקובץ ה-APK מכיל פרופיל.
RESULT_CODE_NO_PROFILE
לא הותקן פרופיל לאפליקציה הזו כשהתקנתם אותה דרך חנות האפליקציות או מנהל החבילות. הסיבה העיקרית לקוד השגיאה הזה היא שהתוכנה להתקנת הפרופיל לא הופעלה בגלל שהאפשרות ProfileInstallerInitializer הושבתה. חשוב לזכור: כשהשגיאה הזו מתקבלת, עדיין נמצא פרופיל מוטמע בקובץ ה-APK של האפליקציה. אם לא נמצא פרופיל מוטמע, קוד השגיאה המוחזר הוא RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
המערכת מוצאת פרופיל בקובץ ה-APK או ה-AAB ומוסיפה אותו לתור לצורך הידור. כשפרופיל מותקן על ידי ProfileInstaller, הוא מתווסף לתור ל-compilation בפעם הבאה שהמערכת מריצה אופטימיזציה של DEX ברקע. הפרופיל לא פעיל עד להשלמת הידור הקוד. אל תנסה לבצע בדיקת ביצועים של פרופילי הבסיס עד שהאוסף יושלם. יכול להיות שתצטרכו לאלץ את הידור הפרופילים של Baseline. השגיאה הזו לא תופיע כשמתקינים אפליקציה מחנות האפליקציות או מנהל החבילות במכשירים עם Android מגרסה 9 ואילך, כי הידור מתבצע במהלך ההתקנה.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
פרופיל לא תואם מותקן והאפליקציה נוצרה באמצעותו. זוהי תוצאה של התקנה דרך חנות Google Play או מנהל החבילות. שימו לב שהתוצאה הזו שונה מ-RESULT_CODE_COMPILED_WITH_PROFILE כי בפרופיל שלא תואם יתבצע הידור רק של השיטות שעדיין משותפות בין הפרופיל לאפליקציה. הפרופיל קטן יותר מהצפוי, ויתבצע הידור של פחות שיטות מאשר אלה שכלולות בפרופיל הבסיסי.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
לא ניתן לכתוב את קובץ המטמון של תוצאות האימות ב-
ProfileVerifier. הסיבה לכך יכולה להיות בעיה בהרשאות התיקייה של האפליקציה או חוסר מקום פנוי בדיסק במכשיר.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier תומך רק ב-Android 9 (רמת API 28) ואילך.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
הערך PackageManager.NameNotFoundException מושלך כששולחים שאילתה ל-PackageManager לגבי חבילת האפליקציה. זה אמור לקרות לעיתים רחוקות. כדאי לנסות להסיר את האפליקציה ולהתקין מחדש את כל הקבצים.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
קובץ מטמון של תוצאת אימות קודמת קיים, אבל אי אפשר לקרוא אותו. זה אמור לקרות לעיתים רחוקות. כדאי לנסות להסיר את האפליקציה ולהתקין מחדש את כל הפריטים.

שימוש ב-ProfileVerifier בסביבת הייצור

בסביבת הייצור, אפשר להשתמש ב-ProfileVerifier בשילוב עם ספריות של דיווח ניתוח נתונים, כמו Google Analytics for Firebase, כדי ליצור אירועי ניתוח נתונים שמציינים את סטטוס הפרופיל. לדוגמה, כך תוכלו לקבל התראה מהירה אם תופץ גרסת אפליקציה חדשה שלא מכילה פרופילים בסיסיים.

אילוץ הידור של פרופילי Baseline

אם סטטוס הידור של פרופילי Baseline הוא RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, אפשר לאלץ הידור מיידי באמצעות adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

בדיקת מצב הידור ללא ProfileVerifier

אם אתם לא משתמשים ב-ProfileVerifier, תוכלו לבדוק את מצב הידור באמצעות adb, אבל התובנות יהיו פחות מעמיקות מאשר ב-ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

שימוש ב-adb מניב משהו שדומה לזה:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

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

סטטוס הידור משמעות
speed‑profile קיים פרופיל שנוצר והוא נמצא בשימוש.
verify לא קיים פרופיל מפורט.

סטטוס verify לא אומר שה-APK או ה-AAB לא מכילים פרופיל, כי הוא יכול להיכנס לתור לצורך הידור על ידי המשימה הבאה של אופטימיזציית DEX ברקע.

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

סיבה משמעות
install‑dm פרופיל בסיס נוהל באופן ידני או על ידי Google Play כשהאפליקציה מותקנת.
bg‑dexopt פרופיל נוצר בזמן שהמכשיר היה במצב מנוחה. יכול להיות שמדובר בפרופיל בסיס או בפרופיל שנאסף במהלך השימוש באפליקציה.
cmdline ה-compilation הופעל באמצעות adb. יכול להיות שמדובר בפרופיל בסיס או בפרופיל שנאסף במהלך השימוש באפליקציה.

בעיות שקשורות לביצועים

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

השוואה נכונה של מדדי סטארט-אפ לשוק

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

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

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

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

המערכת יכולה לזהות את TTID, להציג אותו ב-Logcat ולדווח עליו כחלק ממדדי הביצועים של סטארט-אפים. עם זאת, המערכת לא יכולה לקבוע את TTFD, והאפליקציה אחראית לדווח על כך כשהיא מגיעה למצב אינטראקטיבי מצויר במלואו. אפשר לעשות זאת על ידי קריאה לפונקציה reportFullyDrawn(), או ReportDrawn אם משתמשים ב-Jetpack Compose. אם יש לכם כמה משימות רקע שצריכות להסתיים לפני שהאפליקציה נחשבת כמוצגת במלואה, תוכלו להשתמש ב-FullyDrawnReporter, כפי שמתואר בקטע שיפור הדיוק של תזמון ההפעלה.

פרופילים של ספריות ופרופילים מותאמים אישית

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

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

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

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

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

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

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

כשמריצים את בדיקת הביצועים מ-Android Studio, בוחרים וריאנט releaseWithoutCustomProfile כדי למדוד את הביצועים באמצעות פרופילים של ספריות בלבד, או וריאנט release כדי למדוד את הביצועים באמצעות פרופילים של ספריות ופרופילים מותאמים אישית.

הימנעות מהפעלת אפליקציות שמבוססות על קלט/פלט

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

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

אם האפליקציה שלכם משתמשת ב-Hilt, תוכלו לספק הטמעות מזויפות של I/O בזמן בדיקת הביצועים ב-Microbenchmark and Hilt.

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

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