Debugowanie profili podstawowych

Ten dokument przedstawia sprawdzone metody, które pomogą Ci diagnozować problemy i zadbać o to, aby profile bazowe działały prawidłowo i przynosiły największe korzyści.

Problemy z kompilacjami

Jeśli przykładowy profil podstawowy został skopiowany w przykładowej aplikacji Now in Android, mogą wystąpić błędy testów podczas wykonywania zadania profilu podstawowego informujące, że testów nie można uruchomić w emulatorze:

./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):
        ...

Błędy występują, ponieważ Now na Androidzie do generowania profilu podstawowego korzysta z urządzenia zarządzanego przez Gradle. Niepowodzenia są zjawiskiem normalnym, ponieważ zwykle nie należy przeprowadzać testów porównawczych wydajności na emulatorze. Ponieważ jednak podczas generowania profili podstawowych nie zbierasz danych o wydajności, możesz dla wygody uruchomić zbieranie danych profilu podstawowego za pomocą emulatorów. Aby używać profili bazowych za pomocą emulatora, wykonaj kompilację i instalację za pomocą wiersza poleceń, a następnie ustaw argument włączający reguły profili bazowych:

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

Możesz też utworzyć w Android Studio niestandardową konfigurację uruchamiania, aby włączyć profile bazowe w emulatorach. Aby to zrobić, wybierz Uruchom > Edytuj konfiguracje:

Dodaj niestandardową konfigurację uruchamiania, aby utworzyć profile bazowe w Now w Androidzie
Rysunek 1. Dodaj niestandardową konfigurację uruchamiania, aby utworzyć profile bazowe w Now na Androidzie.

Problemy przy instalacji

Sprawdź, czy tworzony plik APK lub AAB pochodzi z wariantu kompilacji, który zawiera profile bazowe. Aby to sprawdzić, najlepiej otwórz pakiet APK w Android Studio. W tym celu wybierz Utwórz > Analizuj plik APK. Otwórz odpowiedni plik APK i odszukaj profil w pliku /assets/dexopt/baseline.prof:

Sprawdź profil podstawowy za pomocą przeglądarki plików APK w Android Studio
Rysunek 2. Poszukaj profilu podstawowego za pomocą przeglądarki plików APK w Android Studio.

Profile podstawowe muszą być skompilowane na urządzeniu, na którym działa aplikacja. Zarówno w przypadku instalacji ze sklepu z aplikacjami, jak i aplikacji zainstalowanych za pomocą PackageInstaller kompilacja na urządzeniu odbywa się w ramach procesu instalowania aplikacji. Jeśli jednak aplikacja zostanie wczytana z innego urządzenia z Android Studio lub za pomocą narzędzi wiersza poleceń, biblioteka Jetpack ProfileInstaller jest odpowiedzialna za umieszczenie profili w kolejce do kompilacji podczas następnego procesu optymalizacji DEX w tle. W takich przypadkach, jeśli chcesz mieć pewność, że Twoje profile bazowe są używane, może być konieczne wymuszenie kompilacji profili podstawowych. ProfileVerifier umożliwia wysyłanie zapytań o stan instalacji i kompilacji profilu, jak pokazano w tym przykładzie:

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);
    }
}

Te kody wyników zawierają wskazówki dotyczące przyczyny niektórych problemów:

