Dziennik Twórców Stellaris #181: Wielowątkowość i Czasy Ładowania

Witam wszystkich! Z tej strony francuskie studio Paradox!

W imieniu całego zespołu Stellaris mamy nadzieję, że spędziliście udane wakacje, biorąc pod uwagę obecne wydarzenia na świecie!

Wszyscy wróciliśmy już do pracy, chociaż na ten moment pracujemy zdalnie. Zapowiada się bardzo emocjonująca jesień i zima z wieloma ciekawymi nowinkami! Jesteśmy niesamowicie podekscytowani, że będziemy mogli dzielić się nimi w nadchodzących tygodniach i miesiącach!

MatRopert (Stellaris Programmer)

Dzisiaj uchylę rąbka tajemnicy nadchodzącej aktualizacji 2.8 razem z kilkoma sprawami technicznymi, nad którymi my programiści pracowaliśmy latem. Reszta zespołu ujawni więcej na temat nadchodzącej zawartości i funkcji w kolejnych dziennikach.

Nie przedłużając, porozmawiajmy o wątkach!

Wątki? Jakie wątki?

Jest pewien mem, w którym fani zastanawiają się, która rzecz będzie pierwsza: Victoria III czy gra PDS wykorzystująca więcej niż jeden wątek.


Nie kłamcie! Wiemy, że niektórzy z was uważają, że tak wyglądają u nas ważne spotkania decyzyjne.

Obawiam się, że będę musiał (ponownie) rozwiać mit: wszystkie produkowane obecnie gry PDS są wielowątkowe, od EU4 do CK3. Nawet Stellaris! Aby lepiej wyjaśnić mem i skąd pochodzi, musimy przejść przez krótką historię. Słyszeliśmy, że lubicie historie.

Przez długi czas branża oprogramowania opierała się na „prawie Moore’a”, zgodnie z którym zakładano, że procesor zbudowany w ciągu dwóch lat będzie mniej więcej dwa razy wydajniejszy niż obecnie. Było to szczególnie prawdziwe w latach 90., kiedy procesory przeszły z 50 MHz do 1 GHz w ciągu zaledwie dekady. Trend utrzymywał się do 2005 roku, kiedy osiągnięto 3,8 GHz. A potem prędkość zegara przestała rosnąć i od 15 lat częstotliwość procesorów pozostaje mniej więcej taka sama. Jak się okazuje, prawa fizyki sprawiają, że zwiększanie prędkości powyżej 3-4 GHz jest dość nieefektywne. Zamiast tego producenci procesorów poszli w innym kierunku i zaczęli „dzielić” swoje procesory na rdzenie i wątki sprzętowe. Dlatego w dzisiejszych czasach bardziej sprawdzamy, jak dużo procesor ma rdzeni, aniżeli jego taktowanie. Prawo Moore’a jest nadal aktualne, ale patrząc ze strategicznego punktu widzenia w branży procesorów, która w częstotliwości osiągnęła już swój limit opłacalności, lepiej jest dla nich pójść w ilość, a nie jakość rdzenia.

Ta zmiana gruntownie zmieniła branżę oprogramowania, ponieważ pisanie kodu, który będzie działał szybciej na procesorze z wyższą częstotliwością zegara jest trywialne: większość kodu zaadoptuje się naturalnie. Ale wykorzystanie wątków i rdzeni to inna historia. Programy nie „dzielą” swojej pracy w magiczny sposób na 2, 4 lub 8, aby mogły działać na kilku rdzeniach jednocześnie. Wszystko zależy od nas, programistów jak tą pracą pokierujemy.

Wątki są a wydajność bez zmian

To sprowadza nas ciągle z powrotem do naszych gier, no i obawy, które czytamy na forum „czy ta gra używa wielu wątków?”. Odpowiedź brzmi oczywiście: tak! W rzeczywistości używa ich tak często, że miewaliśmy aktualizacje, w których gra nie uruchamiała się na komputerach z 2 lub mniej rdzeniami.

Podejrzewam, że właściwe pytanie powinno brzmieć „czy gra efektywnie wykorzystuje wątki?”. Wtedy odpowiedź brzmi „to zależy”. Jak wspomniałem wcześniej, sprawne wykorzystywanie wielu rdzeni jest znacznie bardziej złożonym problemem niż wykorzystanie tylko liczby cykli zegara procesora. W naszym przypadku istnieją dwa główne wyzwania do pokonania przy rozdzielaniu pracy między wątkami: sekwencjonowanie i porządkowanie.

Problemy z sekwencjonowaniem występują, gdy 2 obliczenia działające jednocześnie muszą uzyskać dostęp do tych samych danych. Na przykład, powiedzmy, że obliczamy produkcję 2 populacji: Prikki-Ti i Blorgów. Oboje mają dostęp do aktualnych zapasów energii, dodają do nich swoją produkcję energii i zapisują wartość z powrotem. W zależności od sekwencji obaj mogli odczytać wartość początkową (powiedzmy 100), dodać swoją produkcję (powiedzmy 12 i 3, Blorgowie mieli zły dzień) i zaktualizować dane. Idealnie byłoby, gdybyśmy otrzymali 115 (100 + 12 + 3). Ale potencjalnie oba odczytałyby 100, a następnie przeliczyłyby i nadpisałyby się nawzajem, kończąc na 112 lub 103. Prostym sposobem obejścia tego jest wprowadzenie blokad: Prikki-Ti „zablokowałby” wartość energii, dopóki nie zakończy obliczeń i nie zapisze nowej wartości, po tym przyjdzie kolej na Blorgów i zaczną własne obliczenia. Chociaż rozwiązuje to problem, wprowadza drugi większy: działania są teraz znów sekwencyjne, a korzyść z wykonywania ich we współbieżnych wątkach została utracona. Co gorsza, ze względu na koszt obliczeń blokowania, odblokowywania i synchronizacji, całość prawdopodobnie zajmie więcej czasu niż gdybyśmy po prostu obliczyli oba w tym samym wątku.

