Was macht TF Intern und warum es ruckelt

Willkommen in der Transport Fever Community

Wir begrüßen euch in der Fan-Community zu den Spielen Transport Fever und Train Fever, den Wirtschaftssimulatoren von Urban Games. Die Community steht euch kostenlos zur Verfügung damit ihr euch über das Spiel austauschen und informieren könnt. Wir pflegen hier einen freundlichen und sachlichen Umgang untereinander und unser Team steht euch in allen Fragen gerne beiseite.

 

Die Registrierung und Nutzung ist selbstverständlich kostenlos.

 

Wir wünschen euch viel Spaß und hoffen auf rege Beteiligung.

Das Team der Transport-Fever Community


  • Hallo


    Kurz und knapp:

    • Deine Grafikkarte hat zu wenig Grafiksspeicher. -> Versuche es mit weniger Texturqualität. TF fängt an zu Swapen und lädt die Texturen von der Festplatte.
    • TF benutzt zur Hauptberechnung höchstens zwei Threads (zwei CPU Kerne, Version 4831), ein Kern kann einfach überfordert sein.


    Eine Analyse zu dem CPU Kern Problem
    Eine Teil-Code Analyse mit Linux Debug Daten, Windows Version 4831 und Beobachtung


    TF macht im Endeffekt bei einem DayChange:


    BuildingSimulator hat mehrere Felder mit Informationen, diese sind mir unbekannt.
    Die Informationen in den Feldern ergibt einen Gesamt-Status A, B, C des Train Fever Haupt-Threads.


    Im Programcode von BuildingSimulator::DayChange


    Ist der Gesamt-Status A:


    BuildingSimulator::PrepareThreadData
    Mache einen Clone der Engine Teile, vom Transport Netzwerk, Stationen, Linien usw.


    Starte einen neuen Thread für die Berechnung (mit den Clone Daten)
    -> TF nutzt nun 2 Kerne für das Spiel und berechnet die Häuser im Hintergrund (wohl auch die Einwohner)


    Ist der Gesamt-Status B (Monatsende?):
    Benutze std::thread::join -> Warte bis Thread 2 fertig mit der Ausführung (Berechnung) ist.
    BuildingSimulation::ProcessResult -> Verarbeite die errechneten Daten
    TownDeveloper::Run(BuildingSimulation const*)
    IndustryDeveloper::Run(BuildingSimulation const*)
    und dann werden die Daten wohl weggeworfen.


    Gesamt-Status C:
    Mach hier gar nichts, der zweite Thread kann gerade im Hintergrund am laufen sein.



    Zusammenfassung:
    BuildingSimulation::ProcessResult in "Gesamt-Status B" scheint die Berechnung des Thread2 zu nutzen um die Daten im Hauptthread zu ändern.
    Umso größer das Transport Netzwerk wird, um so langsamer wird Thread2 da es mehr Daten berechnen muss.


    Wenn es gut läuft ist Thread2 schneller fertig in einem Monat als Thread 1. Thread 1 kann die Daten schnell genug im Status B verarbeiten.
    Resultat: Spiel läuft flüssig.


    Dann gibt es da noch Varianten wenn es hängt:
    a) Thread 2 ist zu langsam auf dem CPU Kern, Thread 1 muss warten.
    b) Thread 2 ist schnell genug (zwei Kerne werden genutzt, wenn Thread 2 fertig ist, nur noch ein CPU Kern von Thread 1)
    Das Zusammenfassen der Daten in Thread 1(Nur ein CPU) braucht zu lange.
    c) Thread 2 ist zu langsam (zwei Kerne werden aber genutzt) dann ist das Zusammenfassen der Daten zu langsam (ein CPU Kern wird genutzt)


    d) GPU / OpenGL Limitierung. Thread 1 hat schon zu viel Zeit verbraten im Frame um Daten zu Grafikkarte zu schicken. Das Mergen im Thread 1 hat keine Rest-Zeit mehr übrig.
    Hier hilft die GPU Einstellungen runter zu schrauben.


    Vielleicht versteht Ihr nun warum es keine einfache Lösung gibt und es mehrere Probleme bzw. Lösungsansätze gibt:
    - CPU Leistung bei einem Thread ist zu wenig.
    - GPU Leistung zu wenig, schaltet die Grafikeinstellungen runter, zum Beispiel Texturqualität.



    PS: Da TF sehr viel mit Hashes/Malloc und Locks arbeitet, gibt es da noch CPU cache misses, mutex/lock congestion, memory fragmentation (sehr schönes Beispiel alte Firefox Versionen). Also nicht gerade ein einfache Thema.


    Ja. TF hat mehr als zwei Threads offen. Dies kann man zum Beispiel mit Sysinternals ProcessExplorer sehen. Unter anderem für Steam, Soundausgabe, Grafikkartentreiber, ist hierbei um das Thema einfacher zu halten nicht berücksichtigt.


    Ich benutzte zur Analyse die erste öffentliche Linux Version für Funktionsnamen (Debugdaten) + Windows Version 4831

  • Urban Games wird dieses wohl mit Patches verbessern....


    Bitte bedenkt, nicht jede Berechnung ist gut zu parallelisieren. In der Kryptographie wird gerade dieses Umstand genutzt, damit die Daten relativ sicher sind.

    Ein einfach Beispiel:
    Zwei Threads berechnen, das Ergebnis ist ein Haus auf der Karte zu ersetzen.
    Thread 1: Eine neue Industrie
    Thread 2: Ein neues Geschäftsgebäude


    Erstmal kann man nicht beides machen, also gibt es ein Lock, damit es keinen Fehler gibt bzw. die Berechnung eines Threads hätte man sich direkt sparen können.
    Das Erstellen der Verwaltungsdaten bedeutet vielleicht mehr Arbeit (Threads aufbauen / Daten synchronisieren) als einfach nur einen Thread anzusetzen.


    Jetzt stell dir mal vor, du errechnest welches Signal Grün zeigen soll. Zwei Züge auf die selbe Strecke zu schicken ist keine gute Idee...

  • @eis_os Dein erster Beitrag ist für mich nicht ganz verständlich, daher frage ich einfach mal nach:


    Meinst Du, dass ein Thread permanent für den Städtebau (also konkret zB: neue Häuser) da ist und dieser bei Dir "B" heißt?


    Ich programmiere zwar keine Spiele oder Hardcore-Thread-Programme und weiß, dass auch vermeintlich einfache Dinge wie "Fülle eine Liste im Hintergrund so, dass der User die Listelement ohne Fehler bereits anklicken kann" auch nicht ganz so schnell funktionieren, ABER falls ich es richtig verstanden habe ergibt sich schon einiges an Potenzial. Da hat UG noch einiges zu tun, falls sie es tun wollen...


    Ich hoffe ja inständig, dass Dein Beispiel nicht auch noch Realität ist, denn es wäre echt Verschwendung, wenn man eine Aufteilung "Haus/Industrie" machen würde und danach merkt "Oh die Teilen sich ja einen Bauplatz..".
    Besser wäre eine Aufteilung wie zB "jede Stadt für sich".
    Schade, wenn das Spiel nicht aktuell schon im Hintergrund einen Thread per Stadt macht und diesen zB. abbricht und neu startet, wenn der User was ändert. Dazu müsste man doch wohl auch nicht eine (komplette?) Kopie ziehen, sondern nur das Kopieren bzw. "übergeben" was benötigt wird.
    Und schade, dass es aktuell so scheint, dass es feste und lange Zeiten gibt, in denen die KI die Städte baut.
    Aber vielleicht denke ich da doch etwas zu vorschnell.


    Naja kleines Team halt. Umso mehr ärgert es mich, dass größere Studios Spiele wie SimCity rausbringen, ohne jeden Sim zu simulieren.

    Einmal editiert, zuletzt von Hunter ()

  • Nein das mit den Häusern war ein Beispiel, warum man zwar vieles parallel berechnen könnte, es aber eben doch nicht so einfach ist. Es gibt Abhängigkeiten.
    TF macht keine doppelten Berechnungen.


    Thread 2 berechnet vor sich hin.
    Thread 1, der Hauptthread wartet am Monatsende bis Thread 2 fertig ist um die Daten dann zu verarbeiten bzw, anzuwenden. (Status B)


    Von denn Eingabe Daten zu Thread 2 gehe ich davon aus. das die Personen / Häuser Beziehungen, bzw. deren Verkehrsverbindungen berechnet werden.


    TF macht diesen Thread jeden Monat neu auf. (Kann man mit ProcessExplorer ganz gut verfolgen und deckt sich mit Code und Beobachtungen).
    Wie weit das parallelisiert werden kann ist die Frage. Ob die Aufteilung in Städte Sinn macht, ich würde sagen das es keinen Nutzen bringt. Da die Passagiere/Güter eben nicht auf Städte begrenzt sind, kann eine naive Variante - einfach in x Teile aufbrechen - trotzdem die schnellere sein.


    Besseren Malloc Code um die Cache-Lokalität zu erhöhen bzw. ein paar unkonventionelle Ideen könnten bestimmt besser sein und man sollte da mehrgleisig fahren.


    Wenn ich denn Quellcode hätte. würde ich da ein mal ein paar Ideen durchspielen, so kann ich nur etwas an der Schale kratzen.

  • Es wird wohl viele Beziehungen geben, ja.


    Es müsste aber z.B: nicht jeden Monat neu gebaut werden und dies stur abgewartet werden, dafür dann evtl. mal nur jeden 2. und dafür dann doppelt so viel.

  • Testaufbau:
    Spiel mit Monats-Abschlussproblemen im Jahr 2228, Volle Geschwindigkeit, 3 Monate gewartet nach Änderungen. ProcessExplorer um die Thread 2 Erstellung und Mergen zu sehen.

    Test
    Zum Testen in einem Spiel alle Fahrzeuge verkauft, danach läuft der Monatsabschluss, da Thread 2 schneller fertig wird.
    Thread 2 braucht dann im Test nur ca. 24 Ingame Tage für seine Berechnung.


    Alle Linen entfernen (waren mehr als 60) resultiert in ca. 22 Tage, also innerhalb der Messtoleranzen...


    Entfernen von ca. 50% aller gebauten Teile wie Schienen Brücken Tunnel und Haltestellen hat nichts geändert.
    Straßen-Schnellverbindung zwischen den Städten hat auch keinen Signifikanten Einfluss, entfernen von Busspuren auch nicht.


    Probleme mit Signalen, Zügen oder Stationen und oder Schienen die das Wachstum beeinflussen kann man auch eher ausschließen, zumindest gab es an den Stellen keine mehr neuen Häuser.


    Für die Stadtberechnung, Einwohner Berechnung braucht TF würde ich sagen zu viel Zeit.


  • Für die Stadtberechnung, Einwohner Berechnung braucht TF würde ich sagen zu viel Zeit.


    Hätte ich jetzt gar nicht erwartet. Könntest Du auch bitte noch testen wie es ist, wenn die KI nur innerhalb der Stadt arbeiten muss:
    Also alle Verbindungen (Straßen/Schienen) kappen?

  • Das kann man im Spiel selbst auch recht gut beobachten.
    Wenn ich direkt nach dem laden fix auf einen Bahnhof zoome, dann läuft es ziemlich flüssig.
    Aber jedesmal wenn ein voller Zug ankommt und 100 Leute aus dem Bahnhof strömen, sinkt die Leistung merklich ab.

    Ryzen 3600, RX 6700XT, 32 GB RAM

  • Schöner einblick in die Architektur von TF. Deckt sich auch mit meiner Beobachtung, dass bei unterschiedlichen Spielgeschwindigkeiten das Lag sich am Monatsende zeitlich verändert (je schneller die spielgeschwindigkeit, desto länger darf man auf Thread2 warten).


    d) GPU / OpenGL Limitierung. Thread 1 hat schon zu viel Zeit verbraten im Frame um Daten zu Grafikkarte zu schicken. Das Mergen im Thread 1 hat keine Rest-Zeit mehr übrig.
    Hier hilft die GPU Einstellungen runter zu schrauben.


    Hierzu noch eine frage: Wäre dieser Fall nicht eher zufälliger Natur, da gerade das std::thread::join den Thread1 ans limit bringen würde? Sprich, würde man bei einer weiteren Stadtentwicklung dann nicht schon vor Monatsende ein ruckeln feststellen können?


    Ich hoffe es gelingt UG die simulation zu optimieren. Es sollten auch die gewünschten Systemkomponenten vom Spiel schon selbst genutzt werden, z.B. automatisches Erkennen/Verwenden der leistungsfähigeren Grafikkarten anstatt IntelHD sowie Erkennen und Nutzen von verfügbarem Memory, etc. Ich denke darin liegt für viele Spieler ein grosses Frustpotential und ist mitunter auch ein Grund für die mässigen Ratings z.B. auf Steam. Wird wohl nicht einfach werden, gerade letzteres, für mehrere OS und deren unterschiedlichen versionen umzusetzen, aber ich empfinde es als ein "Muss".

  • Thread 1 ruft Join auf und wartet auf Thread 2 sollte dieser noch nicht fertig sein.




    Thread 2 CPU gebunden, zu langsam, Tag im Monat:

    Code
    Thread 1: --1------------------15-------30Join                M-1------------------->
    Thread 2:    \------------------------------------------------/ \------------------->



    GPU zu langsam, stottern:

    Code
    Thread 1: --1- - - - - - - - - - - - - - - - - - 15- - - - - - -30Join Merge - - - - - - - - - - - - - -1- - - - - - - - - - - - - - - - - - 15 ->
    Thread 2:    \---------------------                               /                                      \--------------------------/


    Und das Monatsende mit Frames dargestellt:


    Code
    Frame X:  0                                       1                                       2    
    Thread 1: --UI-SOUND-GPUGPUGPU- Join- DataMerge --------------------------------------------->
    Thread 2: -----                /                    \---------------------------------------->


    Wenn beides zu langsam ist, habe wir diesen Fall:

    Code
    Frame X:  0                                       1                                       2    
    Thread 1: --UI-SOUND-GPUGPUGPUGPUGPUGPUGPUGPUGPU-Join      DataMerge --------------------------->
    Thread 2: ------------------------------------------------/           \------------------------->


    Legende:
    - CPU Arbeitet
    \ Thread Start
    / Thread Ende

  • Aber jedesmal wenn ein voller Zug ankommt und 100 Leute aus dem Bahnhof strömen, sinkt die Leistung merklich ab.


    selbst mit unsichtbaren Sims scheint es mir auch nicht viel besser zu sein - zumindest gefühlt. Würde bedeuten, dass es nicht am Darstellen liegt.
    Allerdings muss ich fast jeden Monatsübergang mehrere Sekunden warten bis es weitergeht - unabhängig davon wo ich zuschaue und egal in welcher Geschwindigkeitsstufe.


    eis_os: Da ich davon ausgehe, dass Thread2 bei mir das Problem ist (wohl fast bei allen) würde ich erwarten, dass dieser auch unabhängig von dem obigen immer konstant rechnet, zB. für die Ermittlung der Positionsdaten (welcher Sim ist wo?)

  • Ja, für eine besagte Komplexität X ist der Rechnungsaufwand Konstant. Du musst aber die Daten im Main Thread zusammenführen. Sind die Daten verhältnismäßig Groß, kann Thread 1 das nicht innerhalb eines Frames zusammenführen wenn es mit zu vielen GPU Kommandos beschäftigt ist und TF bleibt dann trotzdem stehen. (kleiner Ruckler am Ende, dieser Fall ist selten aber könnte auch passieren)


    Das Problem ist aber, X ist ist nicht konstant. Wenn deine Sims aus dem Zug steigen, müssen diese einen Weg finden und Kollisionen vermeiden.
    Wenn ein Sim zu Arbeit will muss TF eine Route per Auto / Zug / Tram und Linien + Umsteigen errechnen


    Einfach im Fenstermodus noch den ProcessExplorer (Sysinternals Suite) -> TrainFever auswählen und die Threads anzeigen, dann kann man TF schön beobachten. Der MSVCP Thread ist der hier genannte zweite Thread, der als Eingabe Daten Linienberechnung und die Gebäudesimulation annimmt. Man kann das auch ganz gut selber Testen in einem Savegame, einfach alle Fahrzeuge in der Fahrzeugübersicht verkaufen, dann sollte Thread 2 schneller fertig werden und man hat keine Probleme beim Jahresabschluss.

  • Auch auf die Gefahr dumme Fragen zu stellen aber ab wann ist es unspielbar? Bin im Jahr 2040 (gestartet 2000 mit dem savegame weil ich gerne neue Züge wollte) und bisher geht es noch halbwegs. Wie viele Züge habt ihr so im Schnitt wenn es nur noch Standbilder sind? Spiele auf ner großen map mit alles auf max und am Monatsende hab ich so ca 2-3 sek freeze, verwalte aber bisher nur so um die 35 Züge und pro Stadt min 4 Busse. Danke und nicht gleich mit Steinen werfen ;)

BlueBrixx