RESULT_CODE_COMPILED_WITH_PROFILE
Profil jest instalowany, skompilowany i używany przy każdym uruchomieniu aplikacji. A to właśnie ten wynik.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
W uruchamianym pakiecie APK lub AAB nie znaleziono profilu. Jeśli widzisz ten błąd, upewnij się, że używasz wariantu kompilacji, który zawiera profile bazowe, a plik APK zawiera profil.
RESULT_CODE_NO_PROFILE
Podczas instalowania aplikacji za pomocą sklepu z aplikacjami lub menedżera pakietów nie zainstalowano dla tej aplikacji profilu. Głównym powodem wystąpienia błędu jest to, że instalator profilu nie został uruchomiony z powodu wyłączenia ProfileInstallerInitializer. Pamiętaj, że po zgłoszeniu tego błędu w pliku APK aplikacji nadal znajduje się umieszczony profil. Jeśli nie można znaleźć osadzonego profilu, zwrócony kod błędu to RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Profil znaleziony w pliku APK lub AAB i umieszczony w kolejce do kompilacji. Gdy profil zostanie zainstalowany przez ProfileInstaller, zostanie umieszczony w kolejce do kompilacji, gdy system następnym razem uruchomi optymalizację DEX w tle. Profil nie jest aktywny do czasu zakończenia kompilacji. Nie próbuj przeprowadzać analizy porównawczej swoich profili podstawowych, dopóki nie zakończy się kompilacja. Może być konieczne wymuszenie kompilacji profili podstawowych. Ten błąd nie wystąpi, gdy aplikacja zostanie zainstalowana ze sklepu z aplikacjami lub menedżera pakietów na urządzeniach z Androidem 9 (API 28) lub nowszym, ponieważ kompilacja odbywa się podczas instalacji.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Zainstalowano niepasujący profil i skompilowano z nim aplikację. Jest to wynikiem instalacji ze Sklepu Google Play lub menedżera pakietów. Zwróć uwagę, że ten wynik różni się od RESULT_CODE_COMPILED_WITH_PROFILE, ponieważ niepasujący profil skompiluje tylko metody, które nadal są udostępniane między profilem a aplikacją. Profil jest mniejszy od oczekiwanego i skompilowanych zostanie mniej metod niż w profilu podstawowym.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier nie może zapisać pliku pamięci podręcznej wyników weryfikacji. Może się tak zdarzyć, gdy coś jest nie tak z uprawnieniami do folderów aplikacji lub na urządzeniu nie ma wystarczającej ilości wolnego miejsca.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier obsługuje tylko Androida 9 (poziom interfejsu API 28) i nowsze wersje.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Podczas wysyłania zapytania do PackageManager dotyczącego pakietu aplikacji wywoływany jest obiekt PackageManager.NameNotFoundException. Taka sytuacja zdarza się rzadko. Odinstaluj aplikację i zainstaluj ją jeszcze raz.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Plik pamięci podręcznej poprzedniego wyniku weryfikacji istnieje, ale nie można go odczytać. Taka sytuacja zdarza się rzadko. Odinstaluj aplikację i zainstaluj ją jeszcze raz.

Używanie profilu ProfileVerifier w środowisku produkcyjnym

W środowisku produkcyjnym możesz używać ProfileVerifier w połączeniu z bibliotekami raportów analitycznych, np. Google Analytics dla Firebase, aby generować zdarzenia analityczne wskazujące stan profilu. Dzięki temu możesz np. szybko powiadamiać o opublikowaniu nowej wersji aplikacji, która nie zawiera profili podstawowych.

Wymuś kompilację profili bazowych

Jeśli stan kompilacji profili podstawowych to RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, możesz wymusić natychmiastową kompilację za pomocą adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Sprawdzanie stanu kompilacji bez parametru ProfileVerifier

Jeśli nie używasz narzędzia ProfileVerifier, możesz sprawdzić stan kompilacji za pomocą narzędzia adb. Nie zapewnia on jednak tak szczegółowych statystyk jak ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

Użycie polecenia adb daje podobny efekt:

  [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]

Wartość stanu wskazuje stan kompilacji profilu i jest jedną z następujących wartości:

Stan kompilacji Znaczenie
speed‑profile Profil skompilowany istnieje i jest używany.
verify Nie istnieje skompilowany profil.

Stan verify nie oznacza, że pakiet APK lub AAB nie zawiera profilu, ponieważ może on zostać umieszczony w kolejce do kompilacji przez następne zadanie optymalizacji DEX w tle.

