Profilowanie gry WebGL za pomocą flagi about:tracing

Lilli Thompson
Lilli Thompson

Jeśli nie możesz go zmierzyć, nie będzie można go poprawić.

Lord Kelwin

Aby gry HTML5 działały szybciej, musisz najpierw wykryć wąskie gardła wydajności, ale to niełatwe zadanie. Ocena liczby klatek na sekundę (FPS) to początek, ale aby zobaczyć pełny obraz sytuacji, musisz zrozumieć niuanse działań związanych z Chrome.

Narzędzie about:tracing dostarcza statystyk, które pomagają uniknąć pośpiechu i skutecznych rozwiązań, które mają na celu poprawę wydajności, ale są to w zasadzie słuszne zgadywanie. Zaoszczędzisz dużo czasu i energii, uzyskasz lepszy obraz tego, co Chrome robi z każdą klatką, i wykorzysta te informacje do optymalizacji gry.

Informacje o:śledzeniu

Narzędzie o: Wiele funkcji w Chrome jest wyposażonych w śledzenie od razu po uruchomieniu, więc bez konieczności ręcznego śledzenia możesz nadal używać funkcji about:tracing, aby śledzić swoje wyniki. (więcej informacji znajdziesz w dalszej sekcji o ręcznej instrumentacji kodu JS).

Aby wyświetlić widok śledzenia, po prostu wpisz „about:tracing” w omniboksie (pasku adresu) Chrome.

omniboks w Chrome
Wpisz „about:tracing”. w omniboksie Chrome

Korzystając z narzędzia do śledzenia, możesz rozpocząć nagrywanie, uruchomić grę przez kilka sekund, a następnie wyświetlić dane logu czasu. Oto przykład, jak mogą wyglądać dane:

Prosty wynik śledzenia
Prosty wynik śledzenia

Tak, to mylące. Porozmawiajmy o tym, jak je odczytać.

Każdy wiersz reprezentuje proces, który jest profilowany, oś lewa-prawa wskazuje czas, a każde kolorowe pole to zinstrumentowane wywołanie funkcji. Znajdują się tu wiersze z wieloma różnymi rodzajami zasobów. Najważniejsze z nich w przypadku profilowania gier to CrGpuMain, który pokazuje, co robi procesor graficzny (GPU), oraz CrRendererMain. Każdy log czasu zawiera wiersze CrRendererMain dla każdej otwartej karty w okresie śledzenia (w tym dla samej karty about:tracing).

Odczytując dane logu czasu, pierwszym zadaniem jest określenie, który wiersz CrRendererMain odpowiada Twojej grze.

Wyróżniony wynik prostego śledzenia
Wyróżniony wynik prostego śledzenia

W tym przykładzie 2 kandydaci to: 2216 i 6516. Niestety obecnie nie ma żadnego dopracowanego sposobu na znalezienie aplikacji z wyjątkiem wiersza, który jest poddawany okresowym aktualizacjom (lub jeśli kod został ręcznie instrumentowany za pomocą punktów śledzenia, aby znaleźć wiersz zawierający dane logu czasu). W tym przykładzie wygląda na to, że 6516 wykonuje główną pętlę z częstotliwością aktualizacji. Jeśli przed rozpoczęciem śledzenia zamkniesz wszystkie inne karty, znalezienie właściwej wartości CrRendererMain będzie łatwiejsze. Wciąż mogą się jednak pojawić wiersze CrRendererMain dotyczące procesów innych niż Twoja gra.

Znajdowanie ramki

Gdy znajdziesz właściwy wiersz w narzędziu do śledzenia postępów w grze, kolejnym krokiem jest znalezienie głównej pętli. Główna pętla wygląda jak powtarzający się wzorzec w danych śledzenia. Możesz poruszać się po danych śledzenia za pomocą klawiszy W, A, S, D: A i D do poruszania się w lewo lub w prawo (w przód i w prawo) oraz W i S do powiększania i pomniejszania danych. Można oczekiwać, że główna pętla będzie powtarzała się co 16 milisekund, jeśli gra działa z częstotliwością 60 Hz.

