Jeśli nie możesz czegoś zmierzyć, nie możesz tego poprawić.
Lord Kelvin
Aby przyspieszyć działanie gier w formacie HTML5, musisz najpierw zlokalizować wąskie gardła, co może być trudne. Ocena danych o liczbie klatek na sekundę (FPS) to dobry początek, ale aby uzyskać pełny obraz, musisz zrozumieć niuanse związane z aktywnościami w Chrome.
Narzędzie about:tracing
zapewnia informacje, które pomogą Ci uniknąć pochopnych obejść, które mają na celu poprawę skuteczności, ale są w podstawie dobrze pojętą zgadującą. Zaoszczędzisz dużo czasu i energii, uzyskasz lepszy obraz tego, co Chrome robi z każdą klatką, i użyjesz tych informacji do optymalizacji gry.
Cześć about:tracing
Narzędzie about:tracing
w Chrome pozwala zobaczyć wszystkie działania w Chrome z określonego okresu w tak szczegółowy sposób, że na początku może to przytłoczyć. Wiele funkcji w Chrome jest już gotowych do śledzenia, więc bez ręcznego instrumentowania możesz nadal używać about:tracing
do śledzenia wydajności. (patrz sekcja o ręcznym instrumentowaniu kodu JS)
Aby wyświetlić widok śledzenia, wpisz „about:tracing” w szukarce Chrome (pasku adresu).
Za pomocą narzędzia do śledzenia możesz rozpocząć nagrywanie, uruchomić grę na kilka sekund, a potem wyświetlić dane śledzenia. Oto przykład tego, jak mogą wyglądać dane:
Tak, to naprawdę mylące. Porozmawiajmy o tym, jak czytać takie raporty.
Każdy wiersz reprezentuje profilowany proces, oś od lewej do prawej wskazuje czas, a każde kolorowe pole to wywołanie funkcji z instrumentacją. Zawiera ona wiersze dotyczące różnych rodzajów zasobów. Najciekawszymi funkcjami do profilowania gier są CrGpuMain, która pokazuje, co robi procesor graficzny (GPU), oraz CrRendererMain. Każdy ślad zawiera wiersze CrRendererMain dla każdej otwartej karty w okresie śledzenia (w tym dla samej karty about:tracing
).
Podczas odczytu danych z wykresu pierwszym zadaniem jest określenie, który wiersz CrRendererMain odpowiada Twojej grze.
W tym przykładzie 2 kandydatki to 2216 i 6516. Obecnie nie ma niestety dokładnego sposobu na wybranie aplikacji, z wyjątkiem szukania linii, która często jest aktualizowana (lub, jeśli kod został ręcznie wyposażony w punkty śledzenia, szukania linii zawierającej dane śledzenia). W tym przykładzie wygląda na to, że 6516 uruchamia główny cykl na podstawie częstotliwości aktualizacji. Jeśli przed rozpoczęciem śledzenia zamkniesz wszystkie inne karty, łatwiej będzie znaleźć odpowiedni proces CrRendererMain. Nadal jednak mogą występować wiersze CrRendererMain dla procesów innych niż gra.
Znajdowanie ramki
Gdy znajdziesz odpowiedni wiersz w narzędziu do śledzenia gry, znajdź główny cykl. Główny cykl wygląda jak powtarzający się w danych śledzenia wzór. Dane śledzenia możesz przeglądać za pomocą klawiszy W, A, S i D: A i D służą do przewijania w lewo i w prawo (wstecz i do przodu w czasie), a W i S – do powiększania i pomniejszania danych. Jeśli gra działa z częstotliwością 60 Hz, główna pętla powinna być wzorcem powtarzanym co 16 milisekund.
Po znalezieniu informacji o częstotliwości próbkowania gry możesz sprawdzić, co dokładnie robi Twój kod w każdej klatce. Używaj klawiszy W, A, S, D, aby powiększać obraz, aż będzie można odczytać tekst w polach funkcji.
Ta kolekcja pudełek pokazuje serię wywołań funkcji, a każde wywołanie jest reprezentowane przez kolorowe pudełko. Każda funkcja została wywołana przez pole nad nią, więc w tym przypadku widać, że MessageLoop::RunTask wywołało RenderWidget::OnSwapBuffersComplete, które z kolei wywołało RenderWidget::DoDeferredUpdate i tak dalej. Dzięki tym danym możesz uzyskać pełny obraz tego, co wywołało co i jak długo trwało każde wykonanie.
Ale tutaj jest trochę trudniej. Informacje udostępniane przez about:tracing
to nieprzetworzone wywołania funkcji z kodu źródłowego Chrome. Na podstawie nazwy można się domyślić, do czego służy dana funkcja, ale informacje te nie są zbyt przyjazne użytkownikom. Przydatne jest, aby zobaczyć ogólny przepływ danych w ramce, ale aby zrozumieć, co się dzieje, potrzebujesz czegoś bardziej czytelnego dla człowieka.
Dodawanie tagów śledzenia
Na szczęście istnieje wygodny sposób ręcznego dodawania instrumentacji do kodu, aby tworzyć dane śladu: console.time
i console.timeEnd
.
console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");
Powyższy kod tworzy nowe pola w nazwie widoku śledzenia z określonymi tagami, więc jeśli ponownie uruchomisz aplikację, zobaczysz pola „update” (aktualizacja) i „render” (renderowanie), które pokazują czas upływający między wywołaniami początku i końca dla każdego tagu.
Dzięki temu możesz tworzyć czytelne dla człowieka dane śledzenia, aby śledzić newralgicznie miejsca w kodzie.
GPU czy CPU?
W przypadku grafiki z akceleracją sprzętową jednym z najważniejszych pytań, które możesz zadać podczas profilowania, jest: czy ten kod jest związany z procesorem GPU czy z procesorem CPU? W przypadku każdej klatki część renderowania będzie wykonywana na GPU, a część logiki na procesorze. Aby zrozumieć, co spowalnia grę, musisz sprawdzić, jak jest rozłożona praca między tymi dwoma zasobami.
Najpierw znajdź w widoku śledzenia wiersz o nazwie CrGPUMain, który wskazuje, czy procesor GPU jest zajęty w określonym momencie.
Widać, że każdy klatkę gry powoduje pracę procesora w funkcji CrRendererMain, a także na karcie graficznej. Powyższy ślad pokazuje bardzo prosty przypadek użycia, w którym procesor i procesor graficzny są nieaktywne przez większość 16-msowego interwału.
Widok śledzenia jest bardzo przydatny, gdy gra działa wolno, a nie wiesz, który zasób jest maksymalnie wykorzystywany. Kluczem do debugowania jest sprawdzenie, jak linie GPU i CPU się ze sobą łączą. Weźmy ten sam przykład co wcześniej, ale dodamy trochę dodatkowej pracy w pętli aktualizacji.
console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");
console.time("render");
render();
console.timeEnd("render");
Teraz zobaczysz ślad wyglądający tak:
Co wynika z tego śledzenia? Widać, że czas wyświetlania klatki zmienia się z 2270 ms na 2320 ms, co oznacza, że każda klatka zajmuje około 50 ms (częstotliwość wyświetlania klatek wynosi 20 Hz). Obok pola aktualizacji widać fragmenty kolorowych pól, które reprezentują funkcję renderowania, ale ramka jest całkowicie wypełniona przez aktualizację.
W przeciwieństwie do tego, co dzieje się z procesorem, procesor graficzny pozostaje nieaktywny przez większość czasu trwania każdego klatki. Aby zoptymalizować ten kod, możesz poszukać operacji, które można wykonać w kodzie shadera, i przekazać je do procesora graficznego, aby optymalnie wykorzystać zasoby.
A co, jeśli kod shadera jest powolny, a GPU jest przeciążone? Co jeśli usuniemy zbędne zadania z procesora, a zamiast tego dodamy trochę pracy do kodu fragmentu shadera? Oto niepotrzebnie drogi shader fragmentu:
#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 korzystającego z tego shadera?
Ponownie zwróć uwagę na czas trwania ramki. Tutaj powtarzający się wzór trwa od około 2750 ms do 2950 ms, czyli przez 200 ms (częstotliwość klatek około 5 Hz). Linia CrRendererMain jest prawie pusta, co oznacza, że procesor jest przez większość czasu nieaktywny, a procesor graficzny jest przeciążony. To pewny znak, że Twoje shadery są zbyt ciężkie.
Jeśli nie wiesz, co dokładnie powoduje niską liczbę klatek na sekundę, możesz zauważyć aktualizację 5 Hz i zechcieć przejrzeć kod gry, aby zoptymalizować lub usunąć logikę gry. W tym przypadku nie przyniosłoby to żadnych rezultatów, ponieważ nie to pochłania czas. Z tego śledzenia wynika, że zwiększenie obciążenia procesora w każdej klatce nie będzie miało większego wpływu na czas generowania klatki, ponieważ procesor jest w większości czasu niewykorzystany.
Przykłady z życia
Zobaczmy teraz, jak wyglądają dane śledzenia z rzeczywistej gry. Jedną z ciekawych rzeczy w przypadku gier stworzonych z użyciem otwartych technologii internetowych jest to, że możesz zobaczyć, co dzieje się w Twoich ulubionych usługach. Jeśli chcesz przetestować narzędzia do profilowania, możesz wybrać ulubioną grę WebGL w Chrome Web Store i zprofilować ją za pomocą about:tracing
. To jest przykładowy ślad pochodzący z doskonałej gry WebGL Skid Racer.
Wygląda na to, że każda klatka zajmuje około 20 ms, co oznacza, że liczba klatek wynosi około 50 FPS. Widać, że praca jest równomiernie rozłożona między procesor i GPU, ale GPU jest zasobem, który jest najbardziej obciążony. Jeśli chcesz zobaczyć, jak wygląda profilowanie na przykładach rzeczywistych gier WebGL, wypróbuj niektóre tytuły ze sklepu Chrome Web Store, które korzystają z WebGL, takie jak:
Podsumowanie
Jeśli chcesz, aby gra działała z częstotliwością 60 Hz, wszystkie operacje muszą się zmieścić w 16 ms czasu procesora i 16 ms czasu GPU na każdą klatkę. Masz 2 zasoby, które mogą być wykorzystywane równolegle, i możesz przenosić między nimi zadania, aby zmaksymalizować wydajność. Widok about:tracing
w Chrome to bezcenne narzędzie, które pozwala poznać działanie kodu i pomaga optymalizować czas poświęcany na programowanie, ponieważ pozwala skupić się na odpowiednich problemach.
Co dalej?
Oprócz karty graficznej możesz śledzić też inne części środowiska wykonawczego Chrome. Chrome Canary, czyli wczesna wersja Chrome, jest wyposażona w instrumenty do śledzenia operacji wejścia/wyjścia, IndexedDB i kilku innych działań. Aby dowiedzieć się więcej o bieżącym stanie śledzenia zdarzeń, przeczytaj ten artykuł na stronie Chromium.
Jeśli jesteś deweloperem gier internetowych, obejrzyj film poniżej. Prezentacja zespołu Google ds. promowania deweloperów gier podczas GDC 2012 na temat optymalizacji wydajności gier w Chrome: