Jakie są typowe pułapki podczas pisania gier w zarządzanym języku, takim jak C #? [Zamknięte]

66

Jakie pułapki napotkałeś podczas pisania gier na PC w zarządzanym języku, takim jak C #, i jak je rozwiązałeś?

Michael Klement
źródło
To pytanie lepiej zadać w przypadku przepełnienia stosu, ponieważ niewiele jest o nim specyficznych dla gier.
Chris Garrett,
31
@Chris: Zdecydowanie się nie zgadzam: pytanie dotyczy konkretnie gier! Problemy, które napotykasz, gdy musisz wypychać aktualizację co 16 ms, są zupełnie inne niż w większości aplikacji komputerowych.
Andrew Russell,
Pytanie jest dość niejasne. Java i C # różnią się na tyle, że tylko bardzo ogólne porady dotyczą obu. Wszystkie dotychczasowe odpowiedzi to C #. Nie wymieniono także platformy docelowej - dobre porady mogą się różnić w zależności od urządzenia (np. Programowanie na telefon komórkowy, zune, xbox, inne niż programowanie na PC). Było nawet wystarczająco szerokie pytanie, że ktoś odpowiedział, że same zarządzane języki to „pułapka”.
paulecoyote
@paulecoyote: Zmieniłem pytanie, aby zapytać tylko o C #. Ponadto, ponieważ nie wymieniono żadnej konkretnej platformy, chodzi o komputer.
Michael Klement
@Michael zakładając, że platforma jest niebezpiecznym założeniem, ponieważ różnią się tak bardzo pod względem implementacji, dobrze byłoby wspomnieć o systemie Windows i całkowicie upuścić „jak” i „Java”.
paulecoyote

Odpowiedzi:

69

Nie wiem dużo o Javie, więc jest to z punktu widzenia dewelopera .net.

Zdecydowanie największym jest śmieci. kolektor śmieci .NET w systemie Windows wykonuje fantastyczną robotę i możesz uciec bez dziecka w większości. Na xbox / winPhone7 to inna sprawa. Jeśli dostajesz przeciągnięcia co kilka klatek, usuwanie śmieci może powodować problemy. W tej chwili uruchamia się po każdym przydziale 1 MB.

Oto kilka wskazówek dotyczących postępowania ze śmieciami. W rzeczywistości nie powinieneś martwić się większością z nich, ale któregoś dnia mogą się przydać.

  • Narysuj zawartość GC.GetTotalMemory()ekranu. Daje to przybliżoną ilość przydzielonych bajtów używanych przez grę. Jeśli prawie się nie porusza, masz się dobrze. Jeśli szybko rośnie, masz problemy.
  • spróbuj przydzielić wszystkie swoje obiekty sterty z góry. Jeśli nie rozdzielisz wszystkiego przed rozpoczęciem gry, za każdym razem, gdy trafisz megę przydziałów, przestaniesz. Bez przydziałów, bez kolekcji. Tak proste jak to.
  • Po załadowaniu wywołaj GC.Collect (). Jeśli wiesz, że większość dużych przydziałów jest na dobrej drodze, miło jest poinformować system.
  • NIE WYWOŁAJ GC.Collect()każdej ramki. To może wydawać się dobrym pomysłem, utrzymywanie nadmiaru śmieci i tak dalej, ale pamiętajcie, że tylko z gorszym niż zbiórka śmieci jest ponad śmieci.
  • Poszukaj skąd pochodzą twoje śmieci. Istnieje kilka typowych przyczyn, takich jak łączenie łańcuchów zamiast używania StringBuilder(uwaga, StringBuildernie jest to magiczna kula i wciąż może powodować alokacje! Oznacza to, że proste operacje, takie jak dodanie liczby na końcu łańcucha, mogą spowodować zaskakujące ilości śmieci) lub używanie foreachpętli w kolekcjach korzystających z IEnumerableinterfejsu może również tworzyć śmieci bez Twojej wiedzy (na przykład foreach (EffectPass pass in effect.CurrentTechnique.Passes)jest to powszechne)
  • Użyj narzędzi, takich jak profiler pamięci CLR, aby dowiedzieć się, gdzie przydzielana jest pamięć. Istnieje wiele samouczków na temat korzystania z tego narzędzia.
  • Kiedy wiesz, gdzie alokujesz podczas gry, sprawdź, czy możesz użyć sztuczek, takich jak łączenie obiektów, aby obniżyć liczbę alokacji.
  • Jeśli wszystko inne zawiedzie, spraw, aby Twoje kolekcje działały szybciej! GC w zwartym frameworku śledzi każde odwołanie w kodzie, aby dowiedzieć się, które obiekty nie są już używane. Refaktoryzuj kod, używając mniej referencji!
  • Pamiętaj, aby używać IDisposablena klasach, które zawierają niezarządzane zasoby. Możesz ich użyć do wyczyszczenia pamięci, GC nie może się zwolnić.

Inną rzeczą do przemyślenia jest wydajność zmiennoprzecinkowa. Chociaż .Net JITer wykonuje sporo optymalizacji specyficznych dla procesora, nie może używać SSE ani żadnych innych zestawów instrukcji SIMD do przyspieszenia matematyki zmiennoprzecinkowej. Może to powodować dość dużą różnicę prędkości między C ++ i C # w grach. Jeśli używasz mono, mają specjalne biblioteki matematyczne SIMD, z których możesz skorzystać.

Cubed2D
źródło
Zgadzam się całkowicie. Implementacje modułu wyrzucania elementów bezużytecznych na „mniejszych” platformach wydają się być kompletnym śmieciem.
Krisc,
Dobry post, jednak jeśli chodzi o twoje zdanie na temat „alokowania obiektów sterty z góry”, polecam przeczytać Prawdę o typach wartości
Justin
Świetnie, gdy optymalizujesz kod, naprawdę warto zrozumieć platformę, dla której próbujesz zoptymalizować. To nie był tak naprawdę komentarz na temat typów wartości / typów referencji, żaden długowieczny obiekt prawdopodobnie nie będzie na stosie. Chodzi o to, aby upewnić się, że jak najwięcej zasobów przydzielono w czasie ładowania, abyś nie uderzył w tę magiczną barierę 1 MB podczas gry. Wszystkie implementacje .net, które również są celami xna, mają staranną gwarancję, obiekty przydzielone blisko siebie w czasie będą blisko w przestrzeni, co może być dobre dla perf.
Cubed2D,
Wygląda również na to, że zapomniałem wspomnieć, że obecna kompaktowa struktura na Xboxie jest uczulona na wywołania metod wstawiania. Zachowaj ten jeden dla najgorszych scenariuszy perf. Korzystanie z wersji referencyjnych metod matematycznych wygląda wystarczająco brzydko!
Cubed2D,
10

Typowym problemem związanym z wydajnością nie jest uwzględnianie śmieciarza w projektowaniu / rozwoju gry. Wytwarzanie zbyt dużej ilości śmieci może prowadzić do „czkawek” w grze, które mają miejsce, gdy GC działa przez dość długi czas.

W przypadku języka C # użycie obiektów wartości i instrukcji „using” może złagodzić presję ze strony GC.

Matias Valdenegro
źródło
3
Dodatkowo możesz powiedzieć Garbage Collectorowi, aby uruchomił się jawnie, jeśli właśnie skończyłeś alokację / wolną pętlę ciężką lub jeśli masz wolne cykle.
BarrettJ
16
usingOświadczenie nie ma nic wspólnego ze zbierania śmieci! Jest przeznaczony dla IDisposableobiektów - które służą do zwalniania niezarządzanych zasobów (tj. Takich, które nieobsługiwane przez moduł odśmiecania ).
Andrew Russell
9

Powiedziałbym, że największym problemem, jaki napotkałem podczas pisania gier w C #, był brak porządnych bibliotek. Większość, które znalazłem, to albo bezpośrednie porty, ale niekompletne, lub owijarki w bibliotece C ++, które ponoszą ciężką karę wydajności za zestawianie. (Mówię konkretnie o MOgre i Axiom w bibliotece OGRE oraz BulletSharp w bibliotece fizyki Bullet)

