Zwiększenie wydajności: Globalna inwalidacja VALORANT

Opowiadamy o tym, jak zespół ds. wydajności VALORANT zwiększył osiągi waszego klienta.

Cześć! Z tej strony Aaron Cheney, programista z zespołu ds. wydajności VALORANT. Wydajność to kluczowy element zapewniający integralność rywalizacji w VALORANT, a nasz zespół odpowiada za monitorowanie, utrzymywanie i ulepszanie zarówno wydajności serwerów, jak i klienta.

Bardzo się cieszymy, że możemy udostępnić informacje o funkcji, nad którą pracowaliśmy kilka miesięcy: globalnej inwalidacji. Niedługo szczegółowo opiszemy tę funkcję, najpierw przyjrzyjmy się jednak temu, jak wpłynęła ona na wydajność klienta od pojawienia się patcha 4.03.

Uwaga, spoiler: wpłynęła całkiem nieźle.

AskVal_March22_Global_Invalidation_Graph_3.jpg

Globalna inwalidacja zapewniła spore usprawnienia dla dużej części naszych graczy. Właściwie jest to największy wzrost wydajności klientów od czasu premiery gry.

Chociaż te diagramy są ekscytujące – i jesteśmy zachwyceni wynikami – ważne, abyście wiedzieli, na co dokładnie patrzycie. Pracujemy ze sporymi, złożonymi zbiorami danych. Porządkując, filtrując i kontrolując te informacje, jesteśmy w stanie lepiej zrozumieć, w jaki sposób odbieracie rozgrywkę. Oto, o czym musicie pamiętać, żeby mieć pełen obraz sytuacji:

  • Wykresy zestawiające zmienne „patch” i „średnia wartość FPS”: im wyższa wartość, tym lepiej.
  • Każda linia przedstawia konfigurację sprzętową (kombinację CPU+GPU) często spotykaną wśród naszych graczy. Analizując dane dotyczące wydajności, uważamy tę kombinację za najważniejszy czynnik prognostyczny spodziewanej wydajności. Na tych diagramach sprzęty z tą samą kombinacją CPU+GPU zostały zestawione razem.
  • Próbki danych pobrano z dwóch kolejek: nierankingowej i rankingowej. Ponieważ są to najpopularniejsze tryby gry w VALORANT, nie szczędzimy wysiłków, aby zrozumieć i poprawić ich wydajność.
  • Wykluczyliśmy gry, w których nie uczestniczy dokładnie dziesięciu graczy. Dzięki temu skrajne wartości nie zniekształcają danych (w grach z mniejszą liczbą osób wydajność jest lepsza).

PODSUMOWANIE GLOBALNEJ INWALIDACJI

global-invalidation-summary-flow.jpg

Globalna inwalidacja zapewnia do 15% wzrostu klientom ograniczonym przez procesor (ogólnie rzecz biorąc, chodzi o sprzęt średniej i wyższej klasy). Aby takie wzrosty mogły zaistnieć, potrzebny był wysiłek licznych zespołów oraz wiele miesięcy pracy. Nasz proces identyfikacji zdatnych do optymalizacji obszarów gry opłacił się, a równoległe zarządzanie ryzykiem pomogło zapewnić graczom stabilną rozgrywkę.

Kto skorzystał; dostosowanie oczekiwań

Z naszych aktualnych parametrów wynika, że globalna inwalidacja zwiększyła o do 15% wydajność tych konfiguracji klienta, które są ograniczone przez procesor (ogólnie rzecz biorąc, chodzi o sprzęt ze średniej i wyższej półki).

Chociaż w sumie tendencja wzrostowa jest zauważalna, nie odzwierciedla ona jakości rozgrywki będącej w toku. Ponadto nie ma gwarancji, że rezultaty będą identyczne w przypadku każdego komputera z takim samym sprzętem.

Oznacza to, że podstawowa wydajność VALORANT na komputerach ograniczonych przez procesory zasadniczo wzrosła, jednak wydajność waszego sprzętu zależy od różnych, unikalnych w przypadku każdego z was czynników.

