• Mitspieler gesucht? Du willst dich locker mit der Community austauschen? Schau gerne auf unserem ComputerBase Discord vorbei!

Leserartikel Generelle Performance in Spielen

Forenaffe

Ensign
Registriert
Juli 2011
Beiträge
199
Hi,

dieser Thread soll Metainformation zu Performance-Problemen in Spielen zusammenfassen.
Über weitere Tips, Erfahrungen und generelle Diskussion zum Thema würde ich mich freuen, eventuell können wir einen umfassenden FAQ Thread produzieren (in bunt mit Links und Bildchen).


Ich bin bei mehreren aktuellen Titeln mit etwas Nachforschung via Task-Manager, GPU-Z und googeln nach Spielengines auf ganz einfache Probleme gestoßen die vieles erklären.


tl;dr Version:

1) Multithreading.
Gut machen kostet viel Entwicklungszeit, kaum Entwickler machens, CPU limitiert dann obwohl noch 2-4 physische Kerne ungenutzt sind und die Grafikkarte grad lauwarm ist.
Leistung pro Thread und damit CPU Kern ist meist (!) wichtiger als alles andere.
Workaround: Einzelne Kerne massiv übertakten, Intel > AMD bei Leistung pro Kern (Thread).
2) Daten nachladen von Festplatten.
32bit Software nimmt nur 2GB RAM. Relevante Daten im RAM halten/buffern ist schlecht programmiert.
Workaround: SSD oder RAMDisk für Spiele verwenden.
3) Grafikkarten-RAM
Viel hilft viel. Wenn der GDDR überläuft bricht die Framerate ein, egal welche Hardware.
Workaround: Post-Processing und hochauflösende Texturen runterschrauben



Ausführliche Version:

1) Multithreading

Grobe Grundlagen zur Abarbeitung von Programmen in Windows

Prozesse:
Jedes Programm startet einen Prozess - Windows weist dem Programm für die Abarbeitung Arbeitsspeicher zu, schaufelt Informationen von Eingabegeräten in das Programm rein (Mausklicks,Tastatur,...) und den output raus (Bild am Monitor, Sound, Daten über Netzwerkverbindung,...).
Windows kümmert sich darum dass das Programm von den zur Verfügung stehenden CPU Cycles (bei 3 Ghz sind z.B. 3 Mrd. Berechnungen pro Sekunde möglich) ausreichend viele abkriegt. usw usw.
All das sehen wir als Prozess im Task-Manager. Übrigens ist auch der Windows Desktop nur ein Prozess der sich jederzeit beenden und wieder starten lässt ;).

Threads:
Ein Thread ist ein Teil der Berechnungen innerhalb eines Prozesses. Ein Prozess muss mindestens einen Thread, kann aber theoretisch beliebig viele haben.

Multitasking:
Frühere Betriebssysteme konnten nur einen Prozess gleichzeitig abarbeiten, seit z.B. Windows 3 können Prozesse "parallel" laufen. Die Anführungsstriche hier, da auf einem CPU Kern nie etwas wirklich parallel laufen kann. Nur nacheinander.
Multitasking heisst lediglich dass sich das Betriebssystem darum kümmert die einzelnen Tasks so nacheinander durch die CPU zu stopfen, dass aus Nutzerperspektive alles gleichzeitig passiert (verschoben um ein paar micro- oder nanosekunden).
Bis vor einigen Jahren war die Devise bei Desktop CPUs vor allem MEHR TAKT (abgesehen von besseren Instruktionssets usw.). Mehr Berechnungen pro Sekunde = schneller. Dann kamen CPUs mit mehreren Kernen.

Multithreading:
Durch aufteilen eines Prozesses in mehrere Threads, die tatsächlich parallel abgearbeitet werden können, ergibt sich eine enorme Leistungssteierung. Im Idealfall hat man entsprechend der Anzahl zusätzlicher Kerne das x-Fache an Leistung.