Wygląda na to, że są to trzy ramki wykonania
Wygląda na 3 ramki wykonania

Po znalezieniu rytmu gry możesz zobaczyć, jak dokładnie działa Twój kod w każdej klatce. Używaj klawiszy W, A, S i D, aby powiększać widok do czasu, aż tekst pojawi się w polach funkcji.

Zagłębianie się w ramkę wykonywania
Zagłębianie się w ramkę wykonywania

Ten zbiór pól zawiera serię wywołań funkcji, z których każde wywołanie jest przedstawione za pomocą kolorowego pola. Każda funkcja została wywołana przez pole nad nią, więc w tym przypadku możesz zobaczyć wiadomość MessageLoop::RunTask o nazwie RenderWidget::OnReplaceBuffersComplete, która z kolei nazywa się RenderWidget::DoDeferredUpdate itd. Odczytując te dane, możesz mieć pełny obraz tego, jak się nazywano i jak długo trwało każde wykonanie.

Ale tutaj zaczyna się kleić. Informacje udostępniane przez funkcję about:tracing to nieprzetworzone wywołania funkcji z kodu źródłowego Chrome. Możesz wyciągać wnioski na temat tego, jak działa każda funkcja, biorąc pod uwagę jej nazwę, ale informacje nie są zbyt przyjazne dla użytkownika. Zobaczenie całego ruchu w klatce jest przydatne, ale aby dowiedzieć się, co się dzieje, potrzebujesz czegoś bardziej czytelnego dla człowieka.

Dodawanie tagów śledzenia

Na szczęście istnieje przyjazny sposób dodawania do kodu narzędzi ręcznych do tworzenia danych logu czasu: console.time i console.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

Powyższy kod tworzy w nazwie widoku śledzenia nowe pola z określonymi tagami, więc jeśli uruchomisz aplikację ponownie, zobaczysz komunikat „update” i „renderowanie” pól, które wskazują czas, który upływa między wywołaniem początkowym a końcowym dla każdego tagu.

Tagi dodane ręcznie
Tagi dodane ręcznie

Korzystając z tego rozwiązania, możesz tworzyć czytelne dla człowieka dane śledzenia, które pozwolą Ci śledzić hotspoty w kodzie.

GPU czy CPU?

W przypadku grafiki z akceleracją sprzętową jedno z najważniejszych pytań, jakie należy sobie zadać podczas profilowania, to: czy kod jest powiązany z GPU czy procesorem? Każda klatka wymaga renderowania w układzie GPU i procesie działania logiki. aby zrozumieć, co sprawia, że gra spowalnia Twoją grę, musisz znaleźć równowagę między tymi dwoma zasobami.

Najpierw znajdź w widoku śledzenia wiersz o nazwie CrGPUMain, który wskazuje, czy w określonym momencie GPU jest zajęty.

Ślady GPU i procesora

Jak widać, każda klatka w grze powoduje, że procesor działa w CrRendererMain oraz w GPU. Powyższy log czasu pokazuje bardzo prosty przypadek użycia: procesor i GPU są nieaktywne przez większość każdej klatki (16 ms).

Widok śledzenia jest bardzo przydatny, gdy masz grę, która działa powoli i nie wiesz, który zasób wykorzystujesz. Kluczem do debugowania jest znalezienie związku między GPU i CPU. Użyj takiego samego przykładu jak poprzednio, ale w pętli aktualizacji wzbogać go o kilka dodatkowych zmian.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Teraz zobaczysz ślad podobny do tego:

Ślady GPU i procesora

Co wynika z tego śladu? Widać, że klatka na ilustracji trwa od około 2270 do 2320 ms, co oznacza, że każda klatka trwa około 50 ms (z częstotliwością 20 Hz). Obok okna aktualizacji widać fragmenty kolorowych pól reprezentujących funkcję renderowania, ale ramka jest całkowicie zdominowana przez samą aktualizację.

