Czy zaawansowane gry w javie byłyby mniej wydajne od tych napisanych np. w c++?

0

W javie to znam minecrafta i właściwie tyle gier nie licząc mniejszych. Nie znam żadnych gier z grafiką jak w call of duty, battlefieldy, crysis.
Czy gdyby twórcy gry Crysis 3 (DX11) napisali identyczną grę w Javie, to czy wydajność Crysis 3 w Javie byłaby taka sama, czy gorsza, albo dużoooo gorsza i wręcz niegrywalna?

1

w javie nie ma zarzadzaniem pamieci recznej. Jest Garbage Collector. Wyobraz sobie ze grasz i co jakis czas gra Ci zamraza bo musi wyczyscic to co zadelkarowal program

zreszta
naucz sie szukac sam odpowiedzi bo byla juz milion razy

w google wpisujemy
why games are not written in java

pierwszy link
http://stackoverflow.com/ques[...]t-video-games-written-in-java

12

Czy gdyby twórcy gry Crysis 3 (DX11) napisali identyczną grę w Javie, to czy wydajność Crysis 3 w Javie byłaby taka sama, czy gorsza, albo dużoooo gorsza i wręcz niegrywalna?

Wydaje mi się że Twórcy Gry Crysis 3 to programiści C++ (a przynajmniej ta część twórców która programuje), więc gdyby napisali grę w Javie to byłaby dużoooo gorsza i wręcz niegrywalna.

w javie nie ma zarzadzaniem pamieci recznej. Jest Garbage Collector. Wyobraz sobie ze grasz i co jakis czas gra Ci zamraza bo musi wyczyscic to co zadelkarowal program

Kontrargument (tak, to C#, ale przypadek wystarczająco podobny):
http://unity3d.com/showcase/gallery

Jeśli chodzi o programowanie gier w językach z GC: sam zaczynając programować interesowałem się grami (jak niejeden pewnie), tylko zamiast grać wolałem je tworzyć. No więc na temat podejść do pisania gier w C#:

Po pierwsze: nie alokować (a.k.a. /hackować/ zamiast alokować)
Najprostsze koncepcyjnie i najbardziej ekstremalne podejście - nie alokować nic. A konkretnie nie alokować nic w pętli głównej gry (zdarzenia dynamiczne jakby nie próbować, nową pamięć brać muszą, ale tak jest wszędzie). Żeby to osiągnąć trzeba:

  • cachować obiekty (tzw. object pools). Ilość typów obiektów w grze jest ograniczona. Po prostu zamiast pisać obj = new Obiekt() można pisać obj = Cache<Obiekt>.Create() (...oraz Cache<obiekt>.Free(obj) - i mamy wszystkie problemy pilnowania pamięci w zarządzanym świecie - świetnie). Jest to dużo wolniejsze (i brzydsze) niż "prawdziwe new" w C# (ktore 99.5% czasu jest równie szybką operacją co np. dodawanie)
  • używać struktur gdzie się da/gdzie to ma sens (ok, to się na javę nie przenosi). Struktury są alokowane na stosie zamiast na stercie, ale jest catch - JIT jest optymalizowany pod typowe przypadki, a typowym przypadkiem w C# są klasy. Z tego powodu sporo koniecznych optymalizacji nie działa dla value types (np. inlining! Przy prymitywnych operacjach jak np. dodawanie wektorów to znaczący problem), i albo mamy wolny kod (poza tym że samo przekazywanie sturktur jest wolniejsze niż klas), albo używamy hacków (jak przekazywanie struktur przez referencję).
  • przepisywać funkcje z biblioteki standardowej od zera. Cóż, większość funkcji "leakuje" pamięć, więc jeśli chcemy "całą dla nas", trzeba je napisać samodzielnie zamiast koncentrować się na pisaniu gry.
    disclaimer: nie piszę co robić. Piszę co można zrobić. Żeby nie było że promuję kod nadający się do programistycznego WTF

Po drugie: alokować mało lub krótko
Pogłoski o tym że GC w trakcie gry to śmierć są mocno przesadzone. Nie znam się na ekosystemie i technologiach w świecie javy, więc znowu od strony dotnetu, jak to naprawdę jest z GC:

  • GC gen 0 jest szybki. Mokrym snem projektantów było to żeby zajmował nie więcej czasu niż zwykły cache miss - jesteśmy "not quite there yet", ale to coś na co trzeba zwrócić uwagę - po to obiekty są podzielone na generacje żeby typowy GC wykonywał się w mgnienie oka.
  • z Gen1 i Gen2 nie ma już tak fajnie. Ale to może nie być problemem, jeśli obiekty nie przeżywają gen0 - to znaczy jeśli wszystkie obiekty w grze są albo bardzo ulotne (np. żywe jedną klatkę gry), albo trwałe (np. żywe cały level) to te kolekcje mogą wcale nie nastąpić.
  • z uwag odnośnie GC, sam nie benchmarkowałem nigdy (ani nie korzystałem świadomie) ale podobno duże znaczenia ma ilość rootów (korzeni?) GC. Root = wszystko co globalne zasadniczo (czyli rzeczy statyczne), czyli rzeczy od których GC zaczyna czyszczenie i które musi przejrzeć za każdym razem.
  • jeszcze więcej uwag odnośnie GC. Dotnetowy GC (javowcy też chyba mają analogiczną mechanikę) korzysta z techniki write-barrier, przy pomocy struktury danych nazywającej się cardtable. Cardtable to po prostu tablica bitów, gdzie każdy bit odpowiada pewnemu obszarowi pamięci (np. 1 kb). Zapisanie czegoś do obiektu z niezerowej generacji powoduje zapalenie bitu w cardtable odpowiadającemu pamięci gdzie znajduje się ten obiekt (piszę po polsku? Inaczej ujmując, cardtable mówi mniej-więcej które obszary pamięci zostały zmodyfikowane). Dzięki temu np. GC wykonując mark&sweep w gen1/gen2 ma uproszczoną robotę. Nigdy z tego celowo nie skoszystałem (i nie polecam), ale kolejny przykład na to że GC nie jest musi być głupi.

Po trzecie: skoro trzeba to alokować

  • GC gen 2 jest wolny. Ale od czego są wątki? Od początków dotnetu GC może działać w wielu trybach. Domyślnym z nich do .net 4.5 był concurrent. Dzięki temu przy GC gen 2 większość operacji jest wykonywana równolegle, wątki zatrzymywane są tylko po to żeby poprawić referencje po wykonanej robocie (chyba że wyczerpią ephemeral segment, bo GC gen1 i GC gen0 nie mogą być wykonywać się równocześnie z concurrent GC.
  • GC gen 2 jest wolny. Ale od czego są wątki! Od .net 4.5 domyślnym trybem GC jest nowy 'cutting edge' tryb Background. Background GC potrafi wykonywać jednocześnie GC gen1/gen0 oraz GC gen2 (dodatkowo w server mode na każdy procesor przypada jeden wątek GC, ale to akurat nie dotyczy gier). Przerwy na GC 2 robią się jeszcze krótsze. (Dla ciekawych - http://blogs.msdn.com/b/dotne[...]r-client-and-server-apps.aspx)

Chętnie napisałbym więcej o GC (a jest o czym - brick tables, Large Object Heap, problemy z pinowaniem, managed pointers, finalizacją i rezurekcją, thread hijacking...) ale to by był OT i w dodatku specyficzny dla .net a pytanie o javę ;).

Podsumowanie: czy warto?
Tak chciałem rzucić losowe rzeczy które mi przyszły do głowy z tego co robiłem. Wracając do tematu - gdyby pytanie było po prostu o pisanie gier (gry na komórkę, minigry, gry webowe/flashowe) to odpowiedź byłaby oczywista (że warto - ignorując większość uwag o wydaności z tego posta. Po prostu przy zwykłych grach to nie taki problem).
Z drugiej strony skoro mówimy o "zaawansowanych" grach, czyli grach "przesuwających granicę tego co możliwe" (jak pamiętam np. Crysisa) - nie jestem przekonany (no i pewnie ilosć programistów C#/Javy znających się na pisaniu gier jest... nikła. Podobnie jak wsparcie, chociaż w przypadku C# dzięki Unity to się zmienia). Może i DA SIĘ pisać wydajnie, ale tak naprawdę pytanie jest czy opłaca się to robić.
A z trzeciej strony to i tak jakość współczesnych gier obecnie ogranicza nie wydajność PC tylko konsole (które są ważniejsze z punktu widzenia producentów) ;)

PS. jeszcze zostawię to tutaj: http://superhotgame.com/ (unity)

edit: łał, ale mi post wyszedł. W dodatku rozpisałem się o GC zamiast odpowiadać na pytanie. Cóż, każdy ma jakieś hobby - jedni interesują się samochodami, inni Garbage Collectorami ;).

0
fasadin napisał(a):

w javie nie ma zarzadzaniem pamieci recznej. Jest Garbage Collector. Wyobraz sobie ze grasz i co jakis czas gra Ci zamraza bo musi wyczyscic to co zadelkarowal program

a co chcesz deklarować przy rysowaniu? przecież nie będziesz tworzyć przeciwników przy każdej klatce obrazu; przy ładowaniu poziomu tworzysz obiekty, później je tylko przesuwasz; ewentualnie doczytujesz mapę w którymś miejscu ale przecież nie musisz tego robić cały czas

ostatnio chyba nawet na tym forum ktoś miał problem przycinania się co którąś klatkę gry - rozwiązaniem było ręczne odpalanie GC co klatkę; w ten sposób gra działała niezauważalnie wolniej ale bez przycięć

kod zarządzany po kilkukrotnym odpaleniu zazwyczaj jest tak samo szybki jak natywny
ba - nawet widziałem że javascript potrafi być szybszy od c++

0

@fsadsafdfdsa nie przesadzajmy. JIT to jest fajna rzecz, ale jest mocno empiryczna, niedeterministyczna i w jednej sytuacji przyspieszy kod 10 razy a w innej ani trochę.
Dla tych którzy nie wierzą że język z JIT (np. Java czy Python) mogą być szybsze niż C/C++, nawet pomimo tego że mają narzut maszyny wirtualnej, krótkie wyjaśnienie:

  1. Kompilator języka natywnego może dokonać optymalizacji tylko w czasie kompilacji. W trakcie uruchomienia jest już tylko kod maszynowy interpretowany przez procesor.
  2. Środowisko wykonania dla języków z kodem pośrednim może dokonywać optymalizacji także w trakcie wykonania programu, w trakcie tłumaczenia bajtkodu na kod maszynowy. To jest właśnie JIT - kompilacja Just in Time
    Krótki przykład jak sprawić żeby Java była szybsza niż C. Wyobraźmy sobie że mamy kod który wykonuje miliardy operacji dzielenia przez zmienną podaną przez użytkownika. Kompilator C/C++ niewiele może tu zoptymalizować, w końcu nie wiadomo przez co będziemy dzielić. Co się dzieje jeśli użytkownik poda jako dzielnik liczbę 2? Kod w C/C++ będzie działał identycznie niezależnie od podanej liczby. Kompilator JIT w Javie będzie za to w stanie wykryć że dzielimy przez 2 i będzie mógł zamienić te wszystkie operacje dzielenia na dużo szybsze przesunięcia bitowe. I voila, kod w Javie pomyka szybciej niż kod natywny.

Przykład jest oczywiście przejaskrawiony, ale wcale nie taki nierealny. Ciekawostka: interpreter CPython jest ~5 razy wolniejszy od interpretera PyPy właśnie dlatego że ten drugi posiada kompilator JIT.

Jeśli chodzi o GC to kolejne generacje tych narzędzi są coraz lepsze, nie ma już efektu stop-the-world itd. Nie zmienia to oczywiście faktu że mimo to pewien narzut istnieje. Ale z drugiej strony szansa na wycieki jest dużo mniejsza, czas debugowania też :)

