Wskazówki dotyczące optymalizacji procesora i procesora graficznego

Z tego dokumentu dowiesz się, jak optymalizować wydajność gry za pomocą narzędzi do identyfikowania i usuwania wąskich gardeł CPU i GPU.

Optymalizacja procesora

Jeśli analiza wykaże, że gra jest ograniczona przez procesor, konieczne jest dalsze zbadanie problemu. Wymaga to zidentyfikowania konkretnych wątków lub interfejsów API, które powodują wąskie gardła i obniżają liczbę klatek na sekundę.

W przypadku optymalizacji procesora uniwersalne rozwiązanie zwykle nie jest skuteczne. Zamiast tego musisz określić najbardziej wymagające obciążenie na podstawie gry lub sceny, a następnie zoptymalizować odpowiednią logikę i funkcje.

Narzędzia do śledzenia czasu w silniku gry

W przeprowadzeniu tej analizy mogą Ci pomóc te narzędzia:

Niesamowite statystyki

W projektach Unreal Engine narzędzie Unreal Insight Tool ułatwia analizę informacji o śledzeniu czasu poszczególnych wątków składających się na klatkę.

Przykładowo wątek GameThread zwykle wykorzystuje największą część czasu procesora, co wynika głównie z czasu Tick Time. Ponadto znaczną część czasu Tick Time zajmują zadania związane z FActorComponentTickFunction.

Aby zoptymalizować FActorComponentTick, konieczne jest wykluczenie obliczeń i wdrożenie wycinania w przypadku postaci i obiektów znajdujących się poza polem widzenia kamery. Dodatkowo korzystanie z animacji opartych na poziomie szczegółowości może jeszcze bardziej zwiększyć wydajność.

Oś czasu śledzenia Unreal Insight pokazująca czasy wykonywania wątków GameThread, RenderThread i RHIThread
Ślad Unreal Insight z wątkami GameThread, RenderThread i RHIThread (kliknij, aby powiększyć).

Profiler Unity (Unity)

Analiza za pomocą narzędzia Unity Profiler pokazuje, że wątek główny zużywa ponad 45 ms, a funkcja PostLateUpdate.FinishFrameRendering zajmuje 16,23 ms, co czyni ją najbardziej czasochłonną operacją. W tym czasie obserwujemy wiele wywołań funkcji Inl_RenderCameraStack. Warto sprawdzić, czy włączone kamery są potrzebne, i odpowiednio je zoptymalizować.

Oś czasu profilera Unity pokazująca wątek główny czekający na Gfx.WaitForPresentOnGfxThread
Przykład ograniczenia przez GPU w profilerze Unity (kliknij, aby powiększyć).

Narzędzia do profilowania na poziomie systemu

Skorzystaj z tych narzędzi do profilowania:

Perfetto

Korzystając ze śladu Perfetto, możesz określić przypisania rdzeni procesora i szczegóły wykonania każdego wątku na urządzeniu z Androidem. Umożliwia to identyfikowanie wąskich gardeł wydajności przez analizowanie danych o wykonywaniu wątków.

Przypadek obciążenia procesora

Ślad wskazuje, że obciążenie wątków GameThread i RenderThread powoduje opóźnienia w funkcji QueuePresent wątku RHI, co prowadzi do scenariusza ograniczonego przez procesor na podstawie synchronizacji pionowej.

Ślad Perfetto pokazujący czasy wykonywania wątków GameThread, RenderThread i RHIThread
Ślady Perfetto ze szczegółami wykonania na procesorze (kliknij, aby powiększyć).

Przypadek obciążenia GPU

Ślad wskazuje, że samo zakończenie pracy GPU trwa ponad 25 ms, co oznacza, że jest to scenariusz ograniczony przez GPU.

Ślad Perfetto pokazujący blok ukończenia GPU oczekujący na ukończenie GPU
Ślady Perfetto ze szczegółami dotyczącymi narzutu GPU (kliknij, aby powiększyć).

Simpleperf

Aby zidentyfikować funkcje o najwyższym bieżącym użyciu procesora, można użyć narzędzia simpleperf. Aby uzyskać optymalne wyniki, zalecamy posortowanie tych funkcji w celu ustalenia priorytetów i rozwiązania w pierwszej kolejności problemów z tymi, które są najczęściej używane.

