Przeprowadź analizę z użyciem renderowania GPU profilu

Narzędzie Profil renderowania GPU wskazuje względny czas potrzebny na wyrenderowanie poprzedniej klatki w przypadku każdego etapu potoku renderowania. Wiedza ta może pomóc w identyfikacji wąskich gardeł w potoku, dzięki czemu dowiesz się, co należy zoptymalizować, aby poprawić wydajność renderowania aplikacji.

Na tej stronie pokrótce wyjaśniono, co dzieje się na każdym etapie potoku, oraz omówiono problemy, które mogą powodować wąskie gardła. Zanim przeczytasz tę stronę, zapoznaj się z informacjami podanymi w sekcji Renderowanie GPU profilu. Aby dowiedzieć się, jak wszystkie etapy są ze sobą powiązane, warto sprawdzić, jak działa potok renderowania.

Reprezentacja wizualna

Narzędzie do renderowania GPU w profilu wyświetla etapy i ich względne czasy w postaci wykresu: kolorowego histogramu. Rysunek 1 przedstawia przykład takiego wyświetlacza.

Rysunek 1. Profil renderowania GPU

Każdy segment każdego pionowego słupka na wykresie renderowania GPU profilu reprezentuje etap potoku i jest wyróżniony określonym kolorem na wykresie słupkowym. Rysunek 2 przedstawia najważniejsze informacje o poszczególnych kolorach.

Rysunek 2. Legenda wykresu renderowania GPU

Gdy dowiesz się, co oznaczają poszczególne kolory, możesz ustawić kierowanie na określone aspekty aplikacji, aby spróbować zoptymalizować jej wydajność renderowania.

Etapy i ich znaczenie

W tej sekcji wyjaśniamy, co dzieje się na każdym etapie odpowiadającym kolorowi na Rysunku 2, a także wskazujemy, na jakie wąskie gardła należy zwrócić uwagę.

Obsługa danych wejściowych

Etap obsługi danych wejściowych w potoku mierzy, jak długo aplikacja przetwarzała zdarzenia wejściowe. Ten wskaźnik wskazuje, ile czasu aplikacja wydała na wykonanie kodu w wyniku wywołań zwrotnych zdarzeń wejściowych.

Gdy ten segment jest duży

Wysokie wartości w tym obszarze wynikają zwykle ze zbyt dużej ilości pracy lub zbyt skomplikowanej pracy wykonywanej w wywołaniach zwrotnych zdarzeń modułu obsługi danych wejściowych. Ponieważ wywołania zwrotne zawsze mają miejsce w wątku głównym, rozwiązania tego problemu skupiają się na bezpośredniej optymalizacji pracy lub przeciążeniu jej do innego wątku.

Warto też zauważyć, RecyclerViewże na tym etapie może być widoczne przewijanie. RecyclerView przewija stronę natychmiast po odebraniu zdarzenia dotknięcia. W efekcie może zwiększać lub uzupełniać liczbę nowych wyświetleń produktów. Z tego powodu ważne jest, aby zrobić to jak najszybciej. W dalszej analizie mogą pomóc narzędzia do profilowania, takie jak Traceview czy Systrace.

Animacja

W fazie animacji widać, ile czasu zajęło ocenie wszystkich animatorów działających w danej klatce. Najpopularniejsze animatory to ObjectAnimator, ViewPropertyAnimator i przejścia.

Gdy ten segment jest duży

Wysokie wartości w tym obszarze zazwyczaj wynikają z pracy wykonywanej z powodu pewnej zmiany właściwości animacji. Na przykład animacja przesuwania, która przewija stronę ListView lub RecyclerView, powoduje znaczne zawyżenie liczby wyświetleń i populację.

Pomiar/układ

Aby system Android rysował elementy widoku na ekranie, wykonuje 2 określone operacje na układach i widokach w hierarchii widoków.

Najpierw system mierzy wyświetlenia. Każdy widok i układ zawiera określone dane opisujące rozmiar obiektu na ekranie. Niektóre widoki mogą mieć określony rozmiar, inne mają swój rozmiar, który dostosowuje się do rozmiaru kontenera układu nadrzędnego.

Następnie system ustawia elementy widoku. Gdy system obliczy rozmiary wyświetleń podrzędnych, może kontynuować układ, dostosowywać rozmiar i pozycjonowanie widoków na ekranie.

System wykonuje pomiary i układ nie tylko w przypadku wybieranych widoków, lecz także nadrzędnych hierarchii tych widoków aż do poziomu głównego.

Gdy ten segment jest duży

Jeśli aplikacja poświęca dużo czasu na klatki w tym obszarze, zwykle wynika to z ogromnej liczby wyświetleń do uwzględnienia lub z problemów takich jak podwójne opodatkowanie w niewłaściwym miejscu w hierarchii. W każdym z tych przypadków rozwiązywanie problemów ze skutecznością polega na poprawie skuteczności hierarchii widoków.

Kod dodany do interfejsów onLayout(boolean, int, int, int, int) lub onMeasure(int, int) również może powodować problemy z wydajnością. Traceview i Systrace ułatwiają przeanalizowanie stosów wywołań w celu identyfikacji problemów z kodem.

Rysowanie

Na tym etapie operacje renderowania, takie jak rysowanie tła lub tekstu rysunkowego, są przekształcane w sekwencję natywnych poleceń rysowania. System rejestruje te polecenia i tworzy listę ich wyświetlania.

Pasek Rysuj rejestruje, ile czasu zajmuje przechwycenie poleceń na liście wyświetlania dla wszystkich widoków, które trzeba było zaktualizować na ekranie w tej ramce. Pomiar czasu ma zastosowanie do każdego kodu, który został dodany do obiektów interfejsu użytkownika w aplikacji. Przykładami takiego kodu mogą być onDraw(), dispatchDraw() i różne draw ()methods należące do podklas klasy Drawable.

Gdy ten segment jest duży

W uproszczeniu można zrozumieć te dane, pokazując, ile czasu zajęło wszystkim wywołaniem funkcji onDraw() w przypadku każdego unieważnionego widoku. Ten pomiar obejmuje czas poświęcony na wysyłanie poleceń rysowania do dzieci i elementy rysunkowe, które mogą być dostępne. Z tego powodu, gdy widzisz gwałtowny wzrost słupka, może to oznaczać, że kilka wyświetleń nagle zostało unieważnionych. Unieważnienie sprawia, że konieczne jest ponowne wygenerowanie list wyświetlanych widoków. Zbyt długi czas może być też spowodowany używaniem kilku niestandardowych widoków, które w metodach onDraw() mają bardzo skomplikowaną logikę.

Synchronizuj/prześlij

Wskaźnik „Synchronizuj i prześlij” reprezentuje czas potrzebny na przesłanie obiektów mapy bitowej z pamięci procesora do pamięci GPU podczas bieżącej klatki.

CPU i GPU składają się z różnych procesorów, więc mają różne obszary pamięci RAM przeznaczone do przetwarzania. Gdy rysujesz bitmapę na Androidzie, system przenosi ją do pamięci GPU, zanim będzie mógł ją wyrenderować na ekranie. Następnie GPU zapisuje bitmapę w pamięci podręcznej, dzięki czemu system nie musi ponownie przesyłać danych, chyba że tekstura zostanie usunięta z pamięci podręcznej tekstur GPU.

Uwaga: na urządzeniach Lollipop ten etap jest fioletowy.

Gdy ten segment jest duży

Wszystkie zasoby ramki muszą znajdować się w pamięci GPU, zanim będzie można ich użyć do narysowania klatki. Oznacza to, że wysoka wartość tego wskaźnika może oznaczać dużą liczbę niewielkich załadowań zasobów lub niewielką liczbę bardzo dużych zasobów. Częstym przypadkiem jest wyświetlanie przez aplikację pojedynczej bitmapy o rozmiarze zbliżonym do ekranu. Inny przypadek to sytuacja, w której aplikacja wyświetla dużą liczbę miniatur.

Aby go zmniejszyć, możesz zastosować następujące metody:

  • Upewnij się, że rozdzielczości bitmap nie są znacznie większe niż rozmiar, w którym będą wyświetlane. Na przykład aplikacja powinna unikać wyświetlania obrazu o wymiarach 1024 x 1024 jako obrazu o wymiarach 48 x 48.
  • Zastosowanie prepareToDraw() do asynchronicznego wstępnego przesyłania bitmapy przed następną fazą synchronizacji.