GLOBALNA INWALIDACJA OD PODSZEWKI

Zanim dokonamy właściwego przeglądu globalnej inwalidacji, wyjaśnijmy sobie kwestię elementów interfejsu w silniku Unreal Engine.

Widżety i drzewa

Elementy interfejsu (zwane również widżetami) tworzone są z mniejszych części składowych przy użyciu struktury drzewa. Jest ona analogiczna do systemu plików w komputerze. Widżet może mieć dowolną liczbę widżetów podrzędnych (tak jak folder może mieć dowolną liczbę plików).

Te części składowe można łączyć, aby stworzyć złożone widżety. Na przykład nasz licznik amunicji składa się z wielu części, a jego drzewo wygląda mniej więcej tak:

Screenshot_2022-03-07_at_16-30-57_PRF_-_Global_Invalidation_article.png

Po zestawieniu wszystkiego razem, elementy składowe licznika amunicji wyglądają tak:

AskVal_March22_Valorant_UI_Elements.jpg

(Czerwone obrysy powyżej zostały nadmiernie zarysowane dla przejrzystości. W praktyce wiele z nich nachodziłoby na siebie).

Zawsze, gdy co najmniej jeden widżet ulega zmianie w obrębie struktury drzewa, zaistniała modyfikacja może wpłynąć na wiele innych widżetów. Jeśli na przykład jakiś widżet zostanie przeniesiony w nowe miejsce na ekranie, wszystkie znajdujące się pod nim widżety też muszą ponownie obliczyć własną pozycję. Wspomnianymi zmianami zarządza system zwany „inwalidacją”, który omówimy w kolejnej części tego artykułu.

Inwalidacja

Inwalidacja to mechanizm używany przez silnik Unreal Engine do sygnalizowania, gdy określony widżet został zmieniony i wymaga aktualizacji.

Widżet wymaga inwalidacji z wielu powodów dotyczących animacji, koloru, nieprzezroczystości, rozmiaru, szeregowania, tekstu, obrazów i wielu innych właściwości, które mogą ulec zmianie w reakcji na coś, co dzieje się w grze. Gdy wspomniane zmiany dotkną widżetu, podlega on „inwalidacji”, co oznacza, że jest konieczna jego aktualizacja.

Ten proces ma jednak jeszcze bardziej skomplikowany przebieg, ponieważ widżet może podlegać wielu rodzajom inwalidacji. Należą do nich:

  • Układ – gdy zmianie uległ rozmiar widżetu (bardzo kosztowna zmiana).
  • Malowanie – gdy zmienił się wygląd widżetu, ale rozmiar pozostał ten sam.
  • Porządek widżetów podrzędnych – gdy zmienił się porządek widżetów wewnątrz drzewa (wiąże się to również z układem, a zatem jest to kosztowna zmiana).
  • Widoczność – gdy widoczność widżetu uległa zmianie: albo stał się niewidoczny, albo widoczny (również wiąże się z układem, a zatem jest to kosztowna zmiana).

Takie rodzaje inwalidacji stosowane są po to, by określić, jakie operacje należy wykonać, aby prawidłowo narysować widżet.

Sprawa staje się jeszcze bardziej złożona, gdy jeden widżet zależy od innego. Widżety uporządkowane są według hierarchii, a ich układ uzależniony jest od szeregu czynników. Inwalidacja jednego widżetu może wymagać inwalidacji szeregu innych z nim powiązanych w celu wykonania prawidłowego rysunku. Jeśli na przykład kilka widżetów jest ułożonych w pionie (np. panel społeczności z listą znajomych), a ogólny porządek widżetów ulegnie zmianie (np. znajomy pojawi się online), wszystkie ułożone w pionie widżety muszą zostać zaktualizowane.

