Przeprowadź analizę z użyciem renderowania GPU profilu

Narzędzie Profil renderowania GPU wskazuje względny czas, jaki zajmuje renderowanie poprzedniej klatki na każdym etapie potoku renderowania. Ta wiedza może pomóc Ci zidentyfikować wąskie gardła w potoku, dzięki czemu możesz zoptymalizować wydajność renderowania aplikacji.

Na tej stronie znajdziesz krótkie wyjaśnienie, co dzieje się na każdym etapie potoku, oraz omówienie problemów, które mogą powodować wąskie gardła. Zanim przeczytasz tę stronę, zapoznaj się z informacjami podanymi na stronie Szybkość renderowania profilu GPU. Aby zrozumieć, jak wszystkie etapy są ze sobą powiązane, warto zapoznać się z  działaniem potoku renderowania.

Prezentacja wizualna

Narzędzie Profil renderowania GPU wyświetla etapy i ich względne czasy w postaci wykresu: histogramu oznaczonego kolorami. Na ilustracji 1 pokazano przykład takiego wyświetlania.

Wykres profilu renderowania GPU
Rysunek 1. Wykres profilu renderowania GPU

Każdy segment każdego słupka pionowego wyświetlanego na wykresie Renderowanie GPU profilu reprezentuje etap potoku i jest wyróżniony określonym kolorem na wykresie słupkowym. Na rysunku 2 znajduje się legenda wyjaśniająca znaczenie poszczególnych kolorów.

Legenda wykresu profilu renderowania GPU
Rysunek 2. Legenda wykresu profilu renderowania GPU

Gdy zrozumiesz, co oznacza każdy kolor, możesz kierować reklamy na określone aspekty aplikacji, aby zoptymalizować jej wydajność renderowania.

Etapy i ich znaczenie

W tej sekcji opisujemy, co dzieje się na każdym etapie, a także przyczyny wąskich gardeł, na które należy zwrócić uwagę.

Obsługa danych wejściowych

Etap obsługi danych wejściowych w potoku pomiarowym określa, ile czasu aplikacja poświęciła na obsługę zdarzeń wprowadzania danych. Te dane wskazują, jak długo aplikacja wykonywała kod wywoływany w wyniku wywołań zwrotnych zdarzeń wejściowych.

Gdy ten segment jest duży

Wysokie wartości w tym obszarze są zwykle wynikiem zbyt dużej lub zbyt złożonej pracy wykonywanej w wywołaniach zwrotnych zdarzeń obsługi danych wejściowych. Ponieważ te wywołania zwrotne zawsze występują w głównym wątku, rozwiązania tego problemu koncentrują się na optymalizacji pracy bezpośrednio lub przeniesieniu jej do innego wątku.

Przewijanie LazyColumn lub LazyRow może również pojawić się w tej fazie. Gdy dotknięcie użytkownika zostanie zakwalifikowane jako przewijanie, leniwa lista będzie wykorzystywać zdarzenia dotknięcia do dynamicznego tworzenia i układania elementów. Jeśli aplikacja wykonuje niestandardowe działania w odpowiedzi na zmiany pozycji przewijania, ważne jest, aby operacja ta przebiegała jak najszybciej, co pozwoli uniknąć utraty klatek. Narzędzia do profilowania, takie jak CPU Profiler w Android Studio czy Perfetto, mogą pomóc Ci w dalszym badaniu problemu. Więcej informacji znajdziesz w sekcji Omówienie śledzenia systemu.

Animacje

Faza animacji pokazuje, ile czasu zajęło sprawdzenie wszystkich stanów animacji działających w tej klatce. Niektóre popularne interfejsy API animacji w Compose to animate*AsState, TransitionAnimatable. Dodatkowo w tej fazie działa Recomposer, który przetwarza zmiany stanu migawki i aktualizuje kompozycje. Oznacza to, że narzut związany z rekompozycją często pojawia się bezpośrednio na etapie animacji.

W przypadku interfejsów Jetpack Compose uwzględnij bibliotekę Compose Runtime Tracing, aby wyświetlać szczegółowe ślady kompozycji wraz ze zdarzeniami systemowymi.

Gdy ten segment jest duży

Wysokie wartości w tym obszarze są zwykle wynikiem pracy wykonywanej z powodu zmian stanu wywołanych przez animację. Na przykład animacja przesunięcia, która przewija LazyColumn lub LazyRow, powoduje szybkie tworzenie, pomiar i przydzielanie nowych elementów listy.

Wskaźniki

Aby narysować komponenty na ekranie, Android wykonuje 3 fazy w węzłach układu w drzewie interfejsu.