0

Dużo zależy od konkretnego problemu. Zapewne znacznie szybciej napiszesz grę, która będzie miała ograniczoną grafikę (kosztowne obliczenia realizowane GPU, z którymi java sobie średnio radzi) i dużó AI (planszóweczki w stylu Panzer General czy HoMMIII) niż takiego Crysisa

W Javie można zarządzać pamiecią "ręcznie" za pomocą klasy Unsafe > http://www.slideshare.net/mishadoff/unsafe-java-18168628 oczywiście przypomina to sekswakacje w Tajlandii bez użycia gumy, ale zawsze...

Kolejna rzecz to algorytmy i ich implementacja. To już jest czynnik ludzki i dużo zależy od tego kto pisze kod. Mam takie wrażenie, że programiści C++ sa w tym lepsi niż javowcy.

JIT, jak wspomniano powyżej pozwala na optymalizację w czasie wykonania i pewne związane z tym bonusy.

0
<student> Java jest szybsza od C++. W C++ nie ma JIT <inny student="student"> i java zjada mniej pamięci niż C++. Bo w C++ nie ma garbage collector edit. Zaraz opisze sie ;) takze za godzinke bedzie tu cos dlaczego c#/java nie sa dobre jak c++. C++ jest natywnym jezykiem programowania z "runtime library" wiec kiedy aplikacja w c++ jest wywolywana w maszynowym kodzie uzywa funkcji z "runtime" i to jest kompletnie oddzielone od OS aplikacji. To zyje w swoich wlasnych procesach/watkach i uzywa zasobow systemowych jako standardowa aplikacje na OS. Aplikacja jest zrobiona pod konkretnego OS z c#/java jest inaczej, na poczatku sa uruchamiane za pomoca byte-codu i ten byte code jest jedyna rzecza ktora jest natywna. wtedy aplikacja napisana przez nas, jest przekazywana do runtime. Runtime tlumaczy kod na maszynowy i go wykonuje. To jest ogolna idea, ale jest tego troszke wiecej. Kazdy zasob systemowy jest wrappowany przez runtime sybsystem. Wiec kazde wywolanie bedzie liczba "wrapow" dla tych sub-calls W c/c++ mozemy powiedziec ze "ta linia jest tlumaczona na N op-codes" w c# java mozemy powiedziec podobnie ze ta linia zostala przetlumaczona na M op-codow. ALE zawsze M>N Niektorzy moga powiedziec "to nie jest prawda daj mi algorytm i zrobie M=N". Zgadza sie, nawet moga byc to te same algorytmy, ale jezeli chodzi o prawdziwe aplikacje jak np gra napisana w openGL wtedy to nie jest mozliwe chocby ze wzgledu na architekture Ponownie mozecie powiedziec "to nie prawda" ale przy nastepnej optymalizacji znajdzie sie asm, c++ itd i juz nie bedzie to dluzej natywny kod c#/java No i kolejna rzecz. Bezpieczenstwo. W roznych frameworkach pojawiaja sie bugi calkiem czesto. Jezeli ktos znajdzie takie cos, wszystki applikacje sa narazone. I co mozesz zrobic? Tak na prawde nic, z wyjatkiem modlenia sie. Bugi w c++ w runtime oczywiste tez sa mozliwe, ale sa bardzo rzadkie i mozesz napisac dla nich "workaround" dla prawie kazdego przypadku. No i bugi w c++ sa raczej naprawiane super wczesnie - poniewaz ten bug zapewne tez bedzie w danym OS
0

