Dlaczego zbyt źle jest optymalizować zbyt wcześnie?

168

Po przyjrzeniu się trochę optymalizacji, odkryłem (dosłownie wszędzie), że zbyt wcześnie optymalizacja gry wydaje się być powszechnie uznanym grzechem.

Naprawdę tego nie rozumiem, czy nie byłoby niewiarygodnie trudno zmienić niektóre podstawowe struktury gry na końcu, zamiast rozwijać je po raz pierwszy z myślą o wydajności?

Dostaję to, że czekanie, aż gra się zakończy, powie ci, czy w ogóle potrzebujesz optymalizacji, ale czy nie powinieneś tego zrobić, w końcu może poszerzyć różnorodność urządzeń, na których gra może działać, co zwiększy liczbę potencjalnych gracze.

Czy ktoś mógłby mi wyjaśnić, dlaczego zbyt zły pomysł na optymalizację zbyt wcześnie?

mr-matt
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Josh
3
Odpowiedź na to pytanie nie może być łatwiejsza: „marnuje czas”. Równie dobrze możesz zapytać „po co próbować oszczędzać pieniądze podczas zakupów?”
Fattie
27
I odwrotnie, zauważ, że wielu inżynierów po prostu twierdzi, że zdanie „nie optymalizuj zbyt wcześnie” jest po prostu błędne . Zdanie powinno brzmieć po prostu: „nie optymalizuj, dopóki nie wiesz, co zoptymalizować”. Tak więc, po prostu, jeśli istnieją trzy systemy A, B i C, optymalizacja A jest całkowicie bezcelowa, jeśli, jak się okazuje, A nie jest w żaden sposób wąskim gardłem, a C w rzeczywistości jest wąskim gardłem. W tym przykładzie oczywiście optymalizacja A byłaby całkowitą stratą czasu. Więc - po prostu - nie optymalizuj, dopóki nie dowiesz się, który z A, BC zoptymalizować: to wszystko oznacza, to takie proste.
Fattie
2
Optymalizacja klasy czasowej aplikacji podczas programowania jest zupełnie inna niż próba wycięcia kilku instrukcji z pętli, co zwykle jest stałym czasem. Skoncentruj się na O (n) vs O (n log n) zamiast „pisanie pętli for zapisuje jedną instrukcję CPU”.
1
@phyrfox Właściwie skrócenie czasu ładowania wydaje mi się bardziej imponujące. Widoczny jest trzy sekundowy czas ładowania. Nie jestem pewien, czy zauważalne jest 55 do 60 klatek na sekundę.
immibis

Odpowiedzi:

237

Preambuła:

W komentarzach zgłoszono kilka zastrzeżeń i myślę, że w dużej mierze wynikają one z niezrozumienia tego, co mamy na myśli, gdy mówimy „przedwczesna optymalizacja” - dlatego chciałem dodać trochę wyjaśnienia na ten temat.

„Nie optymalizuj przedwcześnie” nie oznacza „pisz kod, o którym wiesz, że jest zły, ponieważ Knuth twierdzi, że nie możesz go wyczyścić do końca”

Oznacza to, że „nie poświęcaj czasu i czytelności na optymalizację, dopóki nie dowiesz się, które części twojego programu rzeczywiście potrzebują pomocy, by być szybszym”. Ponieważ typowy program spędza większość czasu w kilku wąskich gardłach, inwestowanie w optymalizację „wszystkiego” może nie dać takiego samego przyspieszenia, jak skupienie tej samej inwestycji tylko na wąskim kodzie.

Oznacza to, że w razie wątpliwości powinniśmy:

  • Preferuj kod, który jest prosty do napisania, zrozumiały i łatwy do modyfikacji na początek

  • Sprawdź, czy potrzebna jest dalsza optymalizacja (zwykle poprzez profilowanie działającego programu, chociaż jeden komentarz poniżej zauważa analizę matematyczną - jedyne ryzyko, które musisz sprawdzić, czy matematyka jest poprawna)

Przedwczesna optymalizacja to nie :

  • Architektoniczne decyzje dotyczące struktury kodu w sposób dostosowujący się do twoich potrzeb - rozważne wybranie odpowiednich modułów / obowiązków / interfejsów / systemów komunikacji.

  • Proste wydajności, które nie wymagają dodatkowego czasu lub utrudniają odczytanie kodu. Używanie silnego pisania może być zarówno wydajne, jak i wyraźne. Kolejnym przykładem jest buforowanie referencji zamiast jej wielokrotnego wyszukiwania (o ile twoja sprawa nie wymaga skomplikowanej logiki unieważniania pamięci podręcznej - być może wstrzymasz się z pisaniem tego, dopóki nie profilujesz najpierw w prosty sposób).

  • Korzystanie z właściwego algorytmu dla zadania. * Jest bardziej optymalny i bardziej złożony niż wyczerpujące przeszukiwanie wykresu ścieżki. To także standard branżowy. Powtarzanie tematu, stosowanie sprawdzonych metod takich jak ten może faktycznie ułatwić zrozumienie kodu, niż jeśli robisz coś prostego, ale sprzecznego ze znanymi najlepszymi praktykami. Jeśli masz doświadczenie w napotykaniu wąskich gardeł we wdrażaniu funkcji gry X w jeden sposób w poprzednim projekcie, nie musisz ponownie uderzać w to samo wąskie gardło w tym projekcie, aby wiedzieć, że to prawda - możesz i powinieneś ponownie użyć rozwiązań, które działały w przeszłości Gry.

Wszystkie te typy optymalizacji są dobrze uzasadnione i na ogół nie byłyby oznaczone jako „przedwczesne” (chyba że wybierasz się do króliczej nory i wdrażasz najnowocześniejsze ścieżki wyszukiwania dla twojej mapy szachownicy 8x8 ...)

Teraz wyjaśniono, dlaczego ta polityka może być przydatna w grach:


Szczególnie w gamedev szybkość iteracji jest najcenniejszą rzeczą. Często wdrażamy i wdrażamy o wiele więcej pomysłów, niż ostatecznie dostarczymy z gotową grą, starając się „znaleźć zabawę”.