Wydaj polecenia

Segment Issue Commands (Polecenia dotyczące problemów) pokazuje czas potrzebny na wystawienie wszystkich poleceń niezbędnych do wyświetlenia list wyświetlanych na ekranie.

Aby system mógł wyświetlać listy wyświetlania na ekranie, wysyła do GPU wymagane polecenia. Zwykle wykonuje to działanie za pomocą interfejsu API OpenGL ES.

Ten proces może trochę potrwać, ponieważ system wykonuje końcowe przekształcenie i przycinanie każdego polecenia przed wysłaniem polecenia do GPU. Po stronie GPU pojawia się dodatkowy nadmiar, który generuje ostatnie polecenia. Te polecenia obejmują ostateczne przekształcenia i dodatkowe przycinanie.

Gdy ten segment jest duży

Czas spędzony na tym etapie jest bezpośrednią miarą złożoności i liczby list reklam displayowych, które system renderuje w danej ramce. Na przykład wykonywanie wielu operacji rysowania, zwłaszcza w przypadkach, gdy każdy rysowany obiekt podstawowy wiąże się z niewielkim kosztem, może zwiększyć ten czas. Na przykład:

Kotlin

for (i in 0 until 1000) {
    canvas.drawPoint()
}

Java

for (int i = 0; i < 1000; i++) {
    canvas.drawPoint()
}

jest znacznie droższe niż:

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

Nie zawsze istnieje korelacja 1:1 między wydawaniem poleceń a tworzeniem list wyświetlanych. W przeciwieństwie do poleceń Issue Commands, które rejestrują czas potrzebny na wysłanie poleceń rysowania do GPU, dane Draw pokazują czas potrzebny na zarejestrowanie wydanych poleceń na liście wyświetlania.

Dzieje się tak, ponieważ tam, gdzie to możliwe, listy wyświetlania są zapisywane w pamięci podręcznej systemu. W związku z tym, w sytuacjach, gdy przewijanie, przekształcanie lub animacja wymaga od systemu ponownego wysłania wyświetlanej listy, ale nie musi jej od nowa tworzyć, zamiast ponownie przechwytywać polecenia rysowania. W takiej sytuacji pasek „Problemy z poleceniami” jest widoczny bez wysokiego paska Polecenia rysowania.

Proces/wymiana buforów

Gdy Android zakończy przesyłanie całej listy wyświetlania do GPU, system wydaje ostatnie polecenie informujące sterownik karty, że korzysta z bieżącej klatki. W tym momencie kierowca może wreszcie wyświetlić zaktualizowany obraz na ekranie.

Gdy ten segment jest duży

Pamiętaj, że procesor graficzny jest wykonywany równolegle z procesorem. Problemy z systemem Android powodują wyświetlanie poleceń na GPU, a następnie przechodzi do następnego zadania. GPU odczytuje te polecenia z kolejki i przetwarza je.

W sytuacjach, gdy CPU wydaje polecenia szybciej niż GPU je wykorzystuje, kolejka komunikacji między procesorami może być pełna. W takim przypadku procesor blokuje się i czeka na kolejne polecenie w kolejce, aż będzie dostępne miejsce. Ten stan pełnej kolejki pojawia się często na etapie Zamień bufory, ponieważ w tym momencie została przesłana cała liczba poleceń.

Kluczem do rozwiązania tego problemu jest ograniczenie złożoności pracy wykonywanej na GPU, podobnie jak w przypadku fazy „Issue Commands”.

Inne

Poza czasem potrzebnym systemowi renderowania na wykonanie zadania w wątku głównym występuje dodatkowy zestaw działań, który nie ma nic wspólnego z renderowaniem. Czas zużywany przez tę pracę jest raportowany jako Różny czas. Różny czas oznacza zwykle pracę, która może być wykonywana w wątku UI między 2 kolejnymi klatkami renderowania.

Gdy ten segment jest duży

Jeśli ta wartość jest wysoka, prawdopodobnie aplikacja ma wywołania zwrotne, intencje lub inne działania, które powinny być wykonywane w innym wątku. Narzędzia takie jak śledzenie metody czy Systrace zapewniają widoczność zadań wykonywanych w wątku głównym. Te informacje pomogą Ci poprawić skuteczność.