@fasadin

  1. Może i M>N ale generalnie niekoniecznie jest to wielka różnica.
  2. Nie rozumiem fragmentu o frameworkach. Porównujesz frameworki z gołym językiem. Przeciez we frameworkach C++ też mogą być bugi i to jeszcze groźniejsze bo java mimo wszystko działa w pewnym sandboxie maszyny wirtualnej.
3

W c/c++ mozemy powiedziec ze "ta linia jest tlumaczona na N op-codes" w c# java mozemy powiedziec podobnie ze ta linia zostala przetlumaczona na M op-codow. ALE zawsze M>N

To jest akurat w ogólności nieprawda. Może być zarówno M < N, M == N jak i M > N. Wszystko zależy od tego, jakie optymalizacje wykona kompilator. A do tego więcej opcodów nie zawsze oznacza wolniejsze wykonanie, bo procesory robią pod spodem kolejną warstwę optymalizacji (np. zmieniają kolejność wykonania), więc wnioskowanie na tej podstawie o wydajności nie jest słuszne.

Bezpieczenstwo. W roznych frameworkach pojawiaja sie bugi calkiem czesto. Jezeli ktos znajdzie takie cos, wszystki applikacje sa narazone. I co mozesz zrobic? Tak na prawde nic, z wyjatkiem modlenia sie. Bugi w c++ w runtime oczywiste tez sa mozliwe, ale sa bardzo rzadkie i mozesz napisac dla nich "workaround" dla prawie kazdego przypadku. No i bugi w c++ sa raczej naprawiane super wczesnie - poniewaz ten bug zapewne tez bedzie w danym OS