Beispiel: Zwei Prozessteile eines ego-shooters gleichzeitig auf zwei CPU-Kernen: Kern 1 rechnet KI-Wegfindungsroutinen durch während Kern 2 sich um die Berechnungen der Grafik zusammen mit der dedizierten Grafikkarte kümmert. Sind beide fertig wird das Ganze zusammengelegt, ein Frame berechnet und geht raus an den Monitor.
Stünde nur ein CPU-Kern zur Verfügung müssten beide Berechnungen auf eben diesem nacheinander stattfinden, im Extremfall kommen dabei nur halb soviel fps raus.

Auslastung der CPU:
Windows zeigt die Auslastung der CPU aus seiner Sicht an, das heisst auch wenn grad mal für die nächste Million Berechnungen in der CPU nix passieren kann weil diese auf Daten aus dem RAM wartet, ist das für Windows Auslastung 100%. Das Betriebssystem wartet darauf dass die CPU fertig wird mit den Instruktionen.
Das ist bei der Suche nach Performance-Flaschenhälsen durchaus interessant falls der RAM zu lahm ist.


Dass Leistungshungrige Software 5 Jahre nach Etablierung von Multi-Cores am Massenmarkt immer noch mit schlechtem Threading ausgeliefert wird ist eine absolute Schweinerei seitens der Publisher/Entwickler - erstere bieten in der Regel auch nur sehr schlechten First-Level Support der auf technische Fragen nicht eingeht oder eingehen kann.
Da wir aber damit momentan Leben müssen dieser Thread.

Workaround:
CPUs mit viel Leistung pro Kern. Da ist nunmal Intel weit vorne und führt nichts dran vorbei - alternativ vorhandene CPU übertakten.
Im Windows Task-Manager lassen sich einzelne Prozesse auf ausgewählte CPU-Kerne beschränken. Macht man das und taktet explizit diese hoch, kann man per Hand tun wofür Marketing den Begriff "Turbo" etabliert hat. Jedoch schiebt Windows ab Werk mit eigenem Scheduler die Threads/Prozesse von Kern zu Kern, was nicht auffällt im Desktop Betrieb. Beim Zocken aber schon wenn gleichzeitig die dynamische Taktung einzelner Kerne reinpfuscht.
Oder einfach alle Kerne übertakten und Stromsparfunktionen ausschalten.



2) Nachladen von Daten vom Plattenspeicher

Die Hierarchie beim Laden von Daten in die CPU, zwecks Berechnungen, sieht aus wie folgt:

Register der CPU <- L1-Cache <- L2-Cache <- L3-Cache <- RAM (<- Cache der Festplatte) <- Dateien auf der Festplatte

Das ist von verdammt schnell (links) zu verhältnismäßig lahm (rechts)

Nahezu alle Spiele werden als 32bit Software gecodet. Das hat zur Folge dass sie nicht mehr als 2GB Speicher adressieren können (könnten 4, wird aber seltenst gemacht).
Deshalb wundert man sich auch mit 16GB RAM über Spiele die nur 1,5GB nutzen.
Blöderweise müssen die aber dann Daten von der Festplatte nachladen - und das ständig. Da der ganze Prozess dann auf diese Daten warten muss bricht die Framerate ein - alle paar Sekunden 100ms Zugriffs+Lesezeit durch Festplatte bedeuten 0,1 Sekunden hängendes Bild.

Workaround:
Festplatte durch SSD ersetzen - unterstützt Spiele die ständig kleine Dateien nachladen massiv. Bei Lord of the Rings Online hängt die Gesamte Berechnung des nächsten Frames beim nachladen von der Platte - das Spiel Rage hingegen lädt riesige Texturen von dort nach, läuft dabei aber weiter - man sieht bei schnellen Mausbewegungen aber ganz deutlich eine Vorzögerung beim erscheinen der hochauflösenden Texturen.
Je nach Empfindlichkeit des Nutzers reicht schon eine günstige SSD um dieses Problem zu umgehen.
Wer ausreichend RAM zur Verfügung und auch mit einer SSD noch merkbare Mikroruckler hat installiert das Spiel auf eine virtuelle Festplatte im Arbeitsspeicher, die wohl schnellste Methode auf größere Datenmengen zuzugreifen.

3) RAM der Grafikkarte (GDDR)