Takiemu systemowi przyświeca kilka celów:

  • Inwalidacja możliwie jak najmniejszej liczby widżetów. W ten sposób ograniczona zostaje liczba widżetów wymagających aktualizacji w celu wykonania prawidłowego rysunku.
  • Inwalidacja widżetu tylko w razie konieczności. Nieprawidłowa inwalidacja widżetu prowadzi do zmarnowania cennych cykli CPU.
  • W razie braku inwalidacji widżetu – zbuforowanie wyniku umożliwiające szybkie narysowanie go w każdej klatce. Jeśli nic się nie zmieni, wówczas można oszczędzić cykle procesora.

Dzięki temu wprowadzeniu łatwiej będzie zrozumieć, na czym polega aktualizacja widżetów i co może powodować inwalidację. A teraz zobaczymy, jak deweloperzy stosują ten mechanizm w praktyce.

Komponenty Invalidation Box

Silnik Unreal Engine zawiera komponent – zwany Invalidation Box („pudełko inwalidacji”) – umożliwiający grupowanie wielu widżetów. Wszystkie widżety znajdujące się w pojedynczym Invalidation Box nie zostaną poddane wstępnemu przejściu, zaznaczeniu ani malowaniu. Zamiast tego wynik jest buforowany w buforze wierzchołków.

Za każdym razem, gdy objęty Invalidation Box widżet zostaje poddany inwalidacji, buforowane dane zostają odrzucone, a widżet zaktualizowany i ponownie pomalowany. Chociaż odświeżanie pamięci podręcznej może okazać się kosztowne w przypadku pojedynczej klatki, to w dłuższej perspektywie zamortyzowany wynik będzie znacznie lepszy.

Komponenty Invalidation Box są kluczowym elementem warunkującym wydajność interfejsu VALORANT, zwłaszcza w okresie poprzedzającym premierę. Nie są one jednak zupełnie bezkosztowe:

  • Deweloperzy muszą wiedzieć, które widżety nadają się do grupowania w obrębie Invalidation Box. Nie nadaje się do tego żaden widżet, który jest regularnie aktualizowany.
  • Umieszczanie widżetów w Invalidation Box wymaga od dewelopera ręcznego nakładu pracy. Nie jest to wykonalne w przypadku każdego widżetu w grze, dlatego deweloperzy muszą też wiedzieć, które z widżetów warto umieścić w Invalidation Box.

Więcej informacji na temat Invalidation Boxes znajdziecie w dokumentacji Epic (link do artykułu po angielsku).

Zarysowaliśmy już wystarczająco obszerny kontekst, aby omówić globalną inwalidację!

Nadejście globalnej inwalidacji

W tym momencie być może zadajecie sobie pytanie: „Dlaczego nie umieścić każdego elementu interfejsu użytkownika w globalnym komponencie Invalidation Box?”. Tak się składa, że właśnie o to chodzi (mniej więcej) w globalnej inwalidacji.

Globalna inwalidacja ma za zadanie znacząco zwiększyć wydajność interfejsu użytkownika w całej grze, jednocześnie ograniczając ręczne działania wymagane od deweloperów w celu umieszczenia widżetów w komponentach Invalidation Box. Tak wyglądałby przebieg procesu według idealnego scenariusza.

Jednak w przypadku UE4.25 (wersji silnika Unreal Engine używanej w VALORANT) globalna inwalidacja nie oferuje powszechnej obsługi wszystkim rodzajom widżetów. W późniejszych wersjach Unreal Engine wprowadzono ulepszenia, z których VALORANT nie mógł jednak od razu skorzystać. Ponadto nie wiedzieliśmy do końca, o ile szybciej będzie działać VALORANT dzięki globalnej inwalidacji.

I właśnie tutaj zaczęła się nasza praca.

DLACZEGO SIĘ NA NIĄ ZDECYDOWALIŚMY?

Pod koniec lipca 2021 r. nasz zespół postanowił sprawdzić globalną inwalidację w ramach testów wewnętrznych. Wprowadzono niewielkie zmiany w celu naprawienia kilku błędów, aby z powodzeniem przeprowadzić testy. Wiedzieliśmy jednak, że błędy pojawią się właśnie w trakcie tych testów... i oczywiście je znaleźliśmy.