Jeśli potrafisz prototypować mechanika w prosty i może nieco naiwny sposób i testujesz go następnego dnia, jesteś w znacznie lepszej sytuacji, niż gdybyś spędził tydzień, tworząc najpierw najbardziej optymalną wersję. Zwłaszcza, jeśli okaże się, że jest do bani, a wyrzucicie tę funkcję. Wykonanie tego w prosty sposób, abyś mógł wcześnie przetestować, może zaoszczędzić mnóstwo marnowanej pracy, optymalizując niepotrzebny kod.

Niezoptymalizowany kod jest również ogólnie łatwiejszy do zmodyfikowania i wypróbowania różnych wariantów niż kod, który jest precyzyjnie dostrojony do robienia jednej precyzyjnej rzeczy optymalnie, która jest krucha i trudniejsza do modyfikacji bez jej łamania, wprowadzania błędów lub spowalniania. Dlatego utrzymanie prostoty i łatwości zmiany kodu jest często warte niewielkiej nieefektywności w czasie wykonywania przez większość prac programistycznych (zwykle pracujemy na komputerach powyżej specyfikacji docelowej, abyśmy mogli wchłonąć koszty ogólne i skupić się na uzyskaniu docelowego doświadczenia), dopóki nie zablokowaliśmy to, czego potrzebujemy od tej funkcji, i możemy zoptymalizować części, o których wiemy, że są wolne.

Tak, refaktoryzacja części projektu na późnym etapie rozwoju w celu optymalizacji wolnych miejsc może być trudna. Ale tak samo jest w trakcie opracowywania aplikacji, ponieważ optymalizacje, które wprowadziłeś w zeszłym miesiącu, nie są zgodne z kierunkiem rozwoju gry od tego czasu lub naprawiały coś, co okazało się nie być prawdziwym wąskim gardłem, gdy uzyskasz więcej funkcji i treści w.

Gry są dziwne i eksperymentalne - trudno przewidzieć, jak ewoluuje projekt gry i jego potrzeby techniczne oraz gdzie wydajność będzie najściślejsza. W praktyce często martwimy się niewłaściwymi rzeczami - poszukaj tutaj pytań o wydajność, a zobaczysz wspólny motyw twórców rozpraszanych przez rzeczy na papierze, które prawdopodobnie nie stanowią żadnego problemu.

Weźmy na przykład dramatyczny przykład: jeśli twoja gra jest związana z GPU (nierzadko), cały ten czas poświęcony hiperoptymalizacji i wątkowi pracy procesora może nie przynieść żadnych wymiernych korzyści. Wszystkie te godziny deweloperskie można by zamiast tego poświęcić na wdrożenie i dopracowanie funkcji gry, aby uzyskać lepsze wrażenia z gry.

Ogólnie rzecz biorąc, większość czasu spędzonego na pracy nad grą nie zostanie przeznaczona na kod, który ostatecznie będzie wąskim gardłem. Szczególnie, gdy pracujesz nad istniejącym silnikiem, bardzo drogie pętle wewnętrzne w systemach renderowania i fizyki są w dużej mierze poza twoimi rękami. W tym momencie Twoim zadaniem w skryptach rozgrywki jest po prostu trzymanie się z dala od silnika - dopóki nie wrzucisz w to klucza, prawdopodobnie wyjdziesz całkiem dobrze na pierwszą kompilację.

Więc oprócz odrobiny higieny i budżetowania (np. Nie wyszukuj wielokrotnie / konstruuj rzeczy, jeśli możesz je łatwo ponownie użyć, zachowaj skrupulatne zapytania / fizyki lub odczyty GPU skromne itp.), Nawyk nie przesadzania - optymalizacja, zanim dowiemy się, gdzie są prawdziwe problemy, okazuje się być korzystna dla produktywności - oszczędzając nam marnowania czasu na optymalizację niewłaściwych rzeczy, a także utrzymując nasz kod prostszy i łatwiejszy do ulepszenia.

DMGregory
źródło
38
Trwa tutaj dalsza dyskusja na temat StackOverflow , podkreślająca znaczenie czytelności i przejrzystości kodu oraz niebezpieczeństwo utrudniania zrozumienia kodu (i łatwiejszego wprowadzania błędów) poprzez przedwczesną optymalizację.
DMGregory
8
Świetna odpowiedź, chciałbym dodać, w odpowiedzi na „ czy nie byłoby niewiarygodnie trudne zmienić niektóre podstawowe struktury gry na końcu, zamiast opracowywać je po raz pierwszy z myślą o wydajności? ” Bezpośrednio, że oczywiście starasz się przede wszystkim projektować pod kątem wydajności. Gdy pojawi się dwie możliwości, wybierz bardziej wydajną. W przeciwnym razie piszesz grę tak modularnie, jak to możliwe, z dobrze zdefiniowanymi interfejsami. Następnie możesz zmienić mechanizmy w każdym module bez zmiany struktury bazy kodu.
Baldrickk
1
@Baldrickk Powiedziałbym, że warto udostępnić jako dodatkową odpowiedź. (Głosuję za tym!) Moja odpowiedź pomija znaczenie architektury i planowania, aby w pierwszej kolejności uniknąć tworzenia dużych problemów. Jeśli chodzi o odniesienie Gizmo do „rozwoju programu w ogóle”, stąd bierze się koncepcja przedwczesnej optymalizacji. Jak wspomniano powyżej, zła optymalizacja może stać się długiem technicznym równie łatwo, jak brakująca optymalizacja. Ale powinniśmy podkreślić, że nigdy nie robimy rzeczy w sposób celowo powolny, po prostu preferując prostotę i czytelność, dopóki nie będziemy mogli profilować i znaleźć prawdziwych problemów
DMGregory
1
Chociaż jestem prawdopodobnie niedoświadczony tutaj spośród was, muszę powiedzieć, że uważam ten „przedwczesny optymizm za źródło wszelkiego zła” trochę dziwny. Mniej więcej dwa lata temu próbowałem coś zrobić w Unity3D. Napisałem kilka zapytań LINQ, z których każde używa poprzedniego. Spojrzałem na mój kod i zacząłem mieć poważne wątpliwości. Myślałem, że złożoność obliczeń była okropna. Wziąłem kartkę papieru i obliczyłem, że przetworzenie tych zapytań na oczekiwany rozmiar danych zajmie godziny. Więc dodałem trochę buforowania tu i tam. A zapytania działały poprawnie.
gaazkam
3
Dlatego dołożyłeś należytej staranności i zweryfikowałeś, że problem był prawdziwy przed zmianą kodu z początkowo przejrzystego i najbardziej intuicyjnego dla Ciebie. Oznacza to, że Twoja optymalizacja nie była przedwczesna. ;) „Unikaj przedwczesnej nadmiernej optymalizacji” nie oznacza „pisania niepotrzebnie drogiego kodu” - po prostu faworyzowanie prostoty, czytelności i łatwości modyfikacji, dopóki nie zostanie określony określony problem z wydajnością. Komentarze nie są przeznaczone do dyskusji, więc możemy przejść do czatu, jeśli chcesz o tym porozmawiać.
DMGregory
42