On-board Grafiklösungen nutzen bis auf wenige Ausnahmen mit eigenem kleinen Speicher (auch on-board), ebenso wie die aktuellen in CPU integrierten Grafikeinheiten von Intel und AMD, den RAM der CPU mit.
Dedizierte Grafikkarten haben ihren eigenen Speicher der sich, abgesehen von kleinen technischen Unterschieden, vor allem in Taktrate, Spannung und Bandbreite vom DDR SDRAM der CPU unterscheidet. Auch muss die GPU nicht erst noch durch den Speichercontroller der CPU durch um auf deren (ohnehin langsameren) Speicher zuzugreifen.
Wichtig wird die Speicherauslastung der Grafikkarte wenn der GDDR überläuft - dann wird in den SDRAM ausgelagert. Die Performance bricht sofort ein da für jeden Frame langsamerer Speicher mit reinpfuscht.
Die beiden größten Speicherfresser sind hochauflösende Texturen und Post-Processing (v.A. Anti-Aliasing).

z.B. in Serious Sam 3:
3630MB GDDR, 29FPS: FSAA, MSAA und FAAA maximum, Texturen höchste Auflösung Link
1510MB GDDR, 45FPS: FSAA, MSAA und FAAA aus, Texturen höchste Auflösung Link
781MB GDDR, 45FPS: FSAA, MSAA und FAAA aus, Texturauflösung minimum Link

Die verwendete Radeon HD6990 hat 2GB eigenen Speicher, alles darüber hinaus wurde ausgelagert in den der CPU (GPU-Z stellt das in dieser Version nicht richtig dar, Memory Usage (Dynamic) ist eigentlich die Auslagerung, sind im ersten Beispiel dann gute 1600MB).

Workaround:
Mit z.B. MSI Afterburner kann man ein Overlay einblenden das Informationen über FPS, GPU-Last, und GDDR-Nutzung zeigt. Dann heissts je nach Spiel und vorhandenen Optionen ausprobieren - Texturen mit hoher Auflösung und Post-Processing sind die größten Speicherfresser. Bei den verschiedenen Arten von Anti-Aliasing bieten Grafikkartentreiber die Möglichkeit festzulegen was verwendet wird, falls die Einstellungen innerhalb der Anwendung nicht vorhanden sind (von Konsole portierte Spiele lassen grüßen).


Einige aktuelle (negativ-)Beispiele aus eigener Erfahrung:

Star Wars: The Old Republic (1 Thread)
Bioware lizensiert die HeroEngine und baut darauf schweineteuer ein Spiel. Leider ist die Engine single-threaded, gab dazu auch einen Post im Dev-Forum der dies bestätigt hat.

RIFT (2 Threads)
Gamebryo Engine (Fallout 3), 1 Thread berechnet das gesamte Spiel, der zweite den Netzwerkverkehr

Lord of the Rings online (1 Thread)
Lädt ständig von Festplatte nach, daher Mikroruckler (allein schon die Blinkerei der HDD-LED am Gehäuse verrät das)

The Secret World (2 Threads)
Basiert auf der Engine von Age of Conan, Performanceprobleme sind die selben geblieben

World of Warcraft (2 Threads)

Battlefield 3 (Mindestens 6 Threads)
Nutzt z.B. volle Performance eines Phenom II x6, hier wurde von den Entwicklern vernünftig gearbeitet.
 
Zuletzt bearbeitet:
Interessante Übersicht.

Relevant wären auch noch ein paar Sätze zu den dedizierten Karten. Ich habe das Gefühl, dass sich die Spiele
seit ein paar Jahren immer mehr von der CPU wegbewegen und man teils modernste Spiele mit einem
Athlon II X2 (vielleicht etwas mehr) und einer 680 flüssig auf hohen Details spielen kann.

Ab welchen Auflösungen kann man beispielsweise den VRAM für niedrige fps verantwortlich machen? Welche
Spiele profitieren von mehr VRAM? Welche Spiele (irgendwie muss ich da immer an GTA denken) brauchen
trotz allem eine starke CPU? Gibt es ein allgemeines Muster, nach dem sich vorhersagen lässt, ob eine AMD-
oder eine nVidia-Karte in einem Spiel stärker ist (etwa anhand der Features, die die Engine benutzt)? Schließlich
gibt es Spiele, in denen AMD-Karten "eigentlich stärkere" nVidia-Karten weit hinter sich lassen und umgekehrt.
Wann macht ein SLI/CF als Problemlösung überhaupt Sinn?