Wartość powodu wskazuje, co aktywuje kompilację profilu, i jest jedną z tych wartości:

Uzasadnienie Znaczenie
install‑dm Profil podstawowy został skompilowany ręcznie lub przez Google Play podczas instalacji aplikacji.
bg‑dexopt Profil został skompilowany, gdy urządzenie było bezczynne. Może to być profil podstawowy lub profil zbierany podczas korzystania z aplikacji.
cmdline Kompilacja została uruchomiona przez adb. Może to być profil podstawowy lub profil zbierany podczas korzystania z aplikacji.

problemy z wydajnością,

W tej sekcji przedstawiamy sprawdzone metody prawidłowego definiowania i analizy porównawczej profili podstawowych, które pozwalają w pełni wykorzystać ich możliwości.

Prawidłowo analizuj dane dotyczące startupów

Twoje profile bazowe będą bardziej skuteczne, jeśli wskaźniki startowe będą dobrze zdefiniowane. Dwa kluczowe wskaźniki to czas do początkowego wyświetlenia (TTID) i czas do pełnego wyświetlenia (TTFD).

TTID ma miejsce, gdy aplikacja wyświetla pierwszą klatkę. Pamiętaj, aby był on jak najkrótszy, bo wyświetlanie informacji pokazuje użytkownikowi, że aplikacja jest uruchomiona. Możesz nawet wyświetlać nieokreślony wskaźnik postępu informujący o reakcji aplikacji.

TTFD oznacza, że można wejść w interakcję z aplikacją. Opis powinien być jak najkrótszy, aby uniknąć frustracji użytkowników. Jeśli prawidłowo zasygnalizujesz TTFD, informujesz system, że kod uruchamiany w drodze do TTFD jest częścią procesu uruchamiania aplikacji. W rezultacie system prawdopodobnie umieści ten kod w profilu.

Aby aplikacja reagowała na responsywność, utrzymuj jak najmniejszą możliwą wartość TTID i TFD.

System wykrywa TTID, wyświetla go w Logcat i zgłasza jako część testów porównawczych podczas uruchamiania. System nie jest w stanie określić TTFD, a aplikacja musi zgłosić, kiedy uzyska on w pełni narysowany stan interaktywny. Możesz to zrobić, wywołując reportFullyDrawn() lub ReportDrawn, jeśli używasz Jetpack Compose. Jeśli masz wiele zadań w tle, które trzeba wykonać, aby aplikacja została uznana za w pełni narysowaną, możesz użyć FullyDrawnReporter. Więcej informacji na ten temat znajdziesz w sekcji Zwiększanie dokładności czasu uruchamiania.

Profile biblioteki i profile niestandardowe

Podczas analizy porównawczej wpływu profili może być trudno oddzielić korzyści zapewniane przez profile aplikacji od tych pochodzących z bibliotek, takich jak biblioteki Jetpacka. Podczas tworzenia pliku APK wtyczka Androida do obsługi Gradle dodaje wszystkie profile w zależnościach biblioteki oraz Twój profil niestandardowy. Zwiększa to ogólną wydajność i jest zalecany przy kompilacjach wersji. Trudno jednak zmierzyć, o ile dodatkowy wzrost skuteczności zapewnia niestandardowy profil.

Szybkim sposobem na ręczne sprawdzenie dodatkowej optymalizacji zapewnianej przez profil niestandardowy jest usunięcie go i przeprowadzenie testów porównawczych. Następnie zastąp ją i jeszcze raz przeprowadź testy porównawcze. Porównując te 2 opcje, zobaczysz optymalizacje podane tylko przez profile z biblioteki, profile biblioteczne i Twój profil niestandardowy.

Automatyczny sposób porównywania profili polega na utworzeniu nowego wariantu kompilacji, który zawiera tylko profile biblioteki, a nie Twój profil niestandardowy. Porównaj testy porównawcze tego wariantu z wersją wersji, która zawiera zarówno profile biblioteki, jak i Twoje profile niestandardowe. Przykład poniżej pokazuje, jak skonfigurować wariant zawierający tylko profile biblioteki. Dodaj nowy wariant o nazwie releaseWithoutCustomProfile do modułu klienta w profilu, który jest zwykle modułem aplikacji:

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