Dane wyjściowe narzędzia Simpleperf pokazujące funkcje o najwyższym wykorzystaniu procesora
Profilowanie procesora za pomocą narzędzia Simpleperf: analiza hierarchii wywołań funkcji i wykorzystania zasobów (kliknij, aby powiększyć).

Simpleperf pomaga analizować dane o funkcjach, które wykorzystują najwięcej czasu procesora. Aby zoptymalizować wykorzystanie procesora, zacznij od funkcji, które zużywają najwięcej mocy obliczeniowej. W tym przykładzie najwięcej zasobów procesora wykorzystuje USkeletalMeshComponent, które jest powiązane z animacją w ActorComponentTickFunctions.

Optymalizacja GPU

Jeśli analiza wykaże, że gra jest ograniczona przez GPU, konieczne jest dalsze zbadanie problemu. Wymaga to użycia różnych narzędzi i technik optymalizacji i analizy GPU.

Aby zoptymalizować GPU, użyj debugera klatek do analizowania potoku renderowania i wywołań rysowania w każdej scenie. Musisz też dobrze rozumieć architekturę procesora graficznego i działanie potoku, aby zidentyfikować niepotrzebne operacje lub obszary do optymalizacji.

W sekcjach poniżej znajdziesz opis metod i narzędzi do optymalizacji procesora graficznego.

Usuwanie niepotrzebnych RenderPassów

Aby zwiększyć wydajność renderowania i zmniejszyć obciążenie GPU, wyeliminuj niepotrzebne przebiegi renderowania. Obejmują one wszystkie przebiegi renderowania, które nie mają wywołań rysowania lub których dane wyjściowe nie są używane w klatce końcowej.

Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować możliwości optymalizacji.

  1. No Draw Calls (Brak wywołań rysowania): sprawdź, czy przepustka renderowania zawiera wywołania rysowania. Jeśli nie ma wywołań rysowania, usuń przepustkę.

  2. Nieużywane dane wyjściowe: sprawdź, czy kolejne przebiegi mają dostęp do danych wyjściowych przebiegu renderowania, np. koloru lub głębi, i czy je wyświetlają. Jeśli nie, usuń kartę.

  3. Dokumenty, które można scalić: sprawdź, które dokumenty możesz scalić:

    • Ten sam bufor ramki lub załączniki
    • Zgodne operacje ładowania lub przechowywania
    • Brak barier zależności
Przeglądarka zdarzeń RenderDoc wyświetlająca etapy renderowania i wywołania rysowania interfejsu Vulkan
Sekwencja poleceń RenderPass i GPU w RenderDoc (kliknij, aby powiększyć).

Minimalizowanie operacji wczytywania i zapisywania

Operacje ładowania i przechowywania są zasobożerne, ponieważ wykorzystują dużo pamięci. Minimalizuj niepotrzebne operacje wczytywania i zapisywania. Wykonuj te czynności tylko wtedy, gdy wymagane są załączniki w RenderPass. W przeciwnym razie zastąp je operacjami Clear lub Don't care, aby zmniejszyć narzut.

Optymalizacja

Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:

  1. Wczytywanie: jeśli załącznik do etapu renderowania nie korzysta z danych z poprzedniego etapu lub załącznika, operacja wczytywania jest zbędna. W takich przypadkach użycie metod Don't care lub Clear może zmniejszyć obciążenie.

  2. Przechowywanie: jeśli załącznik przepustki renderowania nie jest używany po bieżącej przepustce renderowania, operacja przechowywania jest niepotrzebna. W takich przypadkach używaj właściwości Don't care lub Clear.

  3. Zastąp: sprawdź, czy bieżące ustawienia wczytywania lub zapisywania można zastąpić ustawieniami Clear lub Don't Care bez wpływu na końcową klatkę.

Przeglądarka zdarzeń i inspektor zasobów RenderDoc analizujące układ obrazu i przebiegi renderowania
Analiza potoku renderowania RenderDoc (kliknij, aby powiększyć).

Unikaj odrzucania, aby włączyć Early-Z

Early-Z zwiększa wydajność na platformach mobilnych. discard Instrukcja w shaderze automatycznie wyłącza jednak Early-Z. Jeśli discardinstrukcja nie jest niezbędna, usuń ją.

Przyspieszenie Early-Z

Ta optymalizacja znacznie zmniejsza liczbę operacji shadera fragmentów i zwiększa wydajność procesora graficznego.

Early-Z Testowanie głębi i szablonu