Języki zarządzane (w odróżnieniu od Interpretowanych - ani Java, ani C # nie są już interpretowane) mogą być tak szybkie jak języki rodzime, jeśli dobrze rozumiesz, co tak naprawdę spowalnia (zbieranie, usuwanie śmieci). Myślę, że prawdziwym problemem jest to, że twórcy bibliotek jeszcze tego nie zauważyli.

Karantza
źródło
To dobrze, że C # i Java są zarządzane zamiast interpretowane ... edytowałem moje pytanie, aby było bardziej dokładne :)
Michael Klement
2
Marshalling ogólnie stanowi wąskie gardło w zakresie wydajności, ale pomaga także zdawać sobie sprawę z możliwych do zaakceptowania typów, które można mapować bezpośrednio do pamięci niezarządzanej bez znaczącego wpływu na wydajność. msdn.microsoft.com/en-us/library/75dwhxf7.aspx
Sean Edwards
8

Jak mówili inni, największym problemem są przerwy w gromadzeniu GC. Korzystanie z pul obiektów jest jednym typowym rozwiązaniem.

hojny
źródło
4

C # i Java nie są interpretowane. Są one kompilowane do pośredniego kodu bajtowego, który po JIT staje się tak samo szybki jak kod macierzysty (lub wystarczająco blisko, aby być nieznacznym)

Największą pułapką, jaką znalazłem, jest uwolnienie zasobów, które bezpośrednio wpływają na wrażenia użytkownika. Te języki nie obsługują automatycznie deterministycznej finalizacji, podobnie jak C ++, co, jeśli nie spodziewasz się, że może prowadzić do takich rzeczy jak siatki unoszące się wokół sceny po tym, jak myślałeś, że zostały zniszczone. (C # dokonuje deterministycznej finalizacji poprzez IDisposable , nie jestem pewien, co robi Java.)

Poza tym zarządzane języki są naprawdę o wiele bardziej zdolne do radzenia sobie z wydajnością, jakiej wymagają gry, niż zdobywają uznanie. Dobrze napisany kod zarządzany jest znacznie szybszy niż źle napisany kod natywny.

Sean Edwards
źródło
Tak, dziękuję za notatkę, już poprawiłem interpretowaną / zarządzaną rzecz;) Również dobrze, że siatki unoszą się wokół sceny. Nie pomyślałem o tym, myśląc o problemach z GC ...
Michael Klement
1
IDisposableumożliwia deterministyczne czyszczenie krytycznych czasowo i niezarządzanych zasobów, ale nie wpływa bezpośrednio na finalizację ani moduł wyrzucania elementów bezużytecznych.
Sam Harwell,
4

Ani Java, ani C # nie są interpretowane. Oba zostaną skompilowane w natywny kod maszynowy.

Największym problemem zarówno z nimi, jak i grami jest konieczność kodowania w taki sposób, aby nigdy nie wyrzucały śmieci podczas gry. Liczba obręczy, które musisz przeskoczyć, aby to osiągnąć, prawie przewyższa zalety korzystania z nich w pierwszej kolejności. Należy unikać większości funkcji, które sprawiają, że język ten jest przyjemny w programowaniu aplikacji lub serwera podczas programowania gier, w przeciwnym razie podczas grania dostaniesz długie przerwy w grze i zbieranie śmieci.

gman
źródło
Śmieciami można sobie poradzić, przynajmniej w języku C #, dość dobrze, więc nie powiedziałbym, że to łamanie umów. Przy odpowiednim wątkowaniu i świadomości stanu programu można uniknąć problemów z wydajnością. To jeszcze jedna rzecz, o której musisz pomyśleć, i sprawia, że ​​zarządzany język jest nieco mniej zarządzany.
Karantza
4
Warto zauważyć, że w .NET 4 śmieciarz obsługuje zbieranie w tle dla wszystkich trzech generacji obiektów. Powinno to skutecznie zminimalizować wpływ wydajności wyrzucania elementów bezużytecznych w grach. Odpowiedni link: geekswithblogs.net/sdorman/archive/2008/11/07/…
Mike Strobel
Śmieciarze ewoluują, a jak zauważył Mike Strobel, niektórzy są już w produkcji, co prawie eliminuje tę pułapkę.
Sam Harwell
2
Ani C #, ani Java nie są kompilowane do natywnego kodu maszynowego. C # kompiluje się do MSIL i Java do bytecode. Odśmiecanie nie spowoduje żadnych „długich” przerw, ale może spowodować „czkawkę”.
Cloudanger
2

Jedną z dużych pułapek, jakie widzę podczas tworzenia gier w takich językach (lub przy użyciu narzędzi takich jak XNA, silnik TorqueX itp.) Jest to, że trudno będzie znaleźć zespół dobrych ludzi z doświadczeniem potrzebnym do stworzenia gry równoważnej z byłoby dość łatwo znaleźć ludzi w C ++ i OpenGL / DirectX.

Branża twórców gier jest nadal bardzo mocno zanurzona w C ++, ponieważ większość narzędzi i potoków używanych do wypychania dużych lub po prostu dobrze dopracowanych małych gier została napisana w C ++ i, o ile wiem, WSZYSTKIE oficjalne zestawy deweloperów, które możesz get for XBox, PS3 i Wii są wydawane tylko z kompatybilnością z C ++ (zestaw narzędzi XBox może być obecnie bardziej zarządzany, czy ktoś wie więcej?)

Jeśli chcesz teraz tworzyć gry na konsole, prawie XNA i C # dostajesz na XBox, a potem tylko w bocznej części biblioteki gier o nazwie XBox Live Indie Games. Niektórzy, którzy wygrywają konkursy itp., Są wybierani do przeniesienia swojej gry do prawdziwej konsoli gier XBox. Poza planem stworzenia gry na PC.

mikeschuld
źródło