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

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

בעיות ב-build

אם העתקתם את הדוגמה 'פרופילי הבסיס' בקטע עכשיו ב-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):
        ...

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

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

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

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

בעיות בהתקנה

צריך לבדוק שה-APK או ה-AAB שיוצרים הם מהווריאנט של build שכולל פרופילים בסיסיים. הדרך הקלה ביותר לבדוק זאת היא לפתוח את ה-APK ב-Android Studio, בוחרים באפשרות Build > (פיתוח >) ניתוח APK, פתיחה של APK, ומחפשים את הפרופיל ב-/assets/dexopt/baseline.prof file:

חיפוש פרופיל Baseline באמצעות מציג APK ב-Android Studio
איור 2. חיפוש פרופיל Baseline באמצעות מציג APK ב- Android Studio.

צריך לבצע הידור של פרופילים בסיסיים במכשיר שבו פועלת האפליקציה. לשני הסוגים התקנות של אפליקציות מחנות האפליקציות ואפליקציות שהותקנו באמצעות PackageInstaller, האיסוף במכשיר מתרחש כחלק מהאפליקציה תהליך ההתקנה. אבל כשהאפליקציה מותקנת מ-Android Studio, או באמצעות כלי שורת הפקודה, ספריית 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());
        ListenableFutureP<rofileVerifier.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 או ב-AAB שפועלים. חשוב לוודא שאתם משתמשים לבנות וריאנט שכולל פרופילים בסיסיים אם מופיעה שגיאה זו, ה-APK מכיל פרופיל.
RESULT_CODE_NO_PROFILE
לא הותקן פרופיל לאפליקציה הזו בזמן התקנת האפליקציה דרך האפליקציה או מנהל החבילות. הסיבה העיקרית לקוד השגיאה הזה היא הפרופיל מנהל ההתקנה לא פעל מכיוון ש-ProfileInstallerInitializer הושבת. שים לב: כאשר מדווח על שגיאה זו, פרופיל מוטמע עדיין נמצא APK של האפליקציה. כאשר פרופיל מוטמע לא נמצא, מוחזר קוד השגיאה RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
פרופיל נמצא ב-APK או ב-AAB ומצורף לתור לאיסוף. כאשר הפרופיל מותקן על ידי ProfileInstaller, הוא נמצא בתור להידור בפעם הבאה שהמערכת תפעיל אופטימיזציה של DEX ברקע. הפרופיל אינו פעיל עד לסיום הידור. אל תנסו ליצור נקודת השוואה של ערך הבסיס הפרופילים עד להשלמת ההידור. יכול להיות שתצטרכו איסוף מאולץ של פרופילים בסיסיים. השגיאה לא תופיע כאשר האפליקציה מותקנת מחנות האפליקציות או ממנהל החבילות במכשירים שפועלים Android 9 (API 28) ואילך, כי ההידור במהלך ההתקנה.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
הותקן פרופיל לא תואם והאפליקציה נוצרה באמצעותו. הסיבה לכך היא ההתקנה דרך חנות Google Play או מנהל החבילות. לתשומת ליבך, התוצאה הזו שונה מ-RESULT_CODE_COMPILED_WITH_PROFILE כי הפרופיל הלא תואם יכלול רק שיטות שעדיין משותפות בין הפרופיל לאפליקציה. הפרופיל קטן בפועל מ- צפוי, ופחות שיטות יעברו הידור (methods) מאלה שנכללו בקבוצת הבסיס פרופיל.
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, כדי ליצור אירועי ניתוח נתונים שמציין את סטטוס הפרופיל. לדוגמה, מתריעה במהירות אם תפורסם גרסה חדשה של האפליקציה שלא כוללת פרופילים בסיסיים.

אילוץ הידור של פרופילים בסיסיים

אם סטטוס ההידור של פרופילי הבסיס הוא RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, אפשר לאלץ 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]

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

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

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

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

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

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

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

נקודת השוואה נכונה של מדדי הפעלה

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

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

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

חשוב להקפיד על רמה נמוכה ככל האפשר של 'TTDID' ושל 'דברים שאפשר לעשות' (TTD) כדי שהאפליקציה תהיה רספונסיבית.

המערכת יכולה לזהות את האובייקט 'TTDID', להציג אותו ב-Logcat ולדווח על כך כחלק של נקודות השוואה לחברות סטארט-אפ. עם זאת, המערכת לא יכולה לקבוע את 'דברים שאפשר לעשות' (TTDFD) באחריות האפליקציה לדווח כשהיא מגיעה לקטע אינטראקטיבי מפורט . אפשר לעשות זאת בטלפון reportFullyDrawn(), או ReportDrawn אם משתמשים ב-Jetpack פיתוח נייטיב. אם יש לך כמה משימות ברקע שצריך לבצע לפני שהאפליקציה תיחשב במלואה ואז אפשר להשתמש ב-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"))
    }
  }
}

מגניב

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") {}
      ...
    }
  ...
}

מגניב

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

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

נמנעים מהפעלה של אפליקציה שמחייבת קלט/פלט (I/O)

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

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

אם באפליקציה שלך נעשה שימוש ב-Hilt, ניתן לספק קידוד קלט/פלט (I/O) מזויף הטמעות בעת ביצוע השוואה לשוק ב-Microbenchmark ו-Hilt.

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

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