uwaga: ta odpowiedź zaczęła się jako komentarz do odpowiedzi DMGregory, więc nie powiela ona bardzo dobrych punktów, które wysuwa.

„Czy nie byłoby niewiarygodnie trudne zmienić niektóre podstawowe struktury gry na końcu, zamiast rozwijać je po raz pierwszy z myślą o wydajności?”

To jest dla mnie sedno pytania.

Tworząc oryginalny projekt, powinieneś starać się projektować pod kątem wydajności - na najwyższym poziomie. Jest to mniej optymalizacji, a więcej o strukturze.

Przykład:
Musisz stworzyć system przekraczania rzeki. Oczywistymi projektami są most lub prom, więc który wybierasz?
Odpowiedź zależy oczywiście od wielkości przejazdu i natężenia ruchu. To nie jest optymalizacja, zamiast tego zaczyna się od projektu odpowiedniego dla twojego problemu.

Kiedy pojawi się wybór projektu, wybierasz ten, który najlepiej pasuje do tego, co chcesz zrobić.

Załóżmy więc, że nasz ruch jest dość niski, dlatego postanowiliśmy zbudować dwa terminale i kupić prom, aby obsłużyć ruch. Ładna prosta implementacja
Niestety, kiedy już ją uruchomimy, stwierdzimy, że ruch jest większy niż oczekiwano. Musimy zoptymalizować prom! (ponieważ to działa, a budowanie mostu nie jest teraz dobrym planem)

Opcje:

  • Kup drugi prom (przetwarzanie równoległe)
  • Dodaj kolejny pokład samochodowy na prom (kompresja ruchu)
  • Zaktualizuj silnik promu, aby był szybszy (przerobione algorytmy przetwarzania)

W tym miejscu powinieneś spróbować uczynić swój oryginalny projekt tak modularnym, jak to możliwe.
Wszystkie powyższe są możliwymi optymalizacjami, a nawet możesz zrobić wszystkie trzy.
Ale jak wprowadzić te zmiany bez dużych zmian strukturalnych?

Jeśli masz modułową konstrukcję z jasno określonymi interfejsami, wdrożenie tych zmian powinno być proste.
Jeśli kod nie jest ściśle powiązany, zmiany w modułach nie wpływają na otaczającą strukturę.

Rzućmy okiem na dodanie dodatkowego promu.
„Zły” program mógłby zostać zbudowany wokół idei pojedynczego promu i miałby stany dokowania oraz stan promu i pozycję wszystkie połączone razem i stan współdzielenia. Będzie to trudne do zmodyfikowania, aby umożliwić dodanie dodatkowego promu do systemu.
Lepszym rozwiązaniem byłoby posiadanie doków i promów jako osobnych jednostek. Nie ma między nimi żadnego ścisłego połączenia, ale mają interfejs, do którego prom może przybyć, rozładować pasażerów, przyjąć nowych i wyjść. Dok i prom współużytkują tylko ten interfejs, co ułatwia wprowadzanie zmian w systemie, w tym przypadku poprzez dodanie drugiego promu. Dok nie dba o to, jakie promy są w rzeczywistości, chodzi tylko o to, że coś (cokolwiek) korzysta z jego interfejsu.

tl; dr:

  • Przede wszystkim spróbuj zaprojektować wydajność.
  • Gdy pojawi się dwie możliwości, wybierz bardziej wydajną.
  • Napisz swój moduł tak modularnie, jak to możliwe z dobrze zdefiniowanymi interfejsami.

Następnie możesz zmienić mechanizmy w każdym module bez konieczności restrukturyzacji całej bazy kodu, gdy trzeba zoptymalizować.

Baldrickk
źródło
16
Na początku możesz również wdrożyć prom jako IRiverCrossing, a gdy stanie się jasne, że natężenie ruchu jest zbyt duże, aby ustawić prom, zaimplementuj most IRiverCrossingi zrzuć go. Cała rzecz polega oczywiście na zmniejszeniu zewnętrznego interfejsu API promu i most do wspólnej funkcjonalności, aby mogły być reprezentowane przez ten sam interfejs.
Mr.Mindor
2
Możesz nawet napisać zautomatyzowane testy dla tych interfejsów modułowych, abyś mógł szybko refaktoryzować bez obawy o zerwanie czegokolwiek poza modułem, którego dotykasz.
user3067860
2
Bardzo fajny przykład tego, dlaczego OOP jest tak ważny w dzisiejszych czasach.
Jaime Gallego
1
Trafiłeś we właściwy powód. Mantra Donalda Knutha jest prawdziwa tylko wtedy, gdy prędkość nie jest jednym z podstawowych wymagań, a skupienie się na optymalizacji zagroziłoby głównej konstrukcji. Niestety, w grach szybkość często stanowi rdzeń i dlatego musi być uwzględniona w projekcie na wczesnym etapie. Obawa PO (i twoja odpowiedź) jest całkowicie uzasadniona.
Peter A. Schneider
3
@AlexandreVaillancourt zamiast odpowiedzieć na tytuł pytania, próbowałem odpowiedzieć na to, co uważam za kluczową część pytania, na które faktycznie udzielono odpowiedzi; to znaczy „dlaczego nie powinienem optymalizować wcześniej, jeśli optymalizacja będzie później dużo więcej pracy, ponieważ mój projekt jest naprawiony” (sparafrazowany) . Ta odpowiedź również zaczęła się jako komentarz do odpowiedzi DMGregory i jest dodatkiem do niej, a powielanie jego odpowiedzi nie ma większego sensu.
Baldrickk
24