Druga sprawa to porządkowanie, czyli „zależność od kolejności”. Co znaczy, że zmiana kolejności działań w niektórych operacjach zmienia końcowy wynik. Na przykład załóżmy, że nasz poprzedni Prikki-Ti i Blorg postanowili rozwiązać spór w przyjazny sposób. Wiemy, że system walki obsłuży obu walczących, ale ponieważ potencjalnie mają miejsce setki innych działań bojowych, nie wiemy, która z nich nastąpi jako pierwsza. I potencjalnie na 2 różnych komputerach kolejność będzie się różnić. Na przykład na serwerze (hoście) Prikki-Ti zaatakuje jako pierwsze, podczas gdy na kliencie Blorgowie zadziałają jako pierwsi.


#BlorgShotFirst

Na serwerze akcja Prikki-Ti jest rozpatrywana jako pierwsza, zabijając Blorgów. Działanie Blorgów, które pojawiło się później (prawdopodobnie w innym wątku), zostało odrzucone, ponieważ martwi Blorgowie nie mogą strzelać (to fakt naukowy). Klient jednak rozprowadził obliczenia w inny sposób (może ma więcej rdzeni niż serwer) i w jego świecie Blorgowie wysłali Prikki-Ti pierwsi do piachu. Po tym obaj gracze otrzymują przerażające okienko „Player is Out of Sync”, ponieważ ich realia się rozeszły.

Istnieją oczywiście sposoby rozwiązania tego problemu, ale zazwyczaj wymagają one przeprojektowania całej funkcjonalności w sposób rozwiązujący obie przeszkody. Na przykład w naszym pierwszym przypadku każdy wątek mógłby przechowywać dane wyjściowe każdej populacji, aby dodać je do każdego imperium, a następnie w końcu je scalić. W ten sam sposób nasz problem dwóch pojedynkowiczów mógłby zostać rozwiązany poprzez natychmiastowe zarejestrowanie obrażeń, ale zastosowanie ich efektów na innym etapie, aby wyeliminować potrzebę deterministycznej kolejności.

Jak można sobie wyobrazić, o wiele łatwiej jest zaprojektować od nowa coś z myślą o wielowątkowości, niż modyfikować już istniejący starszy system. Jeśli mi nie wierzycie, spójrzcie tylko, ile czasu spędzacie na modernizowaniu floty, poczekam.

Dobre wieści

To wszystko super i fajnie, ale co konkretnie z tego będzie w następnej aktualizacji? Cóż, ucieszy was wiadomość, że poświęciłem trochę czasu, aby zastosować to do jednego z najstarszych elementów naszego silnika: systemu ładowania plików i zasobów.

Przez długi czas do tego celu używaliśmy oprogramowania innej firmy. Oszczędziło nam to wielu kłopotów, ale okazało się również, że nie radzi sobie z wielowątkowością. Do tego stopnia, że ​​czasami ten system był wolniejszy na procesorach z większą liczbą rdzeni aniżeli mniejszą i to z powodu problemów z blokowaniem, o którym wspomniałem wcześniej.

W połączeniu z kilkoma innymi optymalizacjami, umożliwiło nam to drastyczne skrócenie czasu uruchamiania gry. Mógłbym napisać kolejne tysiąc słów, wyjaśniając jak, ale myślę, że ten film opisze wszystko lepiej:

Porównanie z filmiku zostało zrobione na moim prywatnym komputerze, który wykorzystuje poczciwy i7 2600K i dysk SSD. Oba uruchomienia były „gorące” (gra była niedawno uruchomiona), ale nawet na „zimnym” starcie jest ogromna różnica.

Aby osiągnąć jak najlepszą wydajność, będziecie musieli użyć nowej bety DirectX11. Tak, dobrze widzicie: następna aktualizacja będzie także oferować otwartą wersję beta DX11 pierwotnie zastosowaną przez naszych przyjaciół z Tantalus dla konsolowej edycji Stellaris, zastępując stary DX9. Choć wizualnie identyczne, użycie DX11 do renderowania grafiki umożliwia cały szereg wielowątkowych optymalizacji, które są trudne lub niemożliwe do osiągnięcia w DX9. Granie ze starym rendererem DX9 nadal da wam niezłe przyspieszenie przy uruchamianiu, ekran główny powinien wczytać się o wiele szybciej, ale raczej nie zobaczysz większej różnicy, tak jak ma to miejsce w przypadku DX11, gdy gra ładuje modele i tekstury.

Niektóre z tych optymalizacji zostały również zastosowane w najnowszej wersji Clausewitz i będą częścią CK3 w dniu premiery. Imperator również powinien na tym skorzystać. Możliwe, że uda się zastosować to również do EU4 i HoI4, ale jak dotąd moje eksperymenty z EU4 nie wykazały tak dużego przyspieszenia, jak to miało miejsce w przypadku Stellaris i CK3.

Jeśli chcecie przeczytać więcej szczegółów technicznych na temat optymalizacji zastosowanych w m.in. Stellaris, możecie zapoznać się z artykułem, który niedawno opublikowałem na moim blogu.

I tym wpisem was żegnam. To prawdopodobnie będzie mój ostatni dziennik twórców na temat Stellaris, ponieważ w przyszłym miesiącu przechodzę kierować zespołem programistów w HoI4. Możecie uznać te optymalizacje za mój pożegnalny prezent. Przy Stellaris nie spędziłem być może za dużo czasu, ale nie martwcie się: nawet jeżeli odejdę, Jeff ciągle będzie przy was!

1 Stellaris Dev Team