Pod koniec testowania uzbieraliśmy ich około 20, przy czym mówimy o najbardziej ewidentnych problemach. Drobniejsze, podstępne błędy prawdopodobnie czekały, aż je wykryjemy. Nie wspominając o tych spoza spektrum naszych podstawowych zainteresowań, których nie przetestowaliśmy.

Tylko czy... globalna inwalidacja doprowadziła do wzrostów wydajności? Jak najbardziej.

Po analizie danych z pojedynczego testu ustalono, że interfejs użytkownika był szybszy o ~35%. (Uwaga: interfejs odpowiada tylko za część kosztu pojedynczej klatki).

Wciąż pozostawały jednak otwarte pytania:

  • Ile czasu zajmie naprawienie wszystkich błędów?
  • Które zespoły powinny odpowiadać za to zadanie?
  • Czy ma ono pierwszeństwo przed innymi zaplanowanymi pracami? Aby zdążyć na czas z regularnymi premierami zawartości, nasze harmonogramy często ustalamy z kilkumiesięcznym wyprzedzeniem i trudno jest nam w nich znaleźć miejsce na inne pojawiające się prace – nawet tak ekscytujące jak ta.
  • Czy naprawa błędów negatywnie wpłynie na wzrosty wydajności? Naprawienie wszystkich błędów wymagałoby wielu zmian w kodzie, a każda z nich mogłaby sprawić, że interfejs użytkownika stałby się kosztowniejszy.
  • Kiedy powinniśmy przeprowadzić takie prace? Wiedząc, że globalna inwalidacja jest przedmiotem dalszych prac w późniejszych wersjach silnika Unreal Engine, musieliśmy zastanowić się nad harmonogramem wprowadzania zmian w związku z aktywnością Epic.

Ostatecznie uznaliśmy, że z kilku powodów wysiłek ten jest wart zachodu.

Integracja z silnikiem Unreal Engine i harmonogram

Mimo że w Unreal Engine w wersjach 4.26 i 4.27 poczyniono znaczące postępy w zakresie globalnej inwalidacji, VALORANT prowadzi prace integracyjne zgodnie z opóźnionym harmonogramem. Nie pracujemy na „najświeższej” wersji, aby ograniczyć czynniki ryzyka i zapewnić graczom stabilność rozgrywki.

Nasz harmonogram przewidywał, że przez wiele miesięcy pozostaniemy przy silniku Unreal Engine 4.25. To oznaczało, że gracze nie skorzystaliby z tych wzrostów wydajności jeszcze przez ponad rok. Nie spodobało nam się to.

Więcej informacji na temat podejścia VALORANT do ulepszeń silnika Unreal Engine znajdziecie w tym wątku na Twitterze, którego autorem jest dyrektor ds. technologii w VALORANT, Marcus Reid.

Znane wzrosty wydajności

Globalna inwalidacja oferowała coś naprawdę wyjątkowego w kontekście wysiłków skupionych na wydajności: wartość mierzoną. Zmierzyliśmy potencjalne wzrosty w trakcie testów wewnętrznych. Przebieg drogi do osiągnięcia tych wartości nie budził (większych) wątpliwości.

Prace nad wydajnością są trudne. Decydują o niej bardzo nieznaczne wartości. Stopniowe zmiany pomagają zwiększyć ogólną wydajność wraz z upływem czasu i rzadko się zdarza, żeby pojedyncza modyfikacja spowodowała dwucyfrowe wzrosty. Nie mogliśmy nie skorzystać z tak dobrej optymalizacji.

Nawet po uwzględnieniu zmniejszonych wzrostów wynikających z poprawek błędów globalna inwalidacja dawała nam największe szanse na zapewnienie graczom znaczących wzrostów w rozsądnym czasie.

JAK UDAŁO SIĘ NAM PRZEPROWADZIĆ TE PRACE?

Chociaż wstępne eksperymenty z globalną inwalidacją rozpoczęły się pod koniec lipca 2021 r., konkretne wysiłki zmierzające do ustabilizowania tej funkcji zostały podjęte dopiero pod koniec września 2021 r.