Najpierw system mierzy węzły układu. Każdy element kompozycyjny ma określone ograniczenia i modyfikatory, które opisują limity rozmiaru obiektu na ekranie. Niektóre funkcje kompozycyjne mogą mieć określony, stały rozmiar, a inne dostosowują się do ograniczeń przekazywanych przez layout nadrzędny.

Po drugie, system umieszcza węzły układu. Gdy w fazie pomiaru Compose obliczy rozmiary węzłów podrzędnych, może przejść do fazy umieszczania, w której określa rozmiary i pozycje węzłów układu na ekranie.

System zawsze wykonuje ten jednoprzebiegowy układ, aby zapewnić wydajność. Gdy układ kompozycyjny zostanie unieważniony, Compose mierzy ten konkretny węzeł i przekazuje aktualizacje układu do hierarchii nadrzędnych tylko wtedy, gdy element podrzędny zmieni rozmiar lub ograniczenia.

Gdy ten segment jest duży

Duży segment w tym obszarze oznacza, że aplikacja spędza zbyt dużo czasu w fazie układu, która polega na pozycjonowaniu i określaniu rozmiaru węzłów układu. Obejmują one wykonywanie modyfikatorów pomiaru i umieszczania w przypadku komponentów, co może opóźnić przygotowanie klatki, jeśli drzewo układu jest zbyt złożone. W takich przypadkach poprawa wydajności wymaga przeprowadzenia testów porównawczych aplikacji napisanej w Compose i stosowania sprawdzonych metod dotyczących wydajności.

Użyj CPU Profilera w Android Studio lub Perfetto, aby sprawdzić przebiegi układu i zidentyfikować wąskie gardła. Więcej informacji znajdziesz w omówieniu śledzenia systemu.

Narysuj

Etap rysowania przekształca operacje renderowania, takie jak rysowanie tła, kształtu lub tekstu, w sekwencję natywnych poleceń rysowania. System rejestruje te polecenia na liście wyświetlania do wykonania przez procesor graficzny.

Pasek Rysowanie rejestruje, ile czasu zajmuje przechwytywanie poleceń do listy wyświetlania dla wszystkich węzłów układu, które musiały zostać zaktualizowane na ekranie w tej klatce. Zmierzony czas dotyczy też niestandardowej logiki rysowania, która może znajdować się w modyfikatorach rysowania lub w kompozycji Canvas.

Gdy ten segment jest duży

W uproszczeniu można powiedzieć, że te dane pokazują, ile czasu zajęło wykonanie wszystkich poleceń rysowania dla każdego unieważnionego węzła układu. Obejmuje to czas potrzebny na wysłanie tych poleceń do węzłów podrzędnych i wektorowych elementów rysowalnych. Z tego powodu, gdy zobaczysz nagły wzrost na tym pasku, przyczyną może być nagłe unieważnienie wielu funkcji kompozycyjnych. Unieważnienie powoduje konieczność ponownego wykonania poleceń rysowania i ponownego wygenerowania list wyświetlania węzłów układu. Długi czas może też wynikać z kilku niestandardowych funkcji kompozycyjnych lub obszarów rysowania, które mają bardzo złożoną logikę w swojej DrawScope implementacji.

Dodatkowo Compose często obsługuje wewnętrzne pomiary i przekazy układu w ramach tego, co platforma uważa za fazę rysowania. W związku z tym wysoki poziom paska rysowania może być spowodowany kosztownymi lub nadmiernymi operacjami pomiaru/układu wewnętrznego, a nie tylko poleceniami rysowania. W razie wątpliwości zarejestruj ślad Perfetto, aby sprawdzić, czy obciążenie wynika z procedur rysowania czy z pomiarów i przebiegów układu w Compose.

Prześlij

Wskaźnik przesyłania odzwierciedla czas potrzebny na przeniesienie obiektów bitmapy z pamięci CPU do pamięci GPU w bieżącej klatce.

Procesor i GPU to różne procesory, 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 GPU może ją wyrenderować na ekranie. Następnie procesor graficzny zapisuje mapę bitową 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 procesora graficznego.

Uwaga: na urządzeniach z Lollipopem ten etap jest fioletowy.

Gdy ten segment jest duży

Zanim będzie można użyć zasobów do narysowania ramki, muszą one znajdować się w pamięci GPU. Oznacza to, że wysoka wartość tego rodzaju danych może oznaczać dużą liczbę małych zasobów lub małą liczbę bardzo dużych zasobów. Częstym przypadkiem jest wyświetlanie przez aplikację pojedynczej mapy bitowej o rozmiarze zbliżonym do rozmiaru ekranu. Inny przypadek to sytuacja, w której aplikacja wyświetla dużą liczbę miniatur.