PS: bin ich eigentlich der einzige, dem Oblivion extrem HDD- und RAM-abhängig vorkam? Stichwort Laderuckler...
 
Schöne Ausarbeitung. Sehr interessant zusammen gefasst. Gut nachvollziehbar.
 
Forenaffe schrieb:
1) Multithreading.
Gut machen kostet viel Entwicklungszeit, kaum Entwickler machens, CPU limitiert dann obwohl noch 2-4 physische Kerne ungenutzt sind und die Grafikkarte grad lauwarm ist.
Der Grund, warum das kaum gemacht wird, ist relativ einfach gesagt:
1) Inter-Thread-Kommunikation ist recht schwierig zu bewerkstelligen, insbesondere wenn mehrere Threads auf nur einen Speicherbereich zugreifen sollen. Es gibt zwar mehrere Techniken, wie das vonstatten gehen kann, aber im Prinzip endet es immer damit, dass ein Thread auf den anderen wartet. Im schlimmsten Fall liest a) ein Thread dann das falsche aus dem Speicher oder b) es kommt zum Deadlock.

a) kommt daher, dass zB ein Thread einen Zeiger auf einen Speicher stellt, denn es lesen will. Dann wird plötzlich ein anderer Thread aktiv und verstellt den Zeiger wieder, weil er was anderes lesen. Kommt nun der erste Thread wieder ran, liest er von der falschen Stelle im Speicher etwas aus. -> Problem!

b) ist recht einfach erklärt. Um a) zu vermeiden, warten die Threads darauf, dass ein anderer Thread mit dem Lesen im Speicher fertig ist. Durch zB einen Programmfehler kann es dann dazu kommen, dass die anderen Threads nie informiert werden, dass sie jetzt auch mal ran dürfen. Sie warten sich also sozusagen zu Tode. Das Problem ist, so etwas ist schwer zu entdecken und zu debuggen.

2) Die meisten Programme sind nach dem Wie-Prinzip aufgebaut: Also wie kommt man vom Problem zur Lösung. Daher wird dann eine sequentielle (= nacheinander folgende) Abfolge von Befehlen definiert, dass die Problemlösung darstellt (= Algorithmus). An diesem Punkt ist es schwer einen Nebenläufigkeit (mehrere Threads) in den Algorithmus einzubringen. Viele Berechnungen hängen voneinander ab, können also nur nacheinander abgearbeitet werden. Deshalb wird man nie alles parallelisieren können.
Außerdem kann Nebenläufigkeit auch logische Probleme aufwerfen. Die Threads haben naturgemäß einen unterschiedlichen Informationsstand über die Umwelt (bspw. Spielwelt). D. h. sie werden zwangsweise fehlerhaft reagieren, zB schießt ein Gegner auf einen anderen, der aber gar nicht mehr da ist.


Es ist schwer, das alles fehlerfrei zu bewerkstelligen. Meist erfordert das zusätzlichen Entwicklungsaufwand, den sich kaum ein Studio leisten kann. Mal eben so drei Monate (ausgedachte Zahl) mehr Entwicklungszeit kostet viel Geld! Lass es 30 Programmierer sein, die kosten in dem Zeitraum mal schnell 300.000€. Geld, das nicht unbedingt wieder reinkommt. Multithreading lässt sich nur schwer (eigentlich gar nicht) dem durchschnittlichen Spieler als Kaufgrund vermitteln.
 
Zuletzt bearbeitet:
"
Beispiel: Zwei Prozessteile eines ego-shooters gleichzeitig auf zwei CPU-Kernen: Kern 1 rechnet KI-Wegfindungsroutinen durch während Kern 2 sich um die Berechnungen der Grafik zusammen mit der dedizierten Grafikkarte kümmert. Sind beide fertig wird das Ganze zusammengelegt, ein Frame berechnet und geht raus an den Monitor.
Stünde nur ein CPU-Kern zur Verfügung müssten beide Berechnungen auf eben diesem nacheinander stattfinden, im Extremfall kommen dabei nur halb soviel fps raus.
"