„Nie optymalizuj wcześnie” nie oznacza „wybierz najgorszy możliwy sposób”. Nadal musisz wziąć pod uwagę wpływ na wydajność (chyba że tylko prototypujesz). Nie chodzi o to, aby okaleczyć inne, ważniejsze rzeczy na tym etapie rozwoju - takie jak elastyczność, niezawodność itp. Wybierz proste, bezpieczne optymalizacje - wybierz rzeczy, które ograniczasz, i rzeczy, które pozostawiasz za darmo; śledzić koszty. Czy powinieneś używać silnego pisania? Większość gier działała i działa dobrze; ile kosztowałoby to usunięcie, gdybyś znalazł ciekawe zastosowania elastyczności w rozgrywce?

Znacznie trudniej jest zmodyfikować zoptymalizowany kod, zwłaszcza kod „inteligentny”. Zawsze jest to wybór, który czyni niektóre rzeczy lepszymi, a inne gorszymi (na przykład możesz handlować czasem procesora na zużycie pamięci). Dokonując takiego wyboru, musisz zdawać sobie sprawę ze wszystkich implikacji - mogą być katastrofalne, ale mogą być również pomocne.

Na przykład Commander Keen, Wolfenstein i Doom zostały zbudowane na zoptymalizowanym silniku renderującym. Każdy z nich miał swoją „sztuczkę”, dzięki której gra istniała w pierwszej kolejności (z czasem opracowano także dalsze optymalizacje, ale nie jest to tutaj ważne). To dobrze . Dobrze jest zoptymalizować sam rdzeń gry, myśl, która umożliwia grę; szczególnie jeśli eksplorujesz nowe terytorium, gdzie ta szczególna zoptymalizowana funkcja pozwala rozważyć projekty gier, które nie były zbytnio badane. Ograniczenia, które wprowadza optymalizacja, mogą również dać ci interesującą rozgrywkę (np. Limity liczby jednostek w grach RTS mogły zacząć jako sposób na poprawę wydajności, ale mają również wpływ na rozgrywkę).

Pamiętaj jednak, że w każdym z tych przykładów gra nie mogłaby istnieć bez optymalizacji. Nie wystartowali z „w pełni zoptymalizowanym” silnikiem - zaczęli od absolutnej konieczności i ruszyli w górę. Opracowywali nowe technologie i używali ich do tworzenia zabawnych gier. A sztuczki silnika ograniczono do jak najmniejszej części bazy kodu - cięższe optymalizacje wprowadzono tylko wtedy, gdy rozgrywka była w większości ukończona lub tam, gdzie pozwoliła na pojawienie się ciekawej nowej funkcji.

Teraz rozważ grę, którą możesz chcieć zrobić. Czy naprawdę jest jakiś technologiczny cud, który tworzy lub psuje tę grę? Może planujesz grę w otwartym świecie w nieskończonym świecie. Czy to naprawdę centralny element gry? Czy gra po prostu nie działałaby bez niego? Może myślisz o grze, w której teren jest odkształcalny bez ograniczeń, z realistyczną geologią i tym podobne; czy możesz sprawić, by działał z mniejszym zakresem? Czy działałoby to w 2D zamiast 3D? Jak najszybciej uzyskaj coś dobrej zabawy - nawet jeśli optymalizacja może wymagać przerobienia dużej części istniejącego kodu, być może warto; a może nawet zdasz sobie sprawę, że powiększanie rzeczy tak naprawdę nie poprawia gry.

Jako przykład ostatniej gry z wieloma optymalizacjami wskazałbym Factorio. Jedną z kluczowych części gry są pasy - jest ich wiele tysięcy i zawierają wiele pojedynczych kawałków materiałów w całej fabryce. Czy gra zaczęła się od mocno zoptymalizowanego silnika pasowego? Nie! W rzeczywistości oryginalna konstrukcja paska była prawie niemożliwa do zoptymalizowania - w pewnym sensie wykonała fizyczną symulację przedmiotów na pasku, co stworzyło kilka interesujących rzeczy, które możesz zrobić (w ten sposób uzyskujesz „wschodzącą” rozgrywkę - rozgrywka, która zaskakuje projektant), ale oznaczało to, że musiałeś zasymulować każdy element na pasku. Dzięki tysiącom pasków otrzymujesz dziesiątki tysięcy symulowanych fizycznie przedmiotów - nawet samo ich usunięcie i pozwolenie pasom na wykonanie pracy pozwala skrócić związany z tym czas procesora o 95-99%, nawet bez uwzględnienia takich rzeczy jak lokalizacja pamięci. Ale warto to zrobić tylko wtedy, gdy faktycznie osiągniesz te limity.

Prawie wszystko, co miało coś wspólnego z pasami, musiało zostać przerobione, aby umożliwić optymalizację pasów. Pasy musiały zostać zoptymalizowane, ponieważ potrzebowaliśmy dużo pasów do dużej fabryki, a duże fabryki to jedna atrakcja gry. W końcu jeśli nie możesz mieć dużych fabryk, dlaczego masz nieskończony świat? Zabawne, że powinieneś zapytać - wczesne wersje tego nie zrobiły :) Gra została przerobiona i wielokrotnie przekształcana, aby dotrzeć tam, gdzie są teraz - w tym 100% remake, gdy zdali sobie sprawę, że Java nie jest dobrym rozwiązaniem taka gra i przeszła na C ++. I zadziałało świetnie dla Factorio (chociaż nadal dobrze było, że nie było zoptymalizowane od samego początku - zwłaszcza, że ​​był to projekt hobbystyczny, który w przeciwnym razie mógłby się nie udać z powodu braku zainteresowania).