Selektywna integracja zmian wprowadzonych przez Epic

Pełna integracja Unreal Engine w wersjach 4.26 i 4.27 nie wchodziła w rachubę. Ponieważ jednak wiedzieliśmy, że Epic aktywnie pracował nad globalną inwalidacją, postanowiliśmy przekopać się przez tysiące zmian i wybrać te, które mogły przybliżyć nas do stabilnej, w pełni funkcjonalnej globalnej inwalidacji.

Częściowa integracja zmian stanowiła nie lada wyzwanie. Musieliśmy zmodyfikować jak najmniej podstawowych funkcji silnika, aby utrzymać stabilność, wdrażając jednocześnie odpowiednie zmiany z Epic. Wszystkie te prace wykonywano nie w głównej, tylko w odrębnej gałęzi projektowej VALORANT, aby nie wpłynęły one na pozostałych deweloperów.

Po selektywnym włączeniu zmian Epic do naszego silnika przez kilka kolejnych tygodni naprawialiśmy tyle błędów, ile się dało, przygotowując się w tym samym czasie do wprowadzenia globalnej inwalidacji do głównej gałęzi VALORANT. W międzyczasie zaprojektowaliśmy przełącznik umożliwiający nam szybkie włączanie i wyłączanie tej funkcji (na wypadek jakiejś katastrofy).

Po dokonaniu wielu poprawek błędów oraz integracji licznych zmian wprowadzonych przez Epic w wersjach 4.26 i 4.27 z powrotem połączyliśmy odizolowaną gałąź z główną.

Odkrywanie podobieństw między błędami

Chociaż wiele błędów związanych z globalną inwalidacją przejawiało się na różne sposoby, ich podstawowa przyczyna często tkwiła w jednym problemie. Naprawienie tego rodzaju błędów było najbardziej opłacalne, ponieważ dzięki temu za pomocą jednej zmiany można było usunąć wiele problemów. Przykładowo jedna modyfikacja usunęła ponad 10 błędów rozsianych po całej grze. Dogłębna analiza podstawowego problemu pozwoliła opracować solidne rozwiązania, które zwiększyły niezawodność i stabilność globalnej inwalidacji.

Wykrycie błędów, naprawa błędów, testy – i tak w kółko

W kolejnych tygodniach i miesiącach trwał regularny cykl uruchamiania globalnej inwalidacji przed testami, wykrywania szeregu błędów, wyłączania globalnej inwalidacji po testach oraz rozwiązywania zidentyfikowanych problemów.

developer-bug-fix-flow.jpg


Z każdą kolejną pętlą raportowano coraz mniej błędów. Kontynuowaliśmy ten proces, dopóki nieprzerwany strumień błędów nie zamienił się w ich powolne skapywanie, aż w końcu ustał.

Do końca listopada 2021 r. wszystkie główne problemy zostały rozwiązane, a globalna inwalidacja była w dużej mierze stabilna.