No, tak bo w takim Qt, gcc czy przeglądarce internetowej to nie ma setek niepoprawionych bugów.
Język w którym każdy kawałek kodu może nadpisać dowolny fragment swojej pamięci nazywasz bezpiecznym.... ROTFL.

Jak musz buga w C++, to faktycznie możesz się tylko modlić aby nie rozwalił sterty i nie zakończył się crashem. W Javie łapiesz wyjątek i jedziesz dalej.

Odpowiadając na oryginalne pytanie:

  1. GC raczej nie jest już problemem. CMS odśmieca współbieżnie i jak się jest ostrożnym można spokojnie zejść z do poziomu pojedynczych milisekund raz na jakiś długi czas. Jak komuś CMS nie daje rady, to można próbować z G1, ale moje doświadczenia są lepsze z CMS. Gry używają bardzo dużo pamięci na zasoby graficzne. Można je alokować poza stertą, nie obciążając GC.

  2. Nadal problemem jest optymalizowanie struktur danych aby były cache-friendly. Java nie ma value-types tak jak ma C#. To powoduje, że trudno zrobić wydajną reprezentację punktów (np. Point3D). Ale z tego co wiem, to pracują nad tym i w niedalekiej przyszłości ten brak zostanie naprawiony (raczej nie w Java 9, a później, ale zostało to już ogłoszone).

  3. Większość dobrych silników została napisana w C/C++, więc z przyczyn historycznych, zaawansowane gry pisze się w tym, co jest wymagane przez silnik.

  4. Przykład Minecrafta pokazuje, że gracze wcale nie są tak bardzo wrażliwi na okazjonalne lagi i przycięcia, jak to niektórzy sugerują. Jak gra jest dobra, to zgubienie jednej klatki raz na 5 minut nie jest jakimś wielkim problemem.

i java zjada mniej pamięci niż C++. Bo w C++ nie ma garbage collector

W pewnym sensie jest to prawda. Tzn. licząc tylko samą stertę, system z GC jest znacznie bardziej przewidywalny pamięciowo niż system z alokacją dynamiczną malloc/free. W przypadku ręcznej alokacji tak jak w C++, tak naprawdę nie masz żadnej gwarancji, że rozmiar sterty jest tylko nieznacznie większy od rozmiaru pamięci faktycznie zaalokowanej. Pogooglaj o problemie fragmentacji pamięci. Typowo fragmentacja nie jest dużym problemem, ale w skrajnym przypadku może się zdarzyć, że Twój program będzie zżerał w danej chwili 100x więcej pamięci niż faktycznie jest zaalokowane. Natomiast w przypadku GC - TY jako programista aplikacji możesz na sztywno wbić jaki chcesz mieć ten współczynnik (całkowity rozmiar sterty / używana pamięć) i w dobrze napisanej aplikacji 1.5x zapewnia zwykle bardzo dobrą wydajność i małą aktywność GC. Jak dasz za mało miejsca na GC, to najwyżej się w którymś momencie przytnie. W C++, gdybyś tak zrobił (hmm... da sie - wystarczy odpowiedni ulimit), dostajesz wyjątkiem, a raczej wywaleniem programu, no bo kto łapie wyjątki rzucane przez new?

Inna sprawa, że systemy z GC mają większą przepustowość (w sensie allocation/deallocation rate) niż systemy z ręczną alokacją. Swoją drogą systemy baz danych od dawna stosują coś w rodzaju GC, właśnie aby zwiększyć wydajność. Jak robisz DELETE w SQLu, to dane nie są usuwane natychmiast, tylko doppiero podczas GC (w Postgresie zwanego "vacuum" a w Cassandrze "compaction").

1

Dochodzi jeszcze jedna rzecz - wiedza i nauka. Jesli jest zespol gosci ktorzy od 10 lat wymataja robiac gry w C/C++ i maja w malym paluszku cala potrzebna im wiedze, to przejscie na nowa technologie zajmie sporo czasu (prototyp czy alfe zrobia szybko, ale potem na walce ze szczaegolami i drobiazgami mnostwo zejdzie).

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0