Ale chodzi o to, że wiele rzeczy, które możesz zrobić z fabryką o ograniczonym zasięgu - i wiele gier właśnie to pokazało. Granice mogą być jeszcze bardziej inspirujące dla zabawy niż wolności; czy Spacechem byłby bardziej zabawny, gdyby „mapy” były nieskończone? Gdybyście zaczęli od mocno zoptymalizowanych „pasów”, bylibyście zmuszeni pójść tą drogą; i nie mogłeś odkryć innych kierunków projektowania (np. zobaczyć, co ciekawego możesz zrobić z symulowanymi fizycznie taśmami przenośnikowymi). Ograniczasz swoją potencjalną przestrzeń projektową. Może się tak nie wydawać, ponieważ nie widzisz wielu niedokończonych gier, ale trudność polega na zapewnieniu dobrej zabawy - na każdą zabawną grę, którą widzisz, prawdopodobnie są setki, które po prostu nie mogły się tam dostać i zostały złomowane (lub gorzej, wydany jako okropne bałagany). Jeśli optymalizacja ci to pomoże - śmiało. Jeśli nie ... to prawdopodobnie przedwczesne. Jeśli uważasz, że jakaś mechanika gry działa świetnie, ale potrzebuje optymalizacji, aby naprawdę zabłysnąć - śmiało. Jeśli nie masz ciekawej mechaniki,nie optymalizuj ich . Najpierw znajdź zabawę - przekonasz się, że większość optymalizacji nie pomaga w tym i często jest szkodliwa.

Wreszcie masz świetną, zabawną grę. Czy warto teraz optymalizować ? Ha! To wciąż nie jest tak jasne, jak mogłoby się wydawać. Czy jest coś zabawnego?możesz zamiast tego zrobić? Nie zapomnij, że Twój czas jest nadal ograniczony. Wszystko wymaga wysiłku, a Ty chcesz skoncentrować się na tym, co najważniejsze. Tak, nawet jeśli tworzysz „darmową grę” lub grę „open source”. Zobacz, jak gra się w tę grę; zauważ, gdzie wydajność staje się wąskim gardłem. Czy optymalizacja tych miejsc sprawia więcej radości (jak budowanie coraz większych, coraz bardziej splątanych fabryk)? Czy pozwala to przyciągnąć więcej graczy (np. Słabszymi komputerami lub na różnych platformach)? Zawsze musisz ustalić priorytety - poszukaj stosunku wysiłku do wydajności. Prawdopodobnie znajdziesz wiele nisko wiszących owoców, po prostu grając w grę i obserwując, jak inni w nią grają. Ale zwróć uwagę na ważną część - aby się tam dostać, potrzebujesz gry . Skoncentruj się na tym.

Podsumowując, weź pod uwagę, że optymalizacja nigdy się nie kończy. To nie jest zadanie z małym znacznikiem wyboru, który kończysz i przechodzisz do innych zadań. Zawsze można zrobić „jeszcze jedną optymalizację”, a dużą część każdego projektu stanowi zrozumienie priorytetów. Nie robisz optymalizacji ze względu na optymalizację - robisz to, aby osiągnąć określony cel (np. „200 jednostek na ekranie jednocześnie na Pentium 333 MHz” to świetny cel). Nie trać tropu celu końcowego tylko dlatego, że zbytnio koncentrujesz się na celach pośrednich, które mogą nawet nie być warunkiem wstępnym dla celu końcowego.

Luaan
źródło
Wierzę, że programiści Faktio znów optymalizują pasy , aby musieli tylko okresowo symulować przedmioty, a przez cały czas interpoluje tylko ich pozycję, gdy na nie patrzysz. Ale robią to dopiero teraz, gdy cała gra była efektywnie oparta na pasach (i robotach) przez długi czas, a dopiero wtedy, gdy ludzie zaczęli zauważać i narzekać na wydajność.
immibis
2
@immibis Dokładnie. Zwiększenie wydajności, zanim ludzie faktycznie osiągną limity, byłoby marnotrawstwem zasobów, które lepiej wydać gdzie indziej. Nawet z najnowszymi optymalizacjami najprawdopodobniej napotkasz problem tylko w megafaktorach znacznie wykraczających poza zakres normalnej rozgrywki (odpalanie rakiety). Umożliwiło to jednak nowe ustawienia rozgrywki Maraton, które wymagają znacznie cięższej logistyki. I znowu, zidentyfikowali wąskie gardła, uzyskując statystyki od osób faktycznie grających w grę - około połowa wąskich gardeł była dla nich wielką niespodzianką.
Luaan
@Fattie Więc rozumiesz pytanie, ale nie odpowiedź? Próbujesz tylko powiedzieć, że możliwa jest prostsza odpowiedź (np. „Ekonomia”)? Nie rozumiem, jak twój komentarz powinien być konstruktywny.
Luaan
2
@Fattie Jeśli uważasz, że odpowiedź brzmi „Optymalizuj tylko wtedy, gdy wiesz, co jest wąskim gardłem”, opublikuj ją jako odpowiedź. Wydałeś już dużo więcej słów, niż wymagałoby to dobrej odpowiedzi, więc śmiało. Jaki rodzaj optymalizacji nie czyni rzeczy lepszymi i gorszymi? Z pewnością nigdy go nie widziałem. Podstawową rzeczą, którą powtarzam, jest to, że zawsze jest to kompromis - czas, który można lepiej spędzić gdzie indziej, złożoność, która ogranicza twoją elastyczność ... Przykłady wskazują, że „wczesne” nie jest terminem absolutnym; niektóre optymalizacje są konieczne od pierwszego dnia, podczas gdy inne są szkodliwe.
Luaan
1
@Fattie „Optymalizuj tylko wtedy, gdy wiesz, jakie jest wąskie gardło” jest odpowiedzią na „Kiedy powinienem optymalizować?” i nie jest odpowiedzią na „Dlaczego zbyt wcześnie optymalizować?”.
immibis
10