Schlecht gewähltes Beispiel. Die beiden Threads laufen in Spielen weitestgehend entkoppelt ab, jeder mit seiner eigenen FPS. "Zusammengelegt" werden muss da auch nichts. Erster Thread läuft mit einer konstant hohen und hohen FrameRate, während letzterer Thread in einer niedrigen und so schnell wie möglichen Framerate läuft, welche der Bildwiederholrate entspricht, die man in Benchmarks sieht. Würde man nun beides auf einen Kern legen, so könnte bei "ungünstigen" Scheduling einer der Threads "verhungern" und man hätte im Extremfall nur einen Bruchteil der Performance. Ein Beispiel dazu; Threadingmodell sei Round Robin mit 100 Zeiteinheiten:
0. Zeiteinheit: Graphikthread sendet Zeichenbefehl an Graka und wartet auf die Vollendung des Befehls. Da es eine potentiell lang dauerende Operation ist legt er sich schlafen.
1. Zeiteinheit: Logikthread fängt mit dem Rechnen an
10. Zeiteinheit: Graphikkarte ist fertig, wodurch Graphikthread wieder bereit wäre weiterzurechnen.
101. Zeiteinheit: Logikthread ist mit seiner Rechnzeit am Ende und legt sich schlafen.
102. Zeiteinheit Graphikthread sendet nächsten Zeichenbefehl an Graka und legt sich wieder schlafen
103. Zeiteinheit: Logikthread fängt wieder mit dem Rechnen an
105. Zeiteinheit: Graka ist fertig mit dem Zeichnen .....
........


In diesem Beispiel würde der Graphikthread verhungern, weshalb die Auslastung der Graphikkarte sehr sehr gering und die Framerate entsprechend sehr sehr niedrig. Deshalb, und wegen diversen anderen Effekten wie Kosten für Threadwechsel oder Cache Misse bei Threadwechsel, kann man nicht sagen, dass eine Halbierung der Kernzahl auch im Extremfall die Framerate halbiert. Das Problem des Verhungerns ist allerdings nicht nur auf obiges Problembeispiel begrenzt sondern ein sehr generelles Problem bei Multithreading. Zusätzlich ist es abhängig von der Hardware, wie zB der Prozessoerkernzahl, als auch von der vom OS zur verfügung gestellten Threading bzw Scheduling Implementierung.
Beides sind potentielle Performancerisiken beim beim Multithreading, welche man beachten bzw testen und optimieren muss, was es den Entwicklern sehr sehr erschweren kann in ihre Anwendung Multihreading einzubauen.

b) ist recht einfach erklärt. Um a) zu vermeiden, warten die Threads darauf, dass ein anderer Thread mit dem Lesen im Speicher fertig ist. Durch zB einen Programmfehler kann es dann dazu kommen, dass die anderen Threads nie informiert werden, dass sie jetzt auch mal ran dürfen. Sie warten sich also sozusagen zu Tode. Das Problem ist, so etwas ist schwer zu entdecken und zu debuggen.

Wäre denke ich etwas besser mit folgendem erklärt:
In einer Gruppe von Threads wollen mehrere Threads eine Resource für sich beanspruchen, die ein anderer Thread gerade besitzt. Damit ein Thread nun eine Resource bekommen könnte müsste ein anderer Thread seine Resource freigeben. Dies kann der andere Thread aber nicht tun, weil er ebenfalls eine bereits belegte Resource zum Abschluss seiner Rechentätigkeit benötigt.
Dadurch wartet jeder dieser Threads für immer auf einen anderen Thread und keiner kann jemals mehr weiterrechnen.
Diese Situation beschreibt man im allgemeinen als Deadlock.
 
Zuletzt bearbeitet:
Update: Bischen was geändert und für Leserlichkeit dezent formatiert
Text über Graka RAM hinzugefügt

Habt ihr ein gutes Beispiel für multithreading in Spielen ?
Ich hab mir dafür nur was aus den Fingern gesaugt das anschaulich sein sollte ;)
Die posts von e-Laurin und Nai füg ich noch ein
 
Zurück
Oben