Odlotowy

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

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

Poprzedni przykładowy kod usuwa zależność baselineProfile ze wszystkich wariantów i selektywnie stosuje ją tylko do wariantu release. Może się wydawać, że profile biblioteki są nadal dodawane po usunięciu zależności od modułu producenta profilu. Odpowiada on jednak tylko za generowanie Twojego profilu niestandardowego. Wtyczka Androida do obsługi Gradle nadal działa we wszystkich wariantach i odpowiada za uwzględnianie profili bibliotek.

Musisz też dodać nowy wariant do modułu generatora profili. W tym przykładzie moduł producenta nosi nazwę :baselineprofile.

Kotlin

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

Odlotowy

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

Aby przeprowadzić analizę porównawczą tylko z profilami z biblioteki, uruchom to polecenie:

./gradlew :baselineprofile:connectedBenchmarkReleaseWithoutCustomProfileAndroidTest

Aby przeprowadzić analizę porównawczą zarówno z profilem biblioteki, jak i z profilem niestandardowym, uruchom to polecenie:

./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest

Uruchomienie poprzedniego kodu w przykładowej aplikacji Macrobenchmark wykazuje różnicę w wydajności 2 wersji. W przypadku wyłącznie profili biblioteki analiza porównawcza startupCompose pokazuje takie wyniki:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min  70.8,   median  79.1,   max 126.0
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Wiele bibliotek Jetpack Compose zawiera profile bibliotek, więc można wprowadzić pewne optymalizacje przy użyciu wtyczki Gradle profilu Baseline. Korzystanie z profilu niestandardowego ma jednak dodatkowe optymalizacje:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min 57.9,   median 73.5,   max 92.3
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

Unikaj uruchamiania aplikacji przez wejścia/wyjścia

Jeśli podczas uruchamiania aplikacja wykonuje dużo wywołań wejścia-wyjścia lub wywołań sieciowych, może to negatywnie wpłynąć zarówno na czas uruchamiania aplikacji, jak i dokładność testów porównawczych podczas uruchamiania. Te intensywne wywołania mogą wymagać nieokreślonego czasu, który może się zmieniać w czasie, a nawet pomiędzy kolejnymi powtórzeniami tej samej analizy porównawczej. Wywołania wejścia-wyjścia są zwykle lepsze niż wywołania sieciowe, ponieważ na to drugie mogą wpływać czynniki zewnętrzne w stosunku do urządzenia i samego urządzenia. Unikaj połączeń sieciowych podczas uruchamiania. Jeśli nie można uniknąć użycia któregoś z tych elementów, użyj wejścia-wyjścia.

Zalecamy, aby architektura aplikacji obsługiwała uruchamianie aplikacji bez wywołań sieci lub wejścia-wyjścia, nawet jeśli używano jej tylko podczas testów porównawczych. Pomaga to zapewnić jak najmniejszą zmienność między różnymi iteracjami testów porównawczych.

Jeśli Twoja aplikacja korzysta z technologii Hilt, możesz podać fałszywe implementacje z wejściem/wyjściem w testach porównawczych w mikroporównaniach i Hilt.

Obejmowanie wszystkich ważnych ścieżek użytkowników

Ważne jest dokładne uwzględnianie wszystkich ważnych ścieżek użytkowników w generowaniu profilu podstawowego. Ścieżki użytkowników, które nie zostały uwzględnione, nie zostaną poprawione przez profile bazowe. Najbardziej skuteczne profile bazowe obejmują wszystkie typowe ścieżki użytkowników biorących udział w startupie oraz te ścieżki użytkownika w aplikacji, w których wydajność jest zależne od wydajności, np. przewijane listy.