Wydaje się, że wiele odpowiedzi skupia się na aspekcie wydajności „optymalizacji”, podczas gdy ja lubię patrzeć na optymalizację i całą próbę optymalizacji zbyt wcześnie na bardziej abstrakcyjnym poziomie.

Humor mnie, gdy staram się rozwinąć moją perspektywę za pomocą poliominoes.

Załóżmy, że mamy ustalone granice określone przez platformę lub silnik, z którym pracujemy.

wprowadź opis zdjęcia tutaj

Następnie przystępujemy do tworzenia pierwszej warstwy / modułu gry.

wprowadź opis zdjęcia tutaj

Idąc dalej, budujemy naszą drugą warstwę / moduł.

wprowadź opis zdjęcia tutaj

W tym momencie możemy zauważyć, że między dwoma modułami jest trochę miejsca i możemy ulec pokusie zoptymalizowania go, aby w pełni wykorzystać przydzielone nam granice.

wprowadź opis zdjęcia tutaj

Idealnie, teraz aplikacja w pełni wykorzystuje dostępne nam zasoby, aplikacja jest lepsza, dobrze, prawda?

Kontynuujemy budowę trzeciej warstwy / modułu naszej aplikacji i nagle dochodzimy do wniosku (być może nawet takiego, którego nie moglibyśmy przewidzieć podczas wstępnego planowania), że trzecia warstwa / moduł nie działa dla nas.

Szukamy alternatywy, znajdujemy ją, a na koniec wymaga to również zmiany drugiego modułu naszej aplikacji, ponieważ jest on niezgodny z naszym nowo wybranym trzecim modułem. (Na szczęście jest nieco kompatybilny z naszym pierwszym modułem, więc nie musimy przepisywać wszystkiego od nowa).

Więc złożyliśmy to wszystko razem ...

wprowadź opis zdjęcia tutaj

Hmm, widzisz co się stało?

Optymalizując zbyt wcześnie, poprawiliśmy efektywność, ponieważ optymalizacja nie jest tym, co zakończyliśmy.

A jeśli chcielibyśmy później dodać dodatkowe moduły lub dodatkowe ciekawostki, moglibyśmy już nie mieć możliwości ich wykonania w tym momencie.

wprowadź opis zdjęcia tutaj

A dostosowanie najniższego poziomu naszego systemu nie jest już tak wykonalne, ponieważ został on pochowany pod wszystkimi innymi warstwami.

Gdybyśmy jednak zdecydowali się poczekać z chęcią zoptymalizowania go natychmiast, otrzymalibyśmy coś takiego:

wprowadź opis zdjęcia tutaj

A teraz, jeśli przeprowadzimy optymalizację w tym momencie, otrzymamy coś satysfakcjonującego.

wprowadź opis zdjęcia tutaj

Mam nadzieję, że przynajmniej niektórzy z was czytali to tak dobrze, jak ja to robiłem :) i jeśli teraz macie wrażenie, że lepiej rozumiecie ten temat - tym lepiej.

Gekon sufitowy
źródło
3
Usuwam reklamę. Nie obchodzi nas, w jaki sposób stworzyłeś obrazy; jedyną ważną częścią jest założenie, że masz prawo je opublikować.
Gnemlock,
2
@Gnemlock jest wystarczająco uczciwy, chociaż mój zamiar nie był skierowany na reklamę, widzę też, jak można go łatwo interpretować jako taki, i zgodzę się, że nie dodaje niczego do odpowiedzi, więc nie ma w nim miejsca.
Gekon sufitowy
2
Myślę, że ta wizualna metafora jest niezwykle skuteczna. Świetne podejście do odpowiedzi na pytanie! :)
DMGregory
8

Pieniądze.

Wszystko sprowadza się do tego. Pieniądze. Ponieważ czas to pieniądz *, im więcej czasu poświęcisz na działania, które nie gwarantują wygenerowania większej ilości pieniędzy (tj. Nie możesz traktować tych działań jako inwestycji ), tym więcej pieniędzy ryzykujesz zmarnowaniem i tym mniej pieniędzy zarobisz dzięki gra.

Oto kilka potencjalnych skutków ubocznych zbyt wczesnej optymalizacji i powody, dla których należy tego unikać:

  • Twoi koledzy cię nienawidzą, ponieważ bawisz się w piaskownicy zabawkami, a oni ciężko pracują, aby uzyskać dostęp do funkcji.
  • Opóźnienia nie podobają się wyższemu kierownictwu i sprawiają, że zespół nie dotrzymuje terminów dostawy, co powoduje, że gra zostanie wydana wiosną zamiast przed sezonem wakacyjnym, co z kolei powoduje, że gra nie sprzedaje się wystarczająco. Z tego powodu centrala decyduje o zamknięciu studia, skutecznie uniemożliwiając pracę. Brak pracy oznacza brak pieniędzy. (A ponieważ nikt cię nie lubi, ponieważ poświęciłeś zbyt dużo czasu na wczesną optymalizację, nikt nie chce napisać pozytywnej recenzji na temat swojej pracy na LinkedIn, co pozwoli Ci uniknąć pieniędzy na dłużej).

* Inne odpowiedzi wystarczająco dobrze podkreśliły część dotyczącą „czasu”


Na marginesie, ogólnie rzecz biorąc , doświadczenie pomaga określić, co jest, a co nie jest przedwczesną optymalizacją i co ma wartość dla firmy, a co nie.

Na przykład, jeśli pracowałeś nad grą A i pod koniec projektu zdałeś sobie sprawę, że funkcja XYZ była szczególnie obciążona w twojej pętli, i ostatecznie zaczynasz pracę nad grą B, która ma dokładnie tę samą funkcję, decydując się na przepisz tę funkcję i optymalizacja od samego początku nie jest tak naprawdę przedwczesną optymalizacją, ponieważ wiesz, że będzie to wąskie gardło, jeśli nic nie zostanie zrobione.