Tabela porównująca dane dotyczące wydajności procesora i procesora graficznego, gdy Early-Z jest włączony i wyłączony
Wpływ przyspieszenia Early-Z na wydajność (kliknij, aby powiększyć).

Optymalizacja

Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:

  1. Użycie słowa kluczowego discard w programach cieniowania fragmentów: słowo kluczowe discard uniemożliwia procesorowi graficznemu przeprowadzanie wczesnych testów głębi, ponieważ widoczność fragmentu nie jest znana z wyprzedzeniem.

  2. Modyfikacja gl_FragDepth: dynamiczna modyfikacja gl_FragDepth zmienia głębię fragmentu, co wyłącza optymalizację Early-Z, ponieważ ostateczna głębia jest nieznana przed przetworzeniem fragmentu.

  3. Włączone przekształcanie wartości alfa w wartości pokrycia: gdy przekształcanie wartości alfa w wartości pokrycia jest włączone (często używane w renderowaniu MSAA), pokrycie fragmentu zależy od wartości alfa. Może to opóźnić testowanie głębi i wyłączyć Early-Z.

Porównanie fragmentów na piksel z użyciem i bez użycia słowa kluczowego discard shader
Debuger GPU RenderDoc do analizy (kliknij, aby powiększyć).

Optymalizacja formatu tekstury

Optymalny wybór formatu tekstury zmniejsza zużycie pamięci, zwiększa wydajność przepustowości i poprawia wydajność renderowania. Stosowanie formatów o zbyt wysokiej precyzji może marnować zasoby GPU bez zapewniania korzyści wizualnych.

Optymalizacja

Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:

  1. Używaj formatu D24S8 zamiast D32S8 w przypadku buforów głębi i szablonu: używanie formatu D24S8 w przypadku buforów głębi i szablonu zmniejsza zużycie pamięci o 20% w porównaniu z formatem D32S8, przy niewielkiej lub żadnej zauważalnej różnicy w jakości obrazu w większości aplikacji.
  2. Używaj kompresji ASTC w przypadku tekstur kolorów: kompresja ASTC znacznie zmniejsza wykorzystanie pamięci przez tekstury – nawet 8-krotnie w porównaniu z formatami nieskompresowanymi – przy zachowaniu wysokiej jakości wizualnej.
  3. Zamiast formatów zmiennoprzecinkowych o pełnej precyzji używaj formatów zmiennoprzecinkowych o połowicznej precyzji: używaj formatów R16F lub RG16F, aby zmniejszyć przepustowość pamięci i zużycie miejsca na dane. Te formaty dobrze nadają się do buforów przetwarzania końcowego.

Optymalizacja złożoności geometrii

Minimalizowanie złożoności geometrycznej poprawia wydajność renderowania, zwłaszcza na urządzeniach mobilnych o ograniczonych możliwościach GPU. Obejmuje to używanie mniejszej liczby wierzchołków i trójkątów, konsolidowanie obiektów w celu zmniejszenia liczby wywołań rysowania oraz eliminowanie niewyrenderowanej lub niepotrzebnej geometrii. Techniki takie jak upraszczanie siatki, poziom szczegółowości (LOD) oraz wycinanie frustum i okluzji mogą znacznie zmniejszyć obciążenie procesora graficznego i zwiększyć liczbę klatek na sekundę.

Optymalizacja

Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc, Android GPU Inspector lub inne analizatory wydajności, aby identyfikować wąskie gardła wydajności związane z geometrią.

  1. Zmniejsz liczbę trójkątów: ogranicz użycie wielokątów, zwłaszcza w przypadku małych lub odległych obiektów.

  2. Użyj poziomu szczegółowości: w zależności od odległości kamery automatycznie używane są prostsze siatki.

  3. Scal małe siatki: konsoliduje obiekty statyczne, aby zmniejszyć liczbę wywołań rysowania i obciążenie procesora.

  4. Odcinanie i usuwanie zasłoniętych obiektów: unikaj renderowania obiektów, które znajdują się poza widokiem lub są zasłonięte przez inne elementy.

Usuwanie niepotrzebnych załączników

Załączniki do przebiegu renderowania (np. kolor, głębia, szablon) zużywają przepustowość pamięci i zasoby GPU, nawet jeśli nie są używane. Usunięcie niepotrzebnych lub zbędnych załączników zwiększa wydajność i zmniejsza zużycie energii, zwłaszcza na platformach mobilnych.

Optymalizacja

Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc,Android GPU Inspector lub innych analizatorów wydajności, aby identyfikować wąskie gardła wydajności związane z geometrią.

  1. Sprawdź rzeczywiste użycie: czy istnieją wywołania rysowania lub shadery, które zapisują dane w załączniku lub je z niego odczytują?
  2. Analizowanie danych wyjściowych klatek: użyj narzędzia RenderDoc lub podobnych narzędzi, aby określić, czy załącznik ma wpływ na obraz końcowy.
  3. Rozważ użycie tymczasowych lub fikcyjnych załączników: tymczasowe załączniki lub operacja magazynu „Nie ma znaczenia” powinny być używane w przypadku danych tymczasowych, które nie wymagają pamięci trwałej.

Optymalizowanie precyzji shadera

Używanie w shaderach zbyt wysokiej precyzji (np. highp zamiast mediump lub lowp) zwiększa obciążenie GPU, zużycie energii i ciśnienie rejestru, zwłaszcza w przypadku mobilnych procesorów graficznych. Używanie najniższej odpowiedniej precyzji w przypadku zmiennych (np. pozycji, kolorów, współrzędnych UV) może poprawić wydajność bez zauważalnego wpływu na wygląd.

Tabela porównująca dane dotyczące wydajności procesora i procesora graficznego przy użyciu precyzji cieniowania mediump i highp
Wpływ precyzji shadera na wydajność (kliknij, aby powiększyć).

Optymalizacja

Używaj narzędzi do profilowania i debuggerów GPU, takich jak RenderDoc, Android GPU Inspector lub inne analizatory wydajności, aby identyfikować wąskie gardła związane z geometrią.

  1. Sprawdź kod shadera: oceń zmienne shadera i upewnij się, że wysoka precyzja jest używana tylko wtedy, gdy jest to konieczne, np. w przypadku obliczeń głębi lub przestrzeni ekranu. Używaj średniej lub niskiej precyzji w przypadku kolorów, współrzędnych UV lub wartości, które nie wymagają wysokiej precyzji.

  2. Używaj debuggerów GPU: narzędzia diagnostyczne, takie jak RenderDoc lub profiler GPU na urządzenia mobilne (np. AGI, Mali/GPU Inspector), identyfikują zwiększone użycie rejestrów lub wstrzymania cieniowania związane z problemami z precyzją.

Profiler Mali Varying Usage wyświetlający 16-bitową interpolację obok kodu shadera korzystającego z mediump
Przykład narzędzi do profilowania i debuggerów GPU (kliknij, aby powiększyć).

Włączanie odrzucania tylnych ścian

Renderowanie trójkątów skierowanych tyłem do kamery (tylnych ścian) jest często niepotrzebne w przypadku obiektów stałych.

Optymalizacja

Używanie elementu VK_CULL_MODE_NONE może negatywnie wpływać na wydajność, ponieważ zmusza GPU do renderowania zarówno przedniej, jak i tylnej strony, co zwiększa obciążenie renderowaniem.

Dziennik poleceń Vulkan z poleceniem vkCmdSetCullMode ustawionym na VK_CULL_MODE_NONE
Dzienniki debugowania z odrzucaniem tylnych ścian (kliknij, aby powiększyć).

Minimalizowanie przerysowania w scenach interfejsu

Eliminuj niepotrzebne wywołania rysowania i przekazywanie renderowania, zwłaszcza w scenach interfejsu, aby zwiększyć wydajność renderowania i zmniejszyć obciążenie procesora graficznego. Na przykład w scenie interfejsu, w której cały świat jest renderowany przed nałożeniem interfejsu na ekran, renderowanie świata staje się zbędne.

Optymalizacja

Użyj debugera GPU, np. RenderDoc, aby przeanalizować potok renderowania i zidentyfikować te możliwości optymalizacji:

  1. Sprawdź, czy nie ma zbędnego przerysowania. W kontekstach interfejsu użytkownika, w których może być renderowany cały ekran, sprawdź, czy poprzednie etapy renderowania nie są niepotrzebnie nadmiernie rysowane.
  2. Włącz testowanie głębi i odrzucanie, aby zoptymalizować wydajność.
  3. Rozważ renderowanie w kolejności od przodu do tyłu.
Przeglądarka zdarzeń i przeglądarka tekstur RenderDoc wskazujące niepotrzebne przejście renderowania przerysowania
Przykład eliminacji zbędnych wywołań rysowania i przekazywania renderowania (kliknij, aby powiększyć).