Aby zmniejszyć ten pasek, możesz zastosować takie techniki jak:

  • Upewnij się, że rozdzielczość map bitowych nie jest znacznie większa niż rozmiar, w jakim będą wyświetlane. Na przykład unikaj wyświetlania obrazu o rozmiarze 1024 x 1024 jako obrazu o rozmiarze 48 x 48.
  • Korzystanie z nowoczesnych bibliotek, takich jak Coil, do asynchronicznego wstępnego przesyłania mapy bitowej przed następną fazą synchronizacji.

Wydawanie poleceń

Segment poleceń problemu reprezentuje czas potrzebny na wydanie wszystkich poleceń niezbędnych do narysowania list wyświetlania na ekranie.

Aby system mógł rysować listy wyświetlania na ekranie, wysyła do procesora graficznego niezbędne polecenia. Zwykle wykonuje to działanie za pomocą interfejsu OpenGL ES.

Ten proces zajmuje trochę czasu, ponieważ system wykonuje ostateczne przekształcenie i przycinanie każdego polecenia przed wysłaniem go do procesora graficznego. Po stronie procesora graficznego pojawia się dodatkowy narzut, który oblicza ostateczne 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 wyświetlania, które system renderuje w danej klatce. Na przykład wiele operacji rysowania, zwłaszcza w przypadku, gdy każdy element rysowania ma niewielki koszt, może wydłużyć ten czas. Przykład:

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

jest znacznie droższe w wydaniu niż:

canvas.drawPoints(thousandPointArray)

Nie zawsze istnieje korelacja 1:1 między wydawaniem poleceń a rzeczywistym rysowaniem list wyświetlania. W przeciwieństwie do paska poleceń dotyczących problemów, który rejestruje czas potrzebny na wysłanie poleceń rysowania do procesora graficznego, dane rysowania reprezentują czas potrzebny na przechwycenie wydanych poleceń na liście wyświetlania.

Ta różnica wynika z tego, że listy wyświetlania są w miarę możliwości buforowane przez system. W rezultacie w niektórych sytuacjach przewijanie, przekształcanie lub animacja wymagają ponownego wysłania listy wyświetlania przez system, ale nie wymagają jej ponownego tworzenia od zera (ponownego przechwytywania poleceń rysowania). W rezultacie możesz zobaczyć wysoki słupek poleceń dotyczących problemów bez wysokiego słupka poleceń dotyczących rysowania.

Zamiana buforów

Gdy Android zakończy przesyłanie listy wyświetlania do procesora graficznego, system wyśle ostatnie polecenie, aby poinformować sterownik graficzny, że bieżąca klatka została przetworzona. W tym momencie sterownik może wreszcie wyświetlić zaktualizowany obraz na ekranie.

Gdy ten segment jest duży

Ważne jest, aby pamiętać, że procesor graficzny wykonuje pracę równolegle z procesorem. System Android wysyła polecenia rysowania do procesora graficznego, a potem przechodzi do następnego zadania. Procesor graficzny odczytuje te polecenia rysowania z kolejki i je przetwarza.

W sytuacjach, gdy procesor wysyła polecenia szybciej niż procesor graficzny je przetwarza, kolejka komunikacyjna między procesorami może się zapełnić. W takim przypadku procesor blokuje się i czeka, aż w kolejce zwolni się miejsce na kolejne polecenie. Ten stan pełnej kolejki często występuje na etapie zamiany buforów, ponieważ w tym momencie przesłano już polecenia dotyczące całej klatki.

Kluczem do rozwiązania tego problemu jest zmniejszenie złożoności pracy wykonywanej na GPU, podobnie jak w przypadku fazy poleceń problemowych.

Pozostałe postanowienia

Oprócz czasu potrzebnego systemowi renderowania na wykonanie pracy na wątku głównym występuje dodatkowy zestaw działań, które nie mają nic wspólnego z renderowaniem. Czas poświęcony na tę pracę jest raportowany jako czas nieokreślony. Różne czasy zwykle oznaczają pracę, która może być wykonywana w wątku UI między dwoma kolejnymi klatkami renderowania.

Gdy ten segment jest duży

Jeśli ta wartość jest wysoka, prawdopodobnie aplikacja ma wywołania zwrotne, intencje lub inne zadania, które powinny być wykonywane w innym wątku. Narzędzia takie jak profiler procesora w Android Studio czy Perfetto mogą zapewnić wgląd w zadania uruchomione w głównym wątku. Te informacje pomogą Ci ukierunkować działania na poprawę skuteczności. Więcej informacji znajdziesz w omówieniu śledzenia systemu.