Alexandre Vaillancourt
źródło
1
@JBentley, który jest prawidłowym punktem. Istnieje jednak konsekwentna odpowiedź na „dlaczego” jako bezpośrednia implikacja zebranych doświadczeń. Tak więc, 1) wczesna optymalizacja może prowadzić do marnowania czasu na części kodu, które nie wpływają na średni czas działania (biorąc pod uwagę doświadczenie, powinieneś wiedzieć, które konstrukcje kodu są kandydatami do najlepszych praktyk optymalizacyjnych) 2) wczesna optymalizacja może sprawić, że kod będzie trudniejszy do utrzymania z powodu ograniczeń projektowych nałożonych na niektóre systemy. W ten sposób możesz zyskać zbyt mało w zamian za dłuższe cykle programowania / kłopotliwy kod do utrzymania.
teodron
3
@JBentley Rozważ to jako dodatek do odpowiedzi DMGregory.
Alexandre Vaillancourt
1
To wydaje się być jedyną wartą odpowiedzi tutaj. Pytanie to tautologia. Równie dobrze możesz zapytać: „Więc dlaczego właściwie chcesz, aby gra zarabiała więcej w sklepach z aplikacjami niż na mniej?” Jak możesz na to odpowiedzieć?
Fattie
@Fattie Dobrze jest mieć taką perspektywę w odpowiedzi. Ale stwierdzenie, że jest to jedyna wartościowa odpowiedź, polega na nieuzasadnionym zawężeniu zakresu pytania. Zakładasz na przykład, że jedyne stworzone gry znajdują się w organizacjach nastawionych na zysk (oczywiście nieprawda). Zakładasz również, że dla PO oczywiste jest, że wczesna optymalizacja prowadzi do poświęcenia większej ilości czasu (a tym samym więcej pieniędzy, jeśli mówimy o firmie). W rzeczywistości pytanie OP pokazuje, że nie jest tego pewien.
JBentley
6

Optymalizacja jest z definicji procesem zwiększania wydajności rozwiązania do momentu, w którym traci on swoją skuteczność. Proces ten oznacza następnie zmniejszenie przestrzeni rozwiązania.

Na wczesnym etapie tworzenia oprogramowania nadal mogą istnieć „ukryte wymagania”: jeśli zbytnio zmniejszysz przestrzeń swojego rozwiązania, możesz skończyć w sytuacji, w której nie będziesz w stanie spełnić „ukrytego wymagania”, gdy się pojawi. na późniejszym etapie rozwoju, zmuszając cię do modyfikacji architektury, dodając w ten sposób niestabilność i możliwą masę niepożądanych zachowań.

Chodzi o to, aby całe rozwiązanie działało, a dopiero wtedy, gdy wszystkie wymagania zostaną ustalone i zaimplementowane, zaostrz kod. Zobaczysz wtedy, że wiele optymalizacji, które bezproblemowo wdrożysz od razu podczas kodowania, nie są już możliwe z powodu interakcji z późnymi wymaganiami.

Rzeczywista przestrzeń rozwiązania jest zawsze większa niż ta, której oczekujemy na początku, ponieważ nie możemy mieć doskonałej wiedzy o bardzo złożonym systemie.

Spraw, by działało pierwsze. Następnie napnij liny.

Earendel
źródło
2
Myślę, że „skuteczność” nie jest tym słowem, którego szukasz - oznacza „siłę do wytworzenia efektu”, więc w pewnym sensie optymalizacja polega na zwiększeniu skuteczności. Czy szukasz konkretnej wersji skuteczności? (Czy możesz link do tego?) Czy zamiast tego masz na myśli elastyczność?
doppelgreener
2
@doppelgreener Oznacza to, że uczynisz to rozwiązanie bardziej wydajnym, aż przestanie wytwarzać pożądane efekty ...
immibis
1
„Najpierw zadziałaj. Następnie napnij liny”. - To musi być tyle samo warte, co reszta odpowiedzi łącznie. Nie mówiąc już, że inne rzeczy nie były dobre.
ebyrob
1
@ebyrob: istnieje inne powiedzenie: „Spraw, by działało, poprawiaj,
ninjalj
@ninjalj To też jest dobre. Oczywiście mój szef zawsze mi mówi: „Nie spiesz się, popraw to”. Nie wiem więc, czy to, co mówisz, jest tak uniwersalne, jak główny, o który pierwotny plakat prosi o więcej szczegółów.
ebyrob
2

Krótko mówiąc, bardzo często wczesna optymalizacja staje się zmarnowanym wysiłkiem, jeśli chcesz zmienić rzeczy później, a potem okazuje się, że zoptymalizowałeś łatwo zmienialne struktury kodu na coś znacznie niższego poziomu, a teraz musisz zmienić go z powrotem na wysoki podejście do poziomu i ponownie zoptymalizuj wynik.

Jest to powszechny błąd dla początkujących programistów, którzy lubią skupiać się na czerpaniu przyjemności z „wykonywania użytecznej pracy”, takiej jak optymalizacja, nie zastanawiając się, czy jest to właściwy moment, aby to zrobić. W miarę zdobywania doświadczenia w programowaniu dużych projektów, takich jak gry, dowiesz się, kiedy jest to uzasadnione, aby zoptymalizować istniejącą bazę kodów, a kiedy będzie za wcześnie. Nie bój się popełnić tego błędu, skorzystasz tylko z nauki.

Zasadniczo optymalizuj tylko, jeśli naprawdę nie możesz pracować z kompilacją programistyczną w tym momencie. Na przykład, jeśli tworzysz i usuwasz miliony obiektów 60 razy na sekundę.

Tak więc powiedziałbym, że warto, jeśli chodzi o doświadczenie uczenia się, optymalizować kilka razy wcześniej: str

użytkownik1306322
źródło
2

Optymalizacja koncentruje się na tym, aby komputer działał najlepiej na kodzie, podczas gdy programowanie wymaga, aby programista działał najlepiej na kodzie.

Optymalizacja nie zapewnia wglądu. To sprawia, że ​​komputer działa mniej, a programista więcej.