Istotne błędy
  • Astra powoduje awarię gry – w pewnym momencie każdy, kto nią grał, napotykał na awarię zaraz po wczytaniu gry. Ten błąd został naprawiony po integracji zmian wprowadzonych przez Epic.
  • Awaria wielokrotnego dziedziczenia – wielokrotne dziedziczenie w C++ to trudny temat. Nie zagłębiając się zbytnio w szczegóły, kolejność destruktorów w konkretnej klasie nie była wykonywana w odpowiednim porządku, co skutkowało awarią. Prosta zamiana dwóch linijek kodu w celu modyfikacji kolejności dziedziczenia pozwoliła pozbyć się problemu. Aby dowiedzieć się więcej o wielokrotnym dziedziczeniu, zajrzyjcie na stronę (link do artykułu po angielsku).
  • Nieskończony dźwięk czatu – podczas korzystania z menu pasek czatu odtwarza dźwięk po najechaniu na niego myszą. Ku irytacji wszystkich błąd powodował, że dźwięk był odtwarzany wiele razy na sekundę. Aby usunąć ten problem, musieliśmy zrozumieć okoliczności, w jakich widżety odbierają zdarzenia myszy wiele razy w obrębie klatki.

    Metodologie testów

    Jednym z elementów globalnej inwalidacji, który skłonił nas do zachowania szczególnej ostrożności (a tym samym do gruntownych testów), jest fakt, że wpływa ona na każdy aspekt gry. Dosłownie.

    Na listę waszych znajomych? Jak najbardziej. Na przycisk, który naciskacie, aby dołączyć do kolejki? Też. Na menu ustawień? No pewnie. Na procent moich strzałów w głowę? No cóż...

    Chodzi o to, że elementy interfejsu użytkownika występują w całej grze i często są źródłem istotnych informacji dla graczy. Niesprawność chociażby jednego z tych elementów interfejsu była niedopuszczalna.

    Aby temu zapobiec, nasz dział kontroli jakości stworzył plan testowania obejmujący wiele strategii, aby mieć pewność, że globalna inwalidacja działa zgodnie z założeniami.

    Testowanie pionowych wycinków

    „Pionowy wycinek” w VALORANT oznacza główną ścieżkę zwykle obieraną przez graczy: od uruchomienia klienta, przez dołączanie do kolejki, aż po rozgrywkę całego meczu i interakcję z ekranem końca gry. Skupiając się na istotnych elementach gry, dział kontroli jakości był w stanie szybko przetestować najczęściej używane elementy i wcześnie wykryć problemy.

    Testowanie destruktywne

    Tam, gdzie kończy się testowanie pionowego wycinka, zaczyna się testowanie destruktywne. Jego celem jest wykrycie odbiegających od normy problemów, często poprzez modyfikację czynników zewnętrznych (takich jak ping sieci, liczba klatek na sekundę, użycie skrótu Alt+Tab itp.). Wyposażony w szereg wewnętrznych narzędzi dział kontroli jakości poświęcił kilka tygodni na ten model testowania.

    Testowanie przypadków brzegowych

    Istnieje wiele aspektów VALORANT, z którymi ma do czynienia tylko niewielki procent graczy. Z niektórymi – np. przeznaczonymi dla nowych graczy – mają oni styczność tak naprawdę tylko raz. To, że korzysta się z nich rzadziej, nie znaczy, że są mniej ważne. Wykrycie i testowanie wszystkich przypadków brzegowych pomogło nam wyłapać ukryte błędy.

    Testowanie na PBE (publicznym serwerze testowym)

    PBE był kluczowym etapem wdrażania globalnej inwalidacji.

    • Po raz pierwszy gracze z zewnątrz mogli przetestować tę funkcję. Oznaczało to, że globalna inwalidacja może zostać przetestowana w warunkach „rzeczywistych”.
    • Na publicznym serwerze testowym pojawiają się rozmaite specyfikacje sprzętowe. PBE celowo obejmuje zakres od słabszych do mocniejszych specyfikacji. Dzięki testom szerokiej gamy urządzeń należących do graczy zyskaliśmy pewność, że globalna inwalidacja będzie działała dobrze wśród szerszego grona osób.

    Po testach PBE w weekend od 22 do 23 stycznia 2022 r. byliśmy już przekonani, że globalna inwalidacja nie zakłóca integralności gry, a wzrosty wydajności są zgodne z naszymi przewidywaniami.

    Premiera globalnej inwalidacji

    Po premierze globalnej inwalidacji w patchu 4.03 uważnie monitorowaliśmy zgłoszenia graczy dotyczące błędów. Bacznie przyglądaliśmy się również danym dotyczącym wydajności, aby potwierdzić, że nasze szacunki pokrywają się z uzyskanymi wynikami. Ostatecznie globalna inwalidacja okazała się ogromnym sukcesem, z którego mogli skorzystać gracze. Mamy też nadzieję, że podoba wam większa wydajność mierzona w klatkach na sekundę.

    Globalna inwalidacja została już udostępniona, więc zespół do spraw wydajności wraca do pracy, żeby zapewnić wam jeszcze więcej wzrostów. Do następnego. Przyjemnego polowania!