W przeciwieństwie do CPU możesz zobaczyć, że GPU przez większość każdej klatki jest wciąż nieaktywny. Aby zoptymalizować ten kod, możesz poszukać operacji, które można wykonać w kodzie cieniowania, i przenieść je do GPU, aby maksymalnie wykorzystać zasoby.

A co, jeśli sam kod do cieniowania działa wolno i GPU jest przeciążony? A co, jeśli usuniemy niepotrzebną pracę z CPU i dodamy trochę pracy do kodu cieniowania fragmentów? Oto niepotrzebnie drogi program do cieniowania fragmentów:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

Jak wygląda ślad kodu za pomocą tego programu do cieniowania?

Ślady GPU i procesora w przypadku korzystania z powolnego kodu GPU
Ślady procesora i GPU w przypadku korzystania z powolnego kodu GPU

Tutaj również zwróć uwagę na czas trwania klatki. W tym przypadku wzorzec powtarza się od 2750 do 2950 ms, przez co czas trwania wynosi 200 ms (częstotliwość klatek około 5 Hz). Wiersz CrRendererMain jest prawie całkowicie pusty, co oznacza, że procesor jest przez większość czasu nieaktywny, gdy GPU jest przeciążony. To pewny znak, że Twoje cieniowanie są za ciężkie.

Jeśli nie będziesz wiedzieć, co dokładnie powoduje małą liczbę klatek, zauważysz aktualizację 5 Hz i będziesz mieć ochotę zajrzeć do kodu gry i zacząć próbować zoptymalizować lub usunąć logikę gry. W tym przypadku nie ma to sensu, ponieważ logika w pętli gry nie jest tym, co pochłania czas. Z tego logu czasu wynika, że większa moc obliczeniowa procesora byłaby w zasadzie bezpłatna. Procesor jest zablokowany, więc większa praca nie wpłynie na czas renderowania klatki.

Prawdziwe przykłady

Zobaczmy teraz, jak wyglądają dane śledzenia z prawdziwej gry. Jedną z fajnych zalet gier tworzonych przy użyciu otwartych technologii internetowych jest możliwość sprawdzenia, co dzieje się w swoich ulubionych usługach. Jeśli chcesz przetestować narzędzia do profilowania, możesz wybrać ulubiony tytuł WebGL w Chrome Web Store i profilować go za pomocą funkcji about:tracing. Oto przykładowy zrzut ekranu ze świetnej gry Skid Racer na platformie WebGL.

Śledzenie prawdziwej gry
Śledzenie prawdziwej gry

Wygląda na to, że każda klatka trwa około 20 ms, co oznacza, że liczba klatek na sekundę to około 50 kl./s. Widać, że nakład pracy jest równoważony między CPU i GPU, ale GPU jest zasobem, który jest najbardziej obciążony. Jeśli chcesz zobaczyć, jak to jest profilowanie gier WebGL, wypróbuj niektóre z tytułów z Chrome Web Store stworzonych przy użyciu WebGL, w tym:

Podsumowanie

Jeśli chcesz, aby gra działała z częstotliwością 60 Hz, w przypadku każdej klatki wszystkie operacje muszą mieścić się w czasie 16 ms procesora i 16 ms czasu GPU. Masz 2 zasoby, z których można korzystać równolegle. Możesz przełączać się między nimi, aby zmaksymalizować wydajność. Widok o:

Co dalej?

Oprócz GPU możesz też śledzić inne części środowiska wykonawczego Chrome. Chrome Canary, wczesna wersja Chrome, jest przystosowana do śledzenia IO, IndexedDB i kilku innych działań. Aby lepiej zrozumieć bieżący stan śledzenia zdarzeń, przeczytaj ten artykuł w Chromium.

Jeśli tworzysz gry internetowe, obejrzyj film poniżej. Prezentacja prowadzona przez zespół Google Game Developer Advocate na konferencji GDC 2012 na temat optymalizacji wydajności gier w Chrome: