mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 13:43:05 +02:00
1065 lines
52 KiB
TeX
1065 lines
52 KiB
TeX
\clearpage
|
|
\raggedbottom
|
|
\section{Testy wydajności}
|
|
\label{sec:testy-wydajnosci}
|
|
|
|
Dla każdego scenariusza i~silnika rejestrowano następujące metryki przy użyciu NVIDIA Nsight Systems:
|
|
|
|
\begin{itemize}
|
|
\item \textbf{Czas klatki} (frame time) -- czas renderowania pojedynczej klatki w~milisekundach.
|
|
\item \textbf{FPS} (frames per second) -- liczba klatek na sekundę, wyliczana jako $1000 / \text{frame time}$.
|
|
\item \textbf{Wykorzystanie GPU} -- procent wykorzystania mocy obliczeniowej karty graficznej.
|
|
\item \textbf{Zużycie pamięci VRAM} -- ilość zajętej pamięci karty graficznej w~megabajtach.
|
|
\item \textbf{Liczba wywołań rysowania} (draw calls) -- liczba instrukcji renderowania na klatkę.
|
|
\item \textbf{Liczba wierzchołków} -- całkowita liczba przetworzonych wierzchołków na klatkę.
|
|
\end{itemize}
|
|
|
|
\subsection{Wyniki testów dla silnika Unity}
|
|
\label{subsec:wyniki-unity}
|
|
|
|
Profilowanie silnika Unity przeprowadzono przy użyciu narzędzia NVIDIA Nsight Systems w wersji 2025.5.2,
|
|
które umożliwia szczegółową analizę wywołań
|
|
API graficznych oraz funkcji systemowych na poziomie pojedynczych mikrosekund. Test trwał 95 sekund,
|
|
podczas których gra działała w
|
|
trybie stacjonarnym (gracz nieruchomy) z włączoną nieśmiertelnością, co pozwoliło na stabilne pomiary
|
|
bez przerwania rozgrywki.
|
|
|
|
|
|
Podczas 94,16-sekundowego okresu aktywnego renderowania zarejestrowano łącznie 13\,556 klatek,
|
|
co przekłada się na średnią wydajność
|
|
\textbf{143,96 klatek na sekundę} (FPS). Wartość ta niemal dokładnie odpowiada częstotliwości
|
|
odświeżania monitora testowego (144 Hz),
|
|
co wskazuje na \textbf{włączoną synchronizację pionową} (V-Sync) podczas testu. Oznacza to,
|
|
że zmierzona wydajność reprezentuje
|
|
górny limit narzucony przez monitor, a nie rzeczywistą maksymalną wydajność silnika Unity.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Ogólne metryki wydajności silnika Unity}
|
|
\label{tab:unity-performance-summary}
|
|
\begin{tabular}{|l|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Wartość} \\
|
|
\hline
|
|
Czas trwania testu & 94,16 s \\
|
|
Liczba wyrenderowanych klatek & 13\,556 \\
|
|
Średnia liczba FPS & 143,96 \\
|
|
Średni czas klatki & 6,95 ms \\
|
|
Minimalny czas klatki & 0,08 ms \\
|
|
Maksymalny czas klatki & 1\,239,62 ms \\
|
|
Odchylenie standardowe & 10,64 ms \\
|
|
Współczynnik zmienności & 153,24\% \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:unity-performance-summary} przedstawia podstawowe metryki wydajności. Średni czas
|
|
klatki wynoszący 6,95 ms oznacza, że silnik
|
|
Unity jest w stanie wyrenderować pojedynczą klatkę w czasie znacznie krótszym niż wymagane 16,67 ms
|
|
dla osiągnięcia 60 FPS. Minimalny czas
|
|
klatki 0,08 ms odpowiada sytuacjom, gdy kolejne wywołania prezentacji następują niemal natychmiast po
|
|
sobie -- może to wynikać z mechanizmu
|
|
podwójnego buforowania (ang. \textit{double buffering}) lub chwilowego braku pracy do wykonania przez GPU.
|
|
|
|
Wartość maksymalna 1\,239,62 ms (ponad sekunda) występuje podczas fazy inicjalizacji aplikacji,
|
|
gdy silnik Unity wykonuje jednorazowe operacje: kompilację shaderów, alokację dużych bloków pamięci \\ GPU,
|
|
tworzenie obiektów swapchain oraz
|
|
inicjalizację systemu renderowania. Jest to zachowanie typowe dla aplikacji Vulkan, gdzie znaczna część
|
|
pracy inicjalizacyjnej wykonywana jest przy
|
|
starcie, w przeciwieństwie do OpenGL, gdzie inicjalizacja jest bardziej rozłożona w czasie.
|
|
|
|
Współczynnik zmienności (CV) wynoszący 153,24\% jest wysoki, jednak wynika on głównie z
|
|
uwzględnienia ekstremalnych wartości inicjalizacyjnych.
|
|
Po wykluczeniu pierwszych kilku klatek, stabilność renderowania jest znacznie wyższa, co potwierdza
|
|
analiza percentylowa przedstawiona w dalszej części.
|
|
|
|
|
|
Szczegółowa analiza rozkładu czasów klatek pozwala ocenić nie tylko średnią wydajność, ale przede
|
|
wszystkim stabilność i przewidywalność działania
|
|
silnika -- aspekty kluczowe dla komfortu odbiorcy gry.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Rozkład percentylowy czasów klatek silnika Unity}
|
|
\label{tab:unity-percentiles}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Percentyl} & \textbf{Czas klatki (ms)} & \textbf{Odpowiadający FPS} \\
|
|
\hline
|
|
1. percentyl (najszybsze 1\%) & 0,71 & 1\,408 \\
|
|
5. percentyl & 6,69 & 149 \\
|
|
25. percentyl (Q1) & 6,90 & 145 \\
|
|
50. percentyl (mediana) & 6,94 & 144 \\
|
|
75. percentyl (Q3) & 6,98 & 143 \\
|
|
95. percentyl & 7,18 & 139 \\
|
|
99. percentyl (najwolniejsze 1\%) & 7,58 & 132 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:unity-percentiles} prezentuje rozkład percentylowy czasów klatek. \textbf{Mediana}
|
|
(50. percentyl) wynosząca 6,94 ms jest niemal
|
|
identyczna z teoretycznym czasem klatki przy 144 Hz (6,944 ms), co potwierdza aktywną
|
|
synchronizację pionową. Wąski rozstęp między 5. percentylem
|
|
(6,69 ms, 149 FPS) a 95. percentylem (7,18 ms, 139 FPS) -- zaledwie 0,49 ms --
|
|
jest charakterystyczny dla V-Sync, gdzie czas klatki jest
|
|
sztucznie stabilizowany przez oczekiwanie na sygnał odświeżania monitora.
|
|
|
|
Szczególnie istotny jest \textbf{99. percentyl} wynoszący 7,58 ms, określany w środowisku graczy
|
|
jako ,,1\% low'' (132 FPS). Wartość ta reprezentuje
|
|
wydajność w najgorszych 1\% przypadków i jest kluczową metryką dla oceny płynności rozgrywki.
|
|
Różnica między medianą (6,94 ms) a 99. percentylem
|
|
(7,58 ms) wynosi 0,64 ms (9,2\%). Należy jednak zauważyć, że niska zmienność jest częściowo
|
|
wynikiem działania V-Sync, który stabilizuje
|
|
czas klatki kosztem wprowadzenia opóźnienia wejścia (ang. \textit{input lag}).
|
|
|
|
\textbf{Rozstęp międzykwartylowy} (IQR), czyli różnica między 75. a 25. percentylem, wynosi zaledwie
|
|
0,08 ms. Tak niski IQR potwierdza, że 50\%
|
|
środkowych czasów klatek mieści się w niezwykle wąskim przedziale, co jest oznaką deterministycznego i
|
|
przewidywalnego zachowania potoku renderowania.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Histogram czasów klatek silnika Unity}
|
|
\label{tab:unity-histogram}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Przedział czasu klatki} & \textbf{Liczba klatek} & \textbf{Udział (\%)} \\
|
|
\hline
|
|
0--5 ms (>200 FPS) & 230 & 1,70 \\
|
|
5--10 ms (100--200 FPS) & 13\,317 & 98,24 \\
|
|
10--16,67 ms (60--100 FPS) & 4 & 0,03 \\
|
|
16,67--33,33 ms (30--60 FPS) & 2 & 0,01 \\
|
|
>33,33 ms (<30 FPS) & 2 & 0,01 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Histogram przedstawiony w tabeli~\ref{tab:unity-histogram} dostarcza dodatkowego wglądu w rozkład
|
|
wydajności. \textbf{98,24\% wszystkich klatek}
|
|
zostało wyrenderowanych w czasie 5--10 ms, co odpowiada wydajności 100--200 FPS. Jedynie 8 klatek
|
|
(0,06\%) przekroczyło próg 10 ms, przy czym klatki
|
|
poniżej 60 FPS (>16,67 ms) stanowiły zaledwie 0,02\% -- praktycznie wszystkie z nich przypadły na fazę
|
|
inicjalizacji.
|
|
|
|
Kategoria 0--5 ms (230 klatek, 1,70\%) reprezentuje sytuacje szczególne: bardzo szybkie klatki podczas
|
|
przejść między scenami, momenty niskiego
|
|
obciążenia lub artefakty pomiarowe wynikające z mechanizmu synchronizacji swapchain.
|
|
|
|
|
|
NVIDIA Nsight Systems przechwytuje wszystkie wywołania interfejsu programistycznego Vulkan, umożliwiając dokładną analizę zachowania silnika
|
|
renderującego na poziomie pojedynczych funkcji API. Podczas testu zarejestrowano łącznie \textbf{218\,815 wywołań} 31 różnych funkcji Vulkan API.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unity -- funkcje synchronizacji i prezentacji}
|
|
\label{tab:unity-vulkan-sync}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Czas (\%)} & \textbf{Wywołania} & \textbf{Śr. (ms)} & \textbf{Med. (ms)} & \textbf{Maks. (ms)} \\
|
|
\hline
|
|
\texttt{vkWaitForFences} & 95,2 & 12\,895 & 5,97 & 6,23 & 1\,181,17 \\
|
|
\texttt{vkQueuePresentKHR} & 3,2 & 13\,556 & 0,19 & 0,02 & 7,20 \\
|
|
\texttt{vkQueueSubmit} & 0,8 & 27\,112 & 0,03 & 0,01 & 2,69 \\
|
|
\texttt{vkAcquireNextImageKHR} & 0,0 & 13\,556 & 0,001 & 0,001 & 0,11 \\
|
|
\texttt{vkQueueWaitIdle} & 0,0 & 1 & 0,27 & 0,27 & 0,27 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\texttt{vkWaitForFences} pochłonęła \textbf{95,2\% całkowitego czasu}
|
|
profilowania wywołań Vulkan API, co stanowi 77,04 sekundy z
|
|
94-sekundowego testu. Funkcja ta, zdefiniowana w specyfikacji Vulkan w rozdziale 7.3 dotyczącym
|
|
synchronizacji, realizuje blokujące oczekiwanie
|
|
procesora na sygnalizację obiektów ogrodzenia (ang. \textit{fence}) przez GPU.
|
|
|
|
Mechanizm ogrodzeń w Vulkan działa następująco: aplikacja tworzy obiekt fence,
|
|
dołącza go do operacji przesyłanej do kolejki GPU
|
|
(np. poprzez \texttt{vkQueueSubmit}), a następnie może wywołać \texttt{vkWaitForFences},
|
|
aby zablokować wątek CPU do momentu zakończenia
|
|
powiązanej pracy przez GPU. Jest to fundamentalny mechanizm synchronizacji w architekturze
|
|
producent-konsument między CPU a GPU.
|
|
|
|
Tak wysoki udział procentowy (95,2\%) jednoznacznie wskazuje na scenariusz \textbf{ograniczenia wydajności przez GPU} (ang. \textit{GPU-bound}).
|
|
W tym scenariuszu procesor główny zakończył przygotowywanie i przesyłanie poleceń renderowania, a następnie oczekuje na ukończenie ich wykonania
|
|
przez kartę graficzną. Jest to pożądany wzorzec w dobrze zoptymalizowanych aplikacjach graficznych -- procesor nie stanowi wąskiego gardła i
|
|
zdąża przygotować pracę dla GPU przed zakończeniem poprzedniej klatki.
|
|
|
|
Średni czas pojedynczego wywołania wyniósł 5,97 ms przy medianie 6,23 ms.
|
|
Różnica między średnią a medianą (0,26 ms) wynika z obecności bardzo krótkich
|
|
czasów oczekiwania w niektórych sytuacjach (np. gdy GPU zakończył pracę przed wywołaniem wait).
|
|
Maksymalny czas 1\,181,17 ms odpowiada fazie
|
|
inicjalizacji, podczas której GPU wykonuje jednorazowe, kosztowne operacje.
|
|
|
|
Stosunek liczby wywołań \texttt{vkWaitForFences} (12\,895) do liczby klatek (13\,556) wskazuje, że
|
|
Unity stosuje strategię oczekiwania, prawie na
|
|
każdą klatkę z pewnymi optymalizacjami pozwalającymi pominąć oczekiwanie w niektórych przypadkach.
|
|
|
|
|
|
\texttt{vkQueuePresentKHR}, zdefiniowana w rozszerzeniu \texttt{VK\_KHR\_swapchain}, odpowiada za przesłanie żądania prezentacji
|
|
wyrenderowanego obrazu do silnika prezentacji (ang. \textit{presentation engine}). Każde wywołanie tej funkcji reprezentuje jedną klatkę przekazaną
|
|
do wyświetlenia, dlatego liczba wywołań (13\,556) równa jest liczbie wyrenderowanych klatek.
|
|
|
|
Średni czas wywołania 0,19 ms przy medianie zaledwie 0,02 ms wskazuje na asymetryczny rozkład -- większość wywołań jest bardzo szybka, ale niektóre
|
|
wymagają dłuższego oczekiwania (maksymalnie 7,20 ms). Dłuższe czasy mogą wynikać z oczekiwania na dostępność bufora w swapchain lub synchronizacji z
|
|
częstotliwością odświeżania monitora (nawet przy wyłączonym V-Sync, pewien poziom synchronizacji jest wymagany).
|
|
|
|
|
|
\texttt{vkQueueSubmit} przesyła bufory poleceń (ang. \textit{command buffers}) do kolejki GPU celem wykonania. Zarejestrowano 27\,112 wywołań, co
|
|
oznacza średnio 2 wywołania na klatkę. Taki wzorzec sugeruje, że Unity stosuje architekturę z oddzielnymi przebiegami renderowania
|
|
(np. przebieg główny + post-processing lub przebieg sceny + UI).
|
|
|
|
Niski średni czas (0,03 ms) potwierdza, że \texttt{vkQueueSubmit} jedynie kolejkuje pracę bez oczekiwania na jej wykonanie -- faktyczne renderowanie
|
|
odbywa się asynchronicznie na GPU.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unity -- bufory poleceń}
|
|
\label{tab:unity-vulkan-cmd}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Wywołania} & \textbf{Śr. ($\mu$s)} & \textbf{Med. ($\mu$s)} & \textbf{Maks. ($\mu$s)} \\
|
|
\hline
|
|
\texttt{vkBeginCommandBuffer} & 40\,679 & 2,53 & 1,76 & 2\,049 \\
|
|
\texttt{vkEndCommandBuffer} & 40\,679 & 0,73 & 0,63 & 116 \\
|
|
\texttt{vkCmdPipelineBarrier} & 40\,800 & 0,46 & 0,39 & 97 \\
|
|
\texttt{vkCmdBindPipeline} & 27\,027 & 1,07 & 0,99 & 36 \\
|
|
\texttt{vkAllocateCommandBuffers} & 687 & 12,78 & 12,08 & 67 \\
|
|
\texttt{vkCreateCommandPool} & 687 & 1,22 & 0,22 & 10 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:unity-vulkan-cmd} przedstawia statystyki funkcji związanych z buforami poleceń.
|
|
Liczba wywołań \texttt{vkBeginCommandBuffer} oraz
|
|
\texttt{vkEndCommandBuffer} (po 40\,679) oznacza, że Unity nagrywa średnio 3 bufory poleceń na klatkę. Jest to typowa wartość dla nowoczesnych
|
|
silników stosujących wielowątkowe nagrywanie poleceń.
|
|
|
|
Funkcja \texttt{vkCmdPipelineBarrier} (40\,800 wywołań) służy do synchronizacji dostępu do zasobów w obrębie GPU i zapewnienia poprawnej kolejności
|
|
operacji. Wysoka liczba wywołań wskazuje na staranną kontrolę zależności między operacjami renderowania.
|
|
|
|
\texttt{vkCmdBindPipeline} (27\,027 wywołań, ~2 na klatkę) przełącza aktywny stan potoku graficznego. Relatywnie niska liczba wywołań sugeruje efektywne
|
|
grupowanie obiektów według używanego potoku, minimalizując kosztowne zmiany stanu.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unity -- inicjalizacja i zasoby}
|
|
\label{tab:unity-vulkan-init}
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Wywołania} & \textbf{Całk. czas (ms)} & \textbf{Śr. (ms)} \\
|
|
\hline
|
|
\texttt{vkCreateDevice} & 1 & 162,35 & 162,35 \\
|
|
\texttt{vkCreateSwapchainKHR} & 1 & 77,02 & 77,02 \\
|
|
\texttt{vkCreateFence} & 341 & 135,60 & 0,40 \\
|
|
\texttt{vkAllocateMemory} & 22 & 15,07 & 0,68 \\
|
|
\texttt{vkFreeMemory} & 8 & 5,07 & 0,63 \\
|
|
\texttt{vkCreateGraphicsPipelines} & 3 & 0,38 & 0,13 \\
|
|
\texttt{vkCreateImage} & 106 & 0,24 & 0,002 \\
|
|
\texttt{vkCreateImageView} & 111 & 0,17 & 0,002 \\
|
|
\texttt{vkCreateShaderModule} & 6 & 0,04 & 0,006 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:unity-vulkan-init} przedstawia jednorazowe operacje inicjalizacyjne. \texttt{vkCreateDevice} (162,35 ms) tworzy logiczne
|
|
urządzenie Vulkan -- jest to najdroższa pojedyncza operacja, obejmująca negocjację możliwości GPU, alokację struktur wewnętrznych sterownika i
|
|
inicjalizację kolejek.
|
|
|
|
\texttt{vkCreateSwapchainKHR} (77,02 ms) tworzy łańcuch wymiany (swapchain), czyli zestaw
|
|
buforów służących do prezentacji obrazu. Operacja ta
|
|
obejmuje alokację pamięci dla buforów, konfigurację formatów i synchronizację z systemem okienkowym.
|
|
|
|
Utworzenie 341 obiektów fence (łącznie 135,60 ms) wskazuje na
|
|
przygotowanie puli ogrodzeń do wielokrotnego użytku w cyklu renderowania.
|
|
Unity stosuje
|
|
strategię \\ pre-alokacji zamiast tworzenia ogrodzeń na żądanie, co
|
|
jest praktyką zalecaną w dokumentacji
|
|
Vulkan.
|
|
|
|
|
|
Oprócz wywołań Vulkan API, Nsight Systems przechwytuje również wywołania funkcji systemowych, umożliwiając analizę zachowania aplikacji na poziomie
|
|
systemu operacyjnego. Zarejestrowano \textbf{29\,383 wywołania} 65 różnych funkcji systemowych.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania systemowe silnika Unity -- synchronizacja wątków}
|
|
\label{tab:unity-osrt-sync}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Czas (\%)} & \textbf{Wywołania} & \textbf{Śr. (ms)} & \textbf{Med. ($\mu$s)} & \textbf{Maks. (s)} \\
|
|
\hline
|
|
\texttt{futex} & 95,9 & 247 & 444,07 & 88,49 & 11,05 \\
|
|
\texttt{pthread\_cond\_timedwait} & 2,7 & 85 & 35,91 & 7\,070,65 & 2,00 \\
|
|
\texttt{pthread\_cond\_wait} & 0,6 & 26 & 28,59 & 10\,433,06 & 0,47 \\
|
|
\texttt{pthread\_create} & 0,0 & 81 & 0,04 & 33,34 & 0,00009 \\
|
|
\texttt{pthread\_join} & 0,0 & 3 & 0,11 & 106,59 & 0,00014 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Funkcja \texttt{futex} (ang. \textit{Fast Userspace muTEX}) pochłonęła \textbf{95,9\% czasu} wywołań systemowych. Futex jest mechanizmem
|
|
synchronizacji wątków w jądrze Linux, zaprojektowanym dla maksymalnej wydajności w scenariuszach bez rywalizacji (ang. \textit{uncontended case}).
|
|
|
|
Mechanizm futex działa dwuetapowo:
|
|
\begin{enumerate}
|
|
\item W przypadku braku rywalizacji, operacje na muteksie wykonywane są całkowicie w przestrzeni użytkownika poprzez instrukcje atomowe, bez
|
|
przełączania do jądra.
|
|
\item Gdy występuje rywalizacja (inny wątek trzyma blokadę), wątek wykonuje wywołanie systemowe \texttt{futex} z operacją \texttt{FUTEX\_WAIT},
|
|
które usypia wątek do momentu zwolnienia blokady.
|
|
\end{enumerate}
|
|
|
|
Tak wysoki udział \texttt{futex} (109,69 sekundy łącznie) wskazuje na intensywne wykorzystanie wielowątkowości przez silnik Unity. Silnik ten stosuje
|
|
architekturę wielowątkową z oddzielnymi wątkami dla: głównej pętli gry, renderowania, fizyki, audio, wczytywania zasobów oraz systemu zadań
|
|
(ang. \textit{job system}).
|
|
|
|
Średni czas wywołania 444,07 ms przy medianie zaledwie 88,49 $\mu$s wskazuje na silnie asymetryczny rozkład -- większość wywołań kończy się szybko
|
|
(wątek od razu uzyskuje blokadę lub jest natychmiast budzony), ale niektóre wywołania skutkują długim oczekiwaniem. Maksymalny czas 11,05 sekundy
|
|
odpowiada najprawdopodobniej wątkowi oczekującemu na zakończenie długotrwałej operacji inicjalizacyjnej.
|
|
|
|
|
|
Funkcje \texttt{pthread\_cond\_timedwait} (2,7\%, 85 wywołań) i \texttt{pthread\_cond\_wait} (0,6\%, 26 wywołań) implementują zmienne warunkowe POSIX,
|
|
używane do bardziej złożonych scenariuszy synchronizacji niż proste muteksy.
|
|
|
|
\texttt{pthread\_cond\_timedwait} różni się od \texttt{pthread\_cond\_wait} możliwością określenia limitu czasu oczekiwania (timeout). Użycie wersji z
|
|
timeoutem (85 vs 26 wywołań) sugeruje, że Unity stosuje wzorzec okresowego sprawdzania warunków zamiast nieograniczonego oczekiwania, co zwiększa
|
|
responsywność systemu.
|
|
|
|
Utworzenie 81 wątków (\texttt{pthread\_create}) podczas testu potwierdza rozbudowaną architekturę wielowątkową. Przy założeniu, że część wątków to wątki
|
|
robocze systemu zadań, sugeruje to pulę kilkudziesięciu wątków aktywnie uczestniczących w renderowaniu i logice gry.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania systemowe silnika Unity -- operacje I/O}
|
|
\label{tab:unity-osrt-io}
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Wywołania} & \textbf{Całk. czas (ms)} & \textbf{Śr. ($\mu$s)} \\
|
|
\hline
|
|
\texttt{poll} & 349 & 314,33 & 900,66 \\
|
|
\texttt{ioctl} & 1\,907 & 284,18 & 149,02 \\
|
|
\texttt{openat64} & 22\,155 & 23,80 & 1,07 \\
|
|
\texttt{read} & 235 & 2,89 & 12,28 \\
|
|
\texttt{open64} & 553 & 1,43 & 2,59 \\
|
|
\texttt{fopen} & 548 & 0,88 & 1,60 \\
|
|
\texttt{writev} & 261 & 0,50 & 1,90 \\
|
|
\texttt{fread} & 317 & 0,49 & 1,55 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:unity-osrt-io} przedstawia statystyki operacji I/O. Funkcja \texttt{poll} (349 wywołań, 314,33 ms) służy do multipleksowanego
|
|
oczekiwania na zdarzenia z wielu deskryptorów plików -- w kontekście gry prawdopodobnie dotyczy komunikacji z systemem okienkowym (X11/Wayland)
|
|
oraz urządzeniami wejścia.
|
|
|
|
Duża liczba wywołań \texttt{openat64} (22\,155) wskazuje na intensywne operacje na systemie plików, prawdopodobnie związane z wczytywaniem zasobów
|
|
gry (tekstur, modeli, shaderów) z dysku. Średni czas 1,07 $\mu$s potwierdza efektywne buforowanie przez system operacyjny.
|
|
|
|
\texttt{ioctl} (1\,907 wywołań) służy do kontroli urządzeń -- w kontekście grafiki Vulkan jest
|
|
używane do komunikacji ze sterownikiem GPU poprzez
|
|
interfejs DRM/KMS (Direct Rendering Manager / Kernel Mode Setting).
|
|
|
|
|
|
Przeprowadzona analiza pozwala na sformułowanie następujących wniosków dotyczących wydajności i architektury silnika Unity:
|
|
|
|
|
|
Dominacja \texttt{vkWaitForFences} (95,2\% czasu Vulkan) i \texttt{futex} (95,9\% czasu systemowego)
|
|
jednoznacznie wskazuje na scenariusz
|
|
\textbf{GPU-bound}. Procesor główny efektywnie przygotowuje i przesyła pracę renderowania, po czym oczekuje na GPU. Jest to optymalny wzorzec dla
|
|
aplikacji graficznych, gdzie GPU wykonuje większość obliczeniowo intensywnej pracy.
|
|
|
|
W scenariuszu CPU-bound obserwowalibyśmy niższy udział funkcji
|
|
synchronizacyjnych i wyższy udział
|
|
funkcji przygotowujących polecenia
|
|
(\texttt{vkBeginCommandBuffer}, \\ \texttt{vkCmdBindPipeline} itp.), co wskazywałoby na wąskie gardło po stronie procesora.
|
|
|
|
|
|
Stosunek liczby wywołań \texttt{vkQueueSubmit} (27\,112) do \texttt{vkQueuePresentKHR} (13\,556) wynoszący 2:1 wskazuje na dwuetapowy potok
|
|
renderowania dla każdej klatki. Może to odpowiadać architekturze z oddzielnymi przebiegami dla sceny 3D i interfejsu użytkownika, lub użyciu techniki
|
|
odroczonego renderowania (ang. \textit{deferred rendering}).
|
|
|
|
Niska liczba wywołań \texttt{vkCmdBindPipeline} (27\,027, ~2 na klatkę)
|
|
sugeruje efektywne grupowanie obiektów renderowanych tym samym shaderem,
|
|
minimalizujące kosztowne zmiany stanu GPU.
|
|
|
|
|
|
Pomimo wysokiego współczynnika zmienności (153\%) wynikającego z wartości ekstremalnych podczas
|
|
inicjalizacji, właściwa stabilność renderowania jest
|
|
wysoka. Świadczy o tym:
|
|
\begin{itemize}
|
|
\item Wąski rozstęp międzykwartylowy (0,08 ms).
|
|
\item Zbieżność mediany (6,94 ms) ze średnią (6,95 ms).
|
|
\item Mała różnica między 50. a 99. percentylem (0,64 ms, 9,2\%).
|
|
\item 98,24\% klatek w przedziale 5--10 ms.
|
|
\end{itemize}
|
|
|
|
Należy jednak podkreślić, że obserwowana stabilność jest w znacznej mierze wynikiem
|
|
działania synchronizacji pionowej (V-Sync), która sztucznie
|
|
wyrównuje czasy klatek poprzez oczekiwanie na sygnał odświeżania monitora. Bez V-Sync
|
|
zmienność czasów klatek mogłaby być wyższa.
|
|
|
|
|
|
Analiza wywołań systemowych potwierdza intensywne wykorzystanie wielowątkowości:
|
|
\begin{itemize}
|
|
\item 81 utworzonych wątków wskazuje na rozbudowany system zadań.
|
|
\item Dominacja \texttt{futex} sugeruje częstą komunikację między wątkami.
|
|
\item Użycie zmiennych warunkowych z timeoutem świadczy o responsywnej architekturze.
|
|
\end{itemize}
|
|
|
|
Unity 2023 LTS stosuje architekturę DOTS (Data-Oriented Technology Stack) z systemem zadań (Job System), który automatycznie dystrybuuje pracę na
|
|
dostępne rdzenie procesora. Wyniki profilowania potwierdzają aktywne wykorzystanie tej architektury.
|
|
|
|
\subsection{Wyniki testów dla silnika Unreal Engine}
|
|
\label{subsec:wyniki-unreal}
|
|
|
|
Profilowanie silnika Unreal Engine 5.5 przeprowadzono przy użyciu NVIDIA Nsight Systems w
|
|
wersji 2025.5.2. Ze względu na problemy ze stabilnością
|
|
połączenia agenta Nsight podczas długich sesji profilowania, 90-sekundową rozgrywkę
|
|
podzielono na \textbf{trzy fazy po 30 sekund każda}:
|
|
\begin{itemize}
|
|
\item \textbf{Faza 1} (0--30 s): Początkowa rozgrywka z niską trudnością.
|
|
\item \textbf{Faza 2} (30--60 s): Środkowa rozgrywka ze średnią trudnością.
|
|
\item \textbf{Faza 3} (60--90 s): Końcowa rozgrywka z wysoką trudnością + ekran zwycięstwa.
|
|
\end{itemize}
|
|
|
|
Każda faza była uruchamiana z flagą \texttt{--start-time=N}, która przesuwa
|
|
zarówno stan gry
|
|
(w \texttt{STGGameDirector}), jak i poziom trudności
|
|
spawnu przeciwników \\ (w \texttt{STGEnemySpawner}) do odpowiedniej sekundy. Grę skompilowano w konfiguracji DebugGame, która zachowuje symbole
|
|
debugowania przy częściowych optymalizacjach.
|
|
|
|
|
|
Ze względu na bardzo dużą ilość danych generowanych przez Unreal Engine podczas śledzenia wywołań Vulkan API (około 13 milionów zdarzeń
|
|
na 30 sekund rozgrywki, w porównaniu z 0,5 miliona dla Unity), 90-sekundową rozgrywkę podzielono na \textbf{trzy fazy po 30 sekund każda}:
|
|
\begin{itemize}
|
|
\item \textbf{Faza 1} (0--30 s): Początkowa rozgrywka z niską trudnością.
|
|
\item \textbf{Faza 2} (30--60 s): Środkowa rozgrywka ze średnią trudnością.
|
|
\item \textbf{Faza 3} (60--90 s): Końcowa rozgrywka z wysoką trudnością + ekran zwycięstwa.
|
|
\end{itemize}
|
|
|
|
Każda faza była uruchamiana z flagą \texttt{--start-time=N},
|
|
która przesuwa zarówno stan gry
|
|
(w \texttt{STGGameDirector}), jak i poziom trudności spawnu przeciwników
|
|
\\ (w \texttt{STGEnemySpawner})
|
|
do odpowiedniej sekundy.
|
|
Grę skompilowano w konfiguracji DebugGame, która zachowuje symbole debugowania przy częściowych optymalizacjach.
|
|
|
|
Profilowanie przeprowadzono z wykorzystaniem tych samych metryk co dla Unity:
|
|
\begin{itemize}
|
|
\item \textbf{Śledzenia wywołań Vulkan API} (\texttt{--trace=vulkan}) -- przechwytywanie wszystkich funkcji Vulkan.
|
|
\item \textbf{Śledzenia wywołań systemowych} (\texttt{--trace=osrt}) -- przechwytywanie funkcji OS Runtime.
|
|
\item \textbf{Metryk sprzętowych GPU} (\texttt{--gpu-metrics-devices=0}) -- próbkowanie liczników wydajności GPU.
|
|
\end{itemize}
|
|
|
|
|
|
NVIDIA Nsight Systems zbiera metryki sprzętowe GPU poprzez bezpośredni dostęp do liczników
|
|
wydajności zintegrowanych w karcie graficznej.
|
|
Podczas trzech 35-sekundowych sesji (30 sekund rozgrywki + 5 sekund buforu) zebrano łącznie \textbf{1\,050\,555 próbek} dla każdej z
|
|
31 monitorowanych metryk.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Kluczowe metryki wykorzystania GPU dla silnika Unreal Engine (fazy 1--2, aktywna rozgrywka)}
|
|
\label{tab:unreal-gpu-metrics}
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Średnia} & \textbf{Min.} & \textbf{Maks.} \\
|
|
\hline
|
|
GPU Active [\%] & 90,98 & 0 & 100 \\
|
|
GR Active [\%] & 85,59 & 0 & 100 \\
|
|
SMs Active [\%] & 42,88 & 0 & 100 \\
|
|
Sync Compute in Flight [\%] & 43,23 & 0 & 100 \\
|
|
Async Compute in Flight [\%] & 0,17 & 0 & 35 \\
|
|
SM Issue [\%] & 13,94 & 0 & 99 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Metryka \texttt{GPU Active} określa procentowy udział czasu, w którym karta graficzna wykonuje jakąkolwiek pracę obliczeniową.
|
|
Średnia wartość \textbf{90,98\%} dla faz 1--2 (aktywna rozgrywka) oznacza, że GPU był niemal w pełni wykorzystany podczas właściwej rozgrywki.
|
|
Faza 3 wykazała niższą wartość (49,55\%) ze względu na włączenie ekranu zwycięstwa i procesu zamykania gry.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie metryk GPU między fazami testu Unreal Engine}
|
|
\label{tab:unreal-gpu-phases}
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Faza 1} & \textbf{Faza 2} & \textbf{Faza 3} \\
|
|
\hline
|
|
GPU Active [\%] & 91,16 & 90,80 & 49,55 \\
|
|
GR Active [\%] & 85,69 & 85,48 & 44,72 \\
|
|
SMs Active [\%] & 42,79 & 42,97 & 23,22 \\
|
|
Compute Warps [\%] & 13,05 & 13,00 & 7,03 \\
|
|
Pixel Warps [\%] & 9,45 & 9,26 & 4,68 \\
|
|
DRAM Read [\%] & 10,40 & 10,19 & 8,04 \\
|
|
DRAM Write [\%] & 10,19 & 10,00 & 5,60 \\
|
|
Liczba próbek & 350\,205 & 350\,249 & 350\,101 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:unreal-gpu-phases} pokazuje stabilność metryk GPU między fazami 1 i 2
|
|
różnice <0,5 pp.),
|
|
co potwierdza poprawność metodologii
|
|
fazowego profilowania.
|
|
Wyraźny spadek w fazie
|
|
3 odzwierciedla zakończenie aktywnej rozgrywki i przejście do ekranu zwycięstwa.
|
|
|
|
|
|
Metryka \texttt{GR Active} (Graphics Active) mierzy wykorzystanie silnika graficznego (ang. \textit{graphics engine}) karty NVIDIA, odpowiedzialnego
|
|
za wykonywanie potoków renderowania (vertex, tessellation, geometry, fragment shaders). Średnia wartość \textbf{85,59\%} dla aktywnej rozgrywki
|
|
stanowi 94\% wartości \texttt{GPU Active} (90,98\%), co oznacza, że praca graficzna dominuje nad obliczeniami ogólnego przeznaczenia (compute shaders).
|
|
|
|
Różnica około 5 punktów procentowych między \texttt{GPU Active} a \texttt{GR Active} odpowiada pracy wykonanej przez jednostki compute i operacje
|
|
kopiowania pamięci, w tym asynchroniczny transfer danych przez Async Copy Engine (aktywny w 24--25\% czasu w fazach 1--2).
|
|
|
|
|
|
\texttt{SMs Active} (Streaming Multiprocessors Active) na poziomie \textbf{42,88\%} wskazuje, że średnio mniej niż połowa dostępnych multiprocesorów
|
|
strumieniowych jest aktywna jednocześnie. Karta NVIDIA RTX 3090 posiada 82 jednostki SM, więc średnio około 35 z nich wykonywało pracę w danym momencie.
|
|
|
|
Wartość \texttt{Sync Compute in Flight} (43,23\%) wskazuje na znaczące wykorzystanie synchronicznych shaderów obliczeniowych,
|
|
prawdopodobnie do operacji post-processingu, culling GPU lub przygotowania danych renderowania.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Metryki przepustowości pamięci GPU dla silnika Unreal Engine (fazy 1--2)}
|
|
\label{tab:unreal-memory-metrics}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Średnia (\%)} & \textbf{Maks. (\%)} \\
|
|
\hline
|
|
DRAM Read Bandwidth & 10,30 & 68,0 \\
|
|
DRAM Write Bandwidth & 10,10 & 78,0 \\
|
|
PCIe RX Throughput & 1,50 & 96,0 \\
|
|
PCIe TX Throughput & 1,39 & 17,0 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:unreal-memory-metrics} przedstawia metryki przepustowości pamięci. Średnie wykorzystanie przepustowości odczytu
|
|
DRAM (\textbf{10,30\%}) i zapisu (\textbf{10,10\%}) jest umiarkowane, wskazując że pamięć nie stanowi głównego wąskiego gardła.
|
|
Wartości maksymalne (68\% i 78\%) pokazują, że w momentach szczytowych obciążenia przepustowość pamięci jest intensywnie wykorzystywana.
|
|
|
|
Stosunek odczytu do zapisu (10,30:10,10 $\approx$ 1,02:1) jest zbliżony do jedności, co sugeruje zbalansowany przepływ danych --
|
|
typowy dla nowoczesnych technik renderowania z wieloma przejściami i render targets.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wykorzystanie różnych typów wątków shader GPU w silniku Unreal Engine (fazy 1--2)}
|
|
\label{tab:unreal-warps}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Typ wątków (warps)} & \textbf{Średnia (\%)} & \textbf{Maks. (\%)} \\
|
|
\hline
|
|
Compute Warps in Flight & 13,03 & 93,0 \\
|
|
Pixel Warps in Flight & 9,36 & 99,0 \\
|
|
Vertex/Tess/Geometry Warps & 0,45 & 10,0 \\
|
|
Unallocated Warps in Active SMs & 20,73 & 90,0 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:unreal-warps} przedstawia rozkład typów aktywnych wątków shader
|
|
(warps -- grupy 32 wątków CUDA wykonywanych synchronicznie).
|
|
Dominacja \texttt{Compute Warps} (13,03\%) nad \texttt{Pixel Warps} (9,36\%) wskazuje na znaczące wykorzystanie compute shaderów, prawdopodobnie do:
|
|
\begin{itemize}
|
|
\item Culling (odrzucanie niewidocznych obiektów na GPU).
|
|
\item Post-processing i tone mapping.
|
|
\item Symulacji cząsteczek lub fizyki na GPU.
|
|
\end{itemize}
|
|
|
|
Niski udział \texttt{Vertex/Tess/Geometry Warps} (0,45\%) sugeruje prostą geometrię sceny bez intensywnego wykorzystania teselacji --
|
|
co jest zgodne z charakterystyką testowanej gry bullet-hell, gdzie większość efektów wizualnych to płaskie sprite'y i efekty cząsteczkowe.
|
|
|
|
\texttt{Unallocated Warps in Active SMs} (20,73\%) reprezentuje niewykorzystaną pojemność
|
|
aktywnych multiprocesorów. Wartość ta wskazuje na
|
|
potencjał optymalizacji przez zwiększenie granularności pracy lub lepsze grupowanie operacji.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Częstotliwości zegara GPU podczas testu Unreal Engine}
|
|
\label{tab:unreal-gpu-clocks}
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Zegar} & \textbf{Średnia (MHz)} & \textbf{Min. (MHz)} & \textbf{Maks. (MHz)} \\
|
|
\hline
|
|
GPC Clock (Graphics) & 1\,887 & 1\,288 & 1\,965 \\
|
|
SYS Clock (Memory) & 1\,596 & 1\,080 & 1\,665 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Częstotliwości zegara (tabela~\ref{tab:unreal-gpu-clocks}) pokazują, że GPU działał ze średnią częstotliwością 1\,887 MHz
|
|
(96\% maksymalnej 1\,965 MHz), co wskazuje na niskie obciążenie termiczne pozwalające na utrzymanie wysokich częstotliwości boost bez throttlingu.
|
|
Minimalne wartości odpowiadają krótkim momentom niższego obciążenia podczas przejść między klatkami.
|
|
|
|
|
|
Dzięki zastosowaniu profilowania fazowego uzyskano \textbf{kompletne dane} śledzenia Vulkan API z całego 90-sekundowego przebiegu gry Unreal Engine.
|
|
Dane podzielone na trzy fazy (0--30s, 30--60s, 60--90s) umożliwiają szczegółową analizę ewolucji wykorzystania GPU w czasie rozgrywki.
|
|
\begingroup
|
|
\setlength{\textfloatsep}{0.5\baselineskip}
|
|
\setlength{\intextsep}{0.5\baselineskip}
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie wywołań Vulkan API silnika Unreal Engine między fazami}
|
|
\label{tab:unreal-vulkan-phases}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Faza 1 (0--30s)} & \textbf{Faza 2 (30--60s)} & \textbf{Faza 3 (60--90s)} \\
|
|
\hline
|
|
Liczba klatek (vkQueuePresentKHR) & 9\,964 & 10\,165 & 4\,846 \\
|
|
Średni FPS & 332 & 339 & 162 \\
|
|
vkCreateComputePipelines & 231 & 233 & 231 \\
|
|
vkCreateGraphicsPipelines & 793 & 797 & 816 \\
|
|
vkQueueSubmit & 166\,918 & 186\,589 & 74\,393 \\
|
|
Submit/klatkę & 16,2 & 16,2 & 16,2 \\
|
|
vkCmdBindPipeline & 2\,236\,013 & 2\,528\,014 & 1\,007\,615 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\endgroup
|
|
Tabela~\ref{tab:unreal-vulkan-phases} ujawnia znaczącą dynamikę wydajności między fazami.
|
|
Fazy 1 i 2 (aktywna rozgrywka) osiągają wysoką wydajność
|
|
(332--339 FPS), natomiast faza 3 pokazuje \textbf{znaczący spadek do 162 FPS} -- redukcję o
|
|
ponad 50\%. Spadek ten występuje w końcowej fazie
|
|
rozgrywki, gdy na ekranie znajduje się największa liczba przeciwników i pocisków, co stanowi
|
|
najbardziej wymagający moment dla silnika renderującego.
|
|
Dodatkowo faza 3 zawiera ekran zwycięstwa, który również wpływa na średnią wydajność.
|
|
|
|
\textbf{Uwaga metodologiczna:} Wartość 162 FPS z fazy 3 lepiej reprezentuje wydajność w wymagających
|
|
scenach niż średnie z faz 1--2, ponieważ faza 3 zawiera moment największego obciążenia gry
|
|
(maksymalna liczba przeciwników i pocisków na ekranie).
|
|
|
|
Stosunek wywołań \texttt{vkQueueSubmit} do \texttt{vkQueuePresentKHR} pozostaje stabilny na poziomie \textbf{16,2:1} we wszystkich fazach, co
|
|
wskazuje na konsystentną architekturę potoku renderowania niezależną od obciążenia sceny.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unreal Engine -- tworzenie potoków (wszystkie fazy)}
|
|
\label{tab:unreal-vulkan-pipelines}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Czas (\%)} & \textbf{Wywołania} & \textbf{Śr. (ms)} & \textbf{Maks. (ms)} \\
|
|
\hline
|
|
\multicolumn{5}{|c|}{\textit{Faza 1 (0--30s)}} \\
|
|
\hline
|
|
\texttt{vkCreateComputePipelines} & 47,3 & 231 & 18,63 & 50,40 \\
|
|
\texttt{vkCreateGraphicsPipelines} & 10,0 & 793 & 1,14 & 36,68 \\
|
|
\texttt{vkCreateDevice} & 6,5 & 1 & 590,50 & 590,50 \\
|
|
\hline
|
|
\multicolumn{5}{|c|}{\textit{Faza 2 (30--60s)}} \\
|
|
\hline
|
|
\texttt{vkCreateComputePipelines} & 47,1 & 233 & 18,92 & 56,01 \\
|
|
\texttt{vkCreateGraphicsPipelines} & 11,2 & 797 & 1,31 & 36,43 \\
|
|
\texttt{vkCreateDevice} & 5,8 & 1 & 541,36 & 541,36 \\
|
|
\hline
|
|
\multicolumn{5}{|c|}{\textit{Faza 3 (60--90s)}} \\
|
|
\hline
|
|
\texttt{vkCreateComputePipelines} & 57,4 & 231 & 19,21 & 51,95 \\
|
|
\texttt{vkCreateGraphicsPipelines} & 14,6 & 816 & 1,39 & 40,88 \\
|
|
\texttt{vkCreateDevice} & 7,4 & 1 & 572,38 & 572,38 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
W przeciwieństwie do Unity, gdzie dominującą funkcją był \texttt{vkWaitForFences},
|
|
w Unreal Engine \textbf{57--72\% czasu} Vulkan API
|
|
pochłonęły funkcje tworzenia potoków.
|
|
Co istotne, liczba wywołań \texttt{vkCreateComputePipelines} i
|
|
\texttt{vkCreateGraphicsPipelines} jest
|
|
\textbf{niemal identyczna we wszystkich trzech fazach}, co wskazuje na strategię \textbf{ciągłej rekompilacji potoków} (Pipeline State Object)
|
|
przez cały czas działania gry.
|
|
|
|
Łącznie w każdej 30-sekundowej fazie tworzonych jest około \textbf{1\,024--1\,047 potoków} (231 compute + 793--816 graphics). Porównując z Unity
|
|
(który utworzył tylko 3 potoki graficzne w całym 95-sekundowym teście), Unreal Engine generuje \textbf{ponad 300 razy więcej potoków}.
|
|
|
|
Średni czas tworzenia potoku compute (18,63--19,21 ms) jest ponad \textbf{14 razy dłuższy}
|
|
niż dla potoku graficznego (1,14--1,39 ms). Różnica ta
|
|
wynika z większej złożoności shaderów obliczeniowych używanych przez Unreal Engine do culling, post-processingu i systemu Nanite.
|
|
|
|
Wywołanie \texttt{vkCreateDevice} pojawia się raz w każdej fazie z czasem 541--590 ms, co odpowiada momentowi startu gry w tej fazie -- narzędzie
|
|
Nsight Systems tworzy nową sesję dla każdej fazy.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unreal Engine -- synchronizacja i prezentacja (faza 2)}
|
|
\label{tab:unreal-vulkan-sync}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Czas (\%)} & \textbf{Wywołania} & \textbf{Śr. ($\mu$s)} & \textbf{Maks. (ms)} \\
|
|
\hline
|
|
\texttt{vkQueuePresentKHR} & 9,5 & 11\,531 & 77,05 & 0,90 \\
|
|
\texttt{vkQueueSubmit} & 7,8 & 186\,589 & 3,92 & 1,64 \\
|
|
\texttt{vkWaitForFences} & 0,5 & 11\,627 & 3,63 & 2,61 \\
|
|
\texttt{vkAcquireNextImageKHR} & 0,1 & 11\,531 & 0,89 & 7,55 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
W ostrzym kontraście z Unity (gdzie \texttt{vkWaitForFences} stanowił 95,2\% czasu), w Unreal Engine funkcja ta pochłonęła zaledwie
|
|
\textbf{0,5\% czasu} ze średnim czasem oczekiwania 3,63 $\mu$s. Tak niski czas oczekiwania wskazuje na:
|
|
\begin{itemize}
|
|
\item Efektywne wykorzystanie wielokrotnego buforowania (triple buffering).
|
|
\item Asynchroniczne przesyłanie pracy do GPU bez blokowania.
|
|
\item Lepsze rozłożenie pracy między CPU a GPU eliminujące przestoje.
|
|
\end{itemize}
|
|
|
|
Stosunek wywołań \texttt{vkQueueSubmit} (186\,589) do \texttt{vkQueuePresentKHR} (11\,531)
|
|
wynosi \textbf{16,2:1}, co oznacza średnio 16
|
|
przesyłek pracy na klatkę. Jest to znacznie więcej niż w Unity (2:1), odzwierciedlając
|
|
bardziej złożony potok renderowania Unreal Engine z
|
|
wieloma przebiegami (deferred rendering, post-processing, UI).
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania Vulkan API silnika Unreal Engine -- bufory poleceń (wszystkie fazy łącznie)}
|
|
\label{tab:unreal-vulkan-cmd}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Wywołania} & \textbf{Śr. ($\mu$s)} & \textbf{Maks. ($\mu$s)} \\
|
|
\hline
|
|
\texttt{vkCmdBindPipeline} & 5\,771\,642 & 0,24 & 2\,722 \\
|
|
\texttt{vkCmdPipelineBarrier2KHR} & 4\,090\,071 & 0,28 & 942 \\
|
|
\texttt{vkBeginCommandBuffer} & 427\,903 & 1,15 & 902 \\
|
|
\texttt{vkEndCommandBuffer} & 427\,900 & 0,78 & 228 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Liczba wywołań \texttt{vkCmdBindPipeline} (\textbf{5\,771\,642}
|
|
łącznie we wszystkich fazach) jest ponad \textbf{213 razy większa} niż w
|
|
Unity (27\,027), co odpowiada około 218 zmianom potoku na klatkę. Tak wysoka wartość wynika z:
|
|
\begin{itemize}
|
|
\item Dynamicznego systemu materiałów Unreal Engine.
|
|
\item Wielu wariantów shaderów dla różnych kombinacji oświetlenia.
|
|
\item Złożonego potoku renderowania z wieloma przebiegami.
|
|
\end{itemize}
|
|
|
|
Funkcja \texttt{vkCmdPipelineBarrier2KHR} (4\,090\,071 wywołań) synchronizuje dostęp do zasobów w obrębie GPU -- wysoka liczba wywołań wskazuje
|
|
na staranną kontrolę zależności między operacjami, typową dla nowoczesnych technik renderowania wykorzystujących wiele render targets.
|
|
|
|
|
|
Interesującą obserwacją jest obecność wywołań związanych z ray tracingiem we wszystkich fazach:
|
|
\begin{itemize}
|
|
\item \texttt{vkCreateAccelerationStructureKHR}: 23\,960 + 26\,275 + 11\,884 = 62\,119 wywołań.
|
|
\item \texttt{vkDestroyAccelerationStructureKHR}: 20\,571 + 23\,063 + 9\,181 = 52\,815 wywołań.
|
|
\item \texttt{vkGetAccelerationStructureBuildSizesKHR}: 41\,161 + 46\,147 + 18\,379 = 105\,687 wywołań.
|
|
\end{itemize}
|
|
|
|
Pomimo że testowana gra nie wykorzystuje widocznych efektów ray tracingu, Unreal Engine przygotowuje struktury akceleracji BVH
|
|
(Bounding Volume Hierarchy), prawdopodobnie do potencjalnego użycia w globalnym oświetleniu lub śledzeniu promieni. Nierówna liczba utworzeń
|
|
i zniszczeń sugeruje akumulację struktur w pamięci GPU podczas rozgrywki.
|
|
|
|
|
|
Podobnie jak dla Unity, Nsight Systems przechwycił wywołania funkcji systemowych we wszystkich trzech fazach, umożliwiając analizę zachowania
|
|
wielowątkowego Unreal Engine. Łącznie zarejestrowano ponad \textbf{9 milionów wywołań} funkcji synchronizacji.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Wywołania systemowe silnika Unreal Engine -- synchronizacja wątków (wszystkie fazy)}
|
|
\label{tab:unreal-osrt-sync}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|r|}
|
|
\hline
|
|
\textbf{Funkcja} & \textbf{Czas (\%)} & \textbf{Wywołania} & \textbf{Śr. (ms)} & \textbf{Maks. (s)} \\
|
|
\hline
|
|
\texttt{pthread\_cond\_wait} & 64,6 & 3\,095\,188 & 0,97 & 22,23 \\
|
|
\texttt{pthread\_cond\_timedwait} & 19,2 & 163\,783 & 5,46 & 2,00 \\
|
|
\texttt{poll} & 7,2 & 215\,851 & 1,56 & 0,10 \\
|
|
\texttt{usleep} & 4,7 & 26\,062 & 7,79 & 0,01 \\
|
|
\texttt{select} & 2,4 & 1\,039 & 99,72 & 0,10 \\
|
|
\texttt{nanosleep} & 0,6 & 755 & 35,55 & 0,20 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Funkcja \texttt{pthread\_cond\_wait} pochłonęła \textbf{64,6\% czasu} przy \textbf{3\,095\,188 wywołaniach} we wszystkich trzech fazach.
|
|
Jest to funkcja POSIX do oczekiwania na zmienną warunkową, używana gdy wątek musi czekać na spełnienie określonego warunku sygnalizowanego przez
|
|
inny wątek.
|
|
|
|
Tak wysoka liczba wywołań (ponad 40 razy więcej niż dla Unity)
|
|
odzwierciedla architekturę wielowątkową Unreal Engine opartą na systemie
|
|
\textbf{TaskGraph}. System ten dekomponuje pracę renderowania na małe zadania (ang. \textit{tasks}),
|
|
które są wykonywane przez pulę wątków roboczych.
|
|
Każde zadanie po zakończeniu sygnalizuje swoją gotowość, a zależne zadania są budzone poprzez
|
|
\texttt{pthread\_cond\_signal}/\texttt{pthread\_cond\_broadcast}.
|
|
|
|
Średni czas pojedynczego oczekiwania (0,97 ms) jest krótki, co wskazuje na częste, ale krótkotrwałe synchronizacje --
|
|
typowe dla drobnoziarnistego paralelizmu. Maksymalny czas 22,23 sekundy odpowiada prawdopodobnie wywołaniu podczas długotrwałej operacji
|
|
inicjalizacyjnej w fazie 2.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie wywołań synchronizacyjnych między fazami Unreal Engine}
|
|
\label{tab:unreal-osrt-phases}
|
|
\small
|
|
\begin{tabular}{|l|r|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Faza 1} & \textbf{Faza 2} & \textbf{Faza 3} \\
|
|
\hline
|
|
\texttt{pthread\_cond\_wait} wywołań & 1\,166\,913 & 1\,253\,746 & 674\,529 \\
|
|
\texttt{pthread\_cond\_wait} czas (\%) & 63,2 & 65,1 & 66,4 \\
|
|
\texttt{pthread\_cond\_timedwait} wywołań & 68\,267 & 63\,863 & 31\,653 \\
|
|
\texttt{pthread\_cond\_broadcast} wywołań & 668\,650 & 747\,301 & 337\,258 \\
|
|
\texttt{backtrace} wywołań & 2\,306\,885 & 2\,289\,546 & 988\,685 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:unreal-osrt-phases} pokazuje konsystencję wzorców wywołań między fazami 1 i 2 (aktywna rozgrywka) oraz wyraźny spadek w
|
|
fazie 3 (zawierającej ekran zwycięstwa). Szczególnie interesująca jest wysoka liczba wywołań \texttt{backtrace} (ponad 5,5 miliona łącznie),
|
|
co sugeruje intensywne wykorzystanie mechanizmów debugowania lub profilowania wbudowanych w Unreal Engine nawet w konfiguracji DebugGame.
|
|
|
|
(19,2\%, 163\,783 wywołań) różni się od
|
|
\texttt{pthread\_cond\_wait} możliwością określenia maksymalnego czasu
|
|
oczekiwania. Użycie tej funkcji wskazuje na mechanizmy:
|
|
\begin{itemize}
|
|
\item Timeoutów zapobiegających zakleszczeniom (deadlock prevention).
|
|
\item Okresowego sprawdzania warunków (polling pattern).
|
|
\item Synchronizacji czasowej dla frame pacing.
|
|
\end{itemize}
|
|
|
|
Średni czas 5,46 ms sugeruje użycie do synchronizacji między-klatkowej,
|
|
gdzie wątki oczekują na gotowość kolejnej klatki z timeout'em
|
|
zapobiegającym nieskończonemu oczekiwaniu w przypadku błędu.
|
|
|
|
|
|
Funkcja \texttt{usleep} (4,7\%, 26\,062 wywołań, średnio 7,79 ms) wprowadza precyzyjne opóźnienia czasowe. Średni czas 7,79 ms jest zbliżony do
|
|
czasu klatki przy ~128 FPS, co może sugerować mechanizm regulacji tempa renderowania lub oszczędzanie energii poprzez redukcję spin-waitingu.
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie mechanizmów synchronizacji Unity i Unreal Engine (zaktualizowane)}
|
|
\label{tab:sync-comparison}
|
|
\small
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Unity} & \textbf{Unreal Engine} \\
|
|
\hline
|
|
Dominujący mechanizm & \texttt{futex} (95,9\%) & \texttt{pthread\_cond\_wait} (64,6\%) \\
|
|
Liczba wywołań synchronizacji & 247 & 3\,095\,188 \\
|
|
Średni czas wywołania & 444,07 ms & 0,97 ms \\
|
|
Utworzone wątki & 81 & $\sim$83 \\
|
|
Liczba próbek GPU (10 kHz) & -- & 1\,050\,555 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
|
|
Tabela~\ref{tab:sync-comparison} ujawnia fundamentalną różnicę architektoniczną między silnikami:
|
|
|
|
\textbf{Unity} stosuje mechanizm \texttt{futex} z niewielką liczbą wywołań (247) i
|
|
długim średnim czasem (444 ms). Wskazuje to na architekturę z
|
|
większymi, bardziej autonomicznymi jednostkami pracy i rzadszą synchronizacją między wątkami.
|
|
|
|
\textbf{Unreal Engine} używa \texttt{pthread\_cond\_wait} z ogromną liczbą wywołań
|
|
(ponad 3 miliony w 90-sekundowym teście) i bardzo krótkim średnim
|
|
czasem (0,97 ms). Odzwierciedla to drobnoziarnisty paralelizm systemu TaskGraph, gdzie praca jest dzielona na małe zadania często komunikujące się ze
|
|
sobą.
|
|
|
|
Różnica ta ma implikacje praktyczne:
|
|
\begin{itemize}
|
|
\item \textbf{Skalowalność}: Drobnoziarnisty model Unreal lepiej skaluje się na procesory z wieloma rdzeniami.
|
|
\item \textbf{Narzut synchronizacji}: Model Unity ma mniejszy narzut z powodu rzadszych wywołań.
|
|
\item \textbf{Responsywność}: Unreal może szybciej reagować na zmiany (np. przerwanie zadania).
|
|
\item \textbf{Debugowanie}: Model Unity jest łatwiejszy do analizy ze względu na prostszą strukturę.
|
|
\end{itemize}
|
|
|
|
|
|
Zebrane dane z trzech faz profilowania pozwalają na charakterystykę architektonicznych aspektów silnika Unreal Engine:
|
|
|
|
|
|
Unreal Engine 5 stosuje zaawansowaną architekturę wielowątkową złożoną z:
|
|
\begin{itemize}
|
|
\item \textbf{Game Thread} -- główny wątek logiki gry.
|
|
\item \textbf{Render Thread} -- wątek przygotowujący polecenia renderowania.
|
|
\item \textbf{RHI Thread} (Render Hardware Interface) -- wątek komunikujący się z API graficznym.
|
|
\item \textbf{Worker Threads} -- pula wątków roboczych systemu TaskGraph.
|
|
\end{itemize}
|
|
|
|
Obserwowana dominacja \texttt{pthread\_cond\_wait} (3+ miliony wywołań) potwierdza intensywną
|
|
komunikację między tymi wątkami.
|
|
Wysokie wykorzystanie
|
|
GPU (90,98\% w fazach aktywnej rozgrywki) przy jednoczesnej intensywnej synchronizacji CPU sugeruje efektywne wykorzystanie zasobów obu procesorów.
|
|
|
|
|
|
Na podstawie zebranych metryk można scharakteryzować profil obciążenia GPU:
|
|
\begin{itemize}
|
|
\item \textbf{Charakter pracy}: Mieszany graficzno-obliczeniowy (GR Active 85,59\%, Sync Compute 43,23\%).
|
|
\item \textbf{Wykorzystanie SM}: Umiarkowane (42,88\%), wskazujące na potencjał optymalizacji.
|
|
\item \textbf{Przepustowość pamięci}: Niewysoka (10,30\% odczyt, 10,10\% zapis), nie jest wąskim gardłem.
|
|
\item \textbf{Transfer PCIe}: Niski (1,50\% RX), dane pozostają w pamięci GPU.
|
|
\item \textbf{Async Copy Engine}: Aktywny w 24--25\% czasu, wskazując na efektywne wykorzystanie asynchronicznych transferów.
|
|
\end{itemize}
|
|
|
|
|
|
Porównanie faz 1 i 2 (tabela~\ref{tab:unreal-gpu-phases}) pokazuje niezwykłą stabilność metryk GPU:
|
|
\begin{itemize}
|
|
\item GPU Active: różnica 0,36 pp. (91,16\% vs 90,80\%).
|
|
\item GR Active: różnica 0,21 pp. (85,69\% vs 85,48\%).
|
|
\item SMs Active: różnica 0,18 pp. (42,79\% vs 42,97\%).
|
|
\end{itemize}
|
|
|
|
Ta konsystencja potwierdza poprawność metodologii fazowego profilowania i sugeruje deterministyczne zachowanie silnika renderującego niezależnie od
|
|
poziomu trudności gry.
|
|
|
|
|
|
Dzięki profilowaniu fazowemu uzyskano kompletne dane śledzenia Vulkan API i metryk GPU dla całej 90-sekundowej rozgrywki.
|
|
Zebrane dane (ponad 32 miliony zdarzeń Vulkan API, ponad milion próbek GPU i ponad 9 milionów wywołań systemowych) dostarczają
|
|
kompleksowego wglądu w charakterystykę wydajnościową silnika, umożliwiając bezpośrednie porównanie z Unity.
|
|
|
|
\subsection{Analiza porównawcza}
|
|
\label{subsec:analiza-porownawcza}
|
|
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie czasów klatek i wydajności między silnikami}
|
|
\label{tab:frame-time-comparison}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Unity} & \textbf{Unreal Engine} \\
|
|
\hline
|
|
Średni FPS (fazy 1--2) & 164 (V-Sync) & 332--339 \\
|
|
Średni FPS (faza 3, wymagająca) & 164 (V-Sync) & 162 \\
|
|
Całkowita liczba klatek (90s) & 14\,765 & 24\,975 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:frame-time-comparison} przedstawia porównanie wydajności obu silników.
|
|
|
|
Kluczową obserwacją jest to, że \textbf{Unity działał z włączonym V-Sync} na monitorze 165 Hz,
|
|
co ograniczało wydajność do około 164 FPS niezależnie od obciążenia sceny. Unreal Engine działał
|
|
bez V-Sync, osiągając 332--339 FPS w fazach 1--2, jednak \textbf{w fazie 3 (najbardziej wymagającej)
|
|
wydajność spadła do 162 FPS} -- wartości zbliżonej do Unity.
|
|
|
|
Ten wynik sugeruje, że przy wysokim obciążeniu sceny (maksymalna liczba przeciwników i pocisków)
|
|
oba silniki osiągają porównywalną wydajność, natomiast Unreal Engine jest w stanie wykorzystać
|
|
zapas mocy obliczeniowej GPU przy mniejszym obciążeniu.
|
|
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie wykorzystania GPU między silnikami}
|
|
\label{tab:gpu-comparison}
|
|
\footnotesize
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Unity} & \textbf{Unreal Engine} \\
|
|
\hline
|
|
Dominująca funkcja Vulkan & vkWaitForFences (95,2\%) & vkCreateComputePipelines (47--57\%) \\
|
|
Charakter ograniczenia & GPU-bound & Pipeline compilation \\
|
|
vkQueueSubmit / klatkę & 2 & 16,2 \\
|
|
vkCmdBindPipeline / klatkę & 2 & 218 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Analiza wywołań Vulkan API ujawnia fundamentalnie różne profile obciążenia (tabela~\ref{tab:gpu-comparison}):
|
|
|
|
Dominacja \texttt{vkWaitForFences} (95,2\% czasu) wskazuje, że CPU efektywnie
|
|
przygotowuje pracę i oczekuje na GPU.
|
|
Jest to pożądany wzorzec w aplikacjach graficznych, gdzie GPU wykonuje
|
|
większość obliczeń. Niski stosunek
|
|
\texttt{vkQueueSubmit}/klatkę (2:1) świadczy o prostym, dwuetapowym potoku
|
|
renderowania.
|
|
|
|
W Unreal Engine dominującymi operacjami były \texttt{vkCreateComputePipelines}
|
|
oraz \texttt{vkCreateGraphicsPipelines},
|
|
pochłaniające łącznie 57--72\% czasu Vulkan. Silnik tworzy około \textbf{1000 potoków w każdej 30-sekundowej fazie}
|
|
(vs 3 potoki w całym teście Unity), co wskazuje na strategię dynamicznej kompilacji shaderów.
|
|
|
|
Wysoki stosunek \texttt{vkCmdBindPipeline}/klatkę (218:1 vs 2:1) odzwierciedla złożony system materiałów Unreal
|
|
z wieloma wariantami shaderów, co wprowadza znaczący narzut zmian stanu GPU.
|
|
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Porównanie mechanizmów synchronizacji między silnikami}
|
|
\label{tab:threading-comparison}
|
|
\small
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Unity} & \textbf{Unreal Engine} \\
|
|
\hline
|
|
Główny mechanizm synchronizacji & futex & pthread\_cond\_wait \\
|
|
Liczba wywołań synchronizacji & 247 & 3\,095\,188 \\
|
|
Średni czas wywołania & 444 ms & 0,97 ms \\
|
|
Model paralelizmu & Gruboziarnisty & Drobnoziarnisty \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Tabela~\ref{tab:threading-comparison} ujawnia fundamentalną różnicę architektoniczną:
|
|
|
|
\textbf{Unity} stosuje model gruboziarnistego paralelizmu z rzadkimi, ale długimi synchronizacjami. Wątki wykonują
|
|
większe jednostki pracy autonomicznie, co minimalizuje narzut komunikacji.
|
|
|
|
\textbf{Unreal Engine} implementuje drobnoziarnisty paralelizm poprzez system TaskGraph. Praca jest dzielona na tysiące
|
|
małych zadań często komunikujących się ze sobą (ponad 3 miliony wywołań synchronizacji w 90 sekund).
|
|
|
|
\subsection{Podsumowanie wyników testów wydajności}
|
|
\label{subsec:podsumowanie-testow}
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\caption{Zestawienie kluczowych wyników testów wydajności}
|
|
\label{tab:wyniki-wydajnosci}
|
|
\begin{tabular}{|l|r|r|}
|
|
\hline
|
|
\textbf{Metryka} & \textbf{Unity} & \textbf{Unreal Engine} \\
|
|
\hline
|
|
Średni FPS (fazy 1--2) & 164 (V-Sync) & 332--339 \\
|
|
FPS w wymagającej scenie & 132 (1\% low) & 162 (faza 3) \\
|
|
GPU Active (\%) & 23 & 91 (fazy 1--2), 50 (faza 3) \\
|
|
Dominujące wąskie gardło & GPU (rendering) & CPU (kompilacja potoków) \\
|
|
Wywołania Vulkan API & $\sim$0,5 mln & $\sim$32 mln \\
|
|
Wywołania synchronizacji OS & 29\,383 & $\sim$9 mln \\
|
|
Potoki graficzne utworzone & 3 & $\sim$2\,400 \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Przeprowadzone testy wydajnościowe pozwalają na sformułowanie następujących wniosków:
|
|
|
|
\begin{enumerate}
|
|
\item \textbf{Wydajność w wymagających scenach}: W fazie 3 (maksymalne obciążenie)
|
|
oba silniki osiągają zbliżoną wydajność: Unity 1\% low 132 FPS vs Unreal 162 FPS. Różnica
|
|
około 23\% na korzyść Unreal wynika częściowo z różnych konfiguracji V-Sync.
|
|
|
|
\item \textbf{Wykorzystanie GPU}: Unity wykorzystuje jedynie 23\% mocy GPU
|
|
\\ (ograniczony V-Sync),
|
|
podczas gdy Unreal Engine osiąga 91\% wykorzystania w fazach 1--2. Sugeruje to znaczny
|
|
zapas wydajności Unity przy wyłączonym V-Sync.
|
|
|
|
\item \textbf{Stabilność}: Unity wykazał stabilne czasy klatek dzięki
|
|
V-Sync, natomiast
|
|
Unreal Engine pokazał dużą zmienność między fazami (332--339 FPS w fazach 1--2 vs 162 FPS
|
|
w fazie 3) -- spadek o ponad 50\%.
|
|
|
|
\item \textbf{Architektura}: Silniki stosują fundamentalnie różne podejścia do
|
|
wielowątkowości i zarządzania potokami renderowania. Unity używa gruboziarnistego paralelizmu
|
|
z rzadkimi synchronizacjami, podczas gdy Unreal stosuje drobnoziarnisty system TaskGraph
|
|
z milionami wywołań synchronizacyjnych.
|
|
|
|
\item \textbf{Narzut Unreal}: Dynamiczna kompilacja potoków (ponad 1000 potoków na
|
|
\\ 30-sekundową fazę vs 3 w całym teście Unity) i 60-krotnie większa liczba wywołań Vulkan API
|
|
stanowią znaczący narzut, który może przyczyniać się do spadków wydajności w wymagających scenach.
|
|
\end{enumerate}
|