Oczywiście istnieją różne kategorie, na przykład przy projektowaniu samochodu. Nie ma nic złego w optymalizacji silnika przed zbudowaniem pierwszego podwozia, ale optymalizacja podwozia, zanim nie wiadomo, jaki kształt ma silnik, może skończyć się stratą czasu. Modułowość i specyfikacje mogą zapewnić wiele miejsc wykonalnych do prac optymalizacyjnych, nawet zanim produkt jako całość zostanie zmontowany.

użytkownik101307
źródło
Można to poprawić, wprowadzając coś innego niż zdefiniowanie optymalizacji i mówiąc o praktycznej sytuacji w rozwoju gier, zamiast sięgać po analogie dotyczące samochodów.
doppelgreener
Idealna odpowiedź na pytanie tautologiczne.
Fattie
2

Zdecydowanie nie zgadzam się ze wszystkimi tymi stwierdzeniami, że kodu nie należy optymalizować na wczesnych etapach. To zależy od tego, na jakim etapie jesteś. Jeśli jest to MVP - prototyp, a ty po prostu starasz się spojrzeć na grę, która zajmuje od 1 do 2 tygodni, że jesteś gotowy przepisać wszystko, co już napisałeś. Tak, optymalizacja nie ma znaczenia. Ale jeśli już pracujesz nad grą, o której wiesz, że zostanie wydana, powinna ona cały czas mieć zoptymalizowany kod. Opowieści, które ludzie mówią o nowych funkcjach i rzeczach, które można dodać, są nieporozumieniem. Jest to źle zaprojektowana architektura kodu zamiast kodu zoptymalizowanego. Nie musisz niczego przepisywać, aby dodać nowe elementy, jeśli masz ładny design kodu.

Na przykład, jeśli masz algorytm wyszukiwania ścieżki A *, czy nie lepiej byłoby napisać go w najbardziej optymalny sposób? Zamiast później zmienić połowę posiadanego kodu, ponieważ musiałeś wprowadzić pewne zmiany w algorytmie, ponieważ teraz potrzebuje on innych wywołań metod i wywołań zwrotnych? A jeśli masz już zoptymalizowany kod od samego początku, zaoszczędzi ci to dużo czasu. Ponieważ możesz narysować relacje między obiektami i sposób ich interakcji, zamiast ślepo tworzyć mnóstwo nowych skryptów i od razu nawiązywać wszystkie połączenia - prowadzi to do niejasnego kodu sfagetti.

Ostatnią rzeczą, którą chcę dodać, jest to, że później, nawet jeśli nie chcesz już tworzyć gry, masz zoptymalizowany algorytm A *, którego możesz użyć do następnych gier. Wielokrotnego użytku. Na przykład wiele gier ma inwentaryzację, wyszukiwanie ścieżek, generowanie procedur, interakcje między NPC, systemem walki, AI, interakcją interfejsu, zarządzaniem danymi wejściowymi. Teraz powinieneś zadać sobie pytanie: „Ile razy przepisałem te elementy od zera?” Stanowią one 70–80% gry. Czy naprawdę musisz je ciągle przepisywać? A jeśli nie zostaną zoptymalizowane?

Candid Moon _Max_
źródło
5
Jeśli kod * powinien zacząć się optymalizować, to jak zoptymalizowany jest „zoptymalizowany”? Czy przed wykonaniem testów porównawczych kodujesz rzeczy w asemblerze? Myślę, że ogólnie rzecz biorąc, nietrywialne prace nad mikrooptymalizacjami powinny zostać pozostawione do czasu przeprowadzenia testów porównawczych. Kompilator optymalizacyjny może wykonać lepszą pracę niż Ty w mikrooptymalizacjach i jest znacznie tańszy.
gmatht
@gmatht Cóż, A * może być inny, ponieważ powiedziałem, że może mieć zły projekt i nie działać naprawdę dobrze. Ale może być wielowątkowy, oparty na stercie Fibonacciego, ma pewne prognozy, podzielony na kawałki. Jeśli mówimy o wyszukiwaniu ścieżek, możemy dodać pole przepływu, które będzie mieszane z A *. Również sposób wygładzania, wiele wysokości. Może A * nie jest najlepszym przykładem, ale jak powiedziałem, optymalizacje nie mają nic wspólnego z faktyczną zmianą kodu, programista zmienia kod, ponieważ został źle zaprojektowany, na przykład nie podążył za SOLID-em z jednego powodu. napisałeś to A * cóż, nie musisz już tego pisać.
Candid Moon _Max_
Mikrooptymalizacje @gmatht tak naprawdę nie stanowią problemu. Myślę, że OP oznacza bardziej najlepszy i najbardziej wydajny sposób obliczania zachowania AI, shadera lub czegokolwiek innego.
Candid Moon _Max_
Jeśli wiesz, jak skutecznie pisać te rzeczy, nie widzę problemu. Zajmie to tyle samo czasu, a nawet mniej. Zależy to bardziej od doświadczenia programistów. Jako programista, jeśli znam lepsze rozwiązanie, nie napisałbym gównianego. Jeśli wiem, jak napisać stertę Fibonacciego, nie będę używać listy i sortowania, aby uzyskać wartości minimalną lub maksymalną. Oczywiście napisanie go zajmie więcej czasu i wysiłku, zamiast korzystania z List.Sort (); , ale co, jeśli mam już urządzenie wielokrotnego użytku?
Candid Moon _Max_
2
@CandidMoon Nie zgadzam się z „To zajmie tyle samo czasu lub nawet mniej”. pisać efektywny kod. Być może pisanie kodu nie byłoby oczywiście nieefektywne, ale kiedy twoje pojęcie „wydajności” oznacza rozważenie rywalizacji o pamięć podręczną, dodanie wielowątkowości, użycie specyficznych dla CPU cech wewnętrznych dostępnych tylko na niektórych procesorach, przełączanie algorytmów na duże N z powodu O (N log N) vs O (N) - tego rodzaju rzeczy są trudne i podatne na błędy i zajmują znacznie więcej czasu niż prosta implementacja. Robisz to tylko wtedy, gdy wiesz, że ma to istotny wpływ na końcowy wynik.
Michael Anderson