Alt-F4 #15 – Nachforschungen zu 1.1  27.11.2020

Geschrieben von Conor_, Therenas, editiert von stringweasel, Nanogamer7, nicgarner, Firerazer,
übersetzt von BluePsyduck, Nanogamer7, lektoriert von EDLEXUS, dexteritas

Inhaltsverzeichnis

Diese Woche werfen wir mit der Veröffentlichung der experimentellen Version 1.1 einen Blick auf zwei Dinge, die diese mit sich bringt. Zuerst untersucht Conor_, was ihm die neuen Zughaltestellenbeschränkungen in seiner TSM-basierten Fabrik alles ermöglicht. Danach erkundet Therenas, was das Multithreading-Update für die Fließbänder sowohl in Theorie als auch in der Praxis bedeutet. Vorsicht, vielleicht lernst du dabei noch etwas!

Zughaltestellenbeschränkungen Conor_

Als das Thema über die Veränderungen, die 1.1 mit sich bringt, aufkam, lag es für mich auf der Hand, über die neue Zughaltestellenbeschränkung zu sprechen, denn ich liebe Züge! Im Folgenden schaue ich mir an, welches Problem dieses Feature löst und wie ich bisher damit umgegangen bin.

Ein einfacher Fehler eines jungen, naiven Conor_

Vor einer Weile war ich gerade dabei, eine meiner ersten großen Basen zu bauen, um SpaceX bei 5-fachen Forschungskosten zu bewältigen (weil, warum nicht?), als ich Probleme in meinem Spaghetti™-Zugnetz bemerkte. In meiner jungen Ignoranz hatte ich beschlossen, dass die Haltestellen für ein bestimmtes Material einen gemeinsamen Namen haben sollten, wobei eine große Anzahl von Zügen zwischen diesen Haltestellen verkehrte, um sicherzustellen, dass alle benutzt werden. Das war keine gute Idee. Das einfach zu implementierende System führte zu Schmerz und Leid, wie ich es nicht mehr erlebt hatte, seit ich versuchte, die Ölverarbeitung zu verstehen. Es hatte für mich in kleinen Basen mit nur ein paar, über eine kurze Strecke verteilten Haltestellen gut funktioniert, aber in großem Maßstab fuhren die Züge einfach nicht zu den weiter entfernten Außenposten. Einige Haltestellen stauten sich zurück und verklemmten, während andere verwaist waren. Ich beschloss, dass es eine bessere Lösung geben musste, und als der gute Ingenieur, der ich bin, arbeitete ich hart an der Forschung und Entwicklung einer besseren Lösung für das Problem, fragte ich Reddit.

Kennt jemand eine Mod, die die Züge gleichmäßig auf gleichnamige Haltestellen verteilt, unabhängig von Entfernungsunterschieden. Z.B. alle Eisen-Entlade-Haltestellen werden gleich benannt, aber eine gleichmäßige Anzahl an Zügen fahren zu jeder der verschiedenen Haltestellen.

Does anyone knows of a mod that will equally distribute trains between stops of the same name, irrelevant of the distance difference. E.g. All iron unload stops called one thing but an equal number of trains go to each of the different stops.

— Conor_ (August, 2019)

Was für ein schöner Wunschtraum, den ich damals hatte. Statt um was ich gebeten hatte, wurde ich zu Recht auf TSM und LTN hingewiesen und dann aufgefordert, weiterzugehen. Aber bei 1.1 riefen die Entwickler von oben herab: “Wir haben eine neues Feature für Züge!” Okay Entwickler, ihr habt meine Aufmerksamkeit…

Was sind Zughaltestellenbeschränkungen?

Die Zughaltestellenbeschränkungen, in FFF-361 angesprochen, ermöglicht es dir, die Anzahl der Züge an einer Haltestelle zu begrenzen. Über die technischen Details dieses Systems wird in den FFF genauer eingegangen (was auf jeden Fall einen Blick wert ist), aber im Wesentlichen sollte ein Zug nur dann zu einer Haltestelle fahren, wenn es Platz für diesen gibt, was genau das ist, was der junge Conor_ wollte. Aber hält das im Vergleich zu meiner neu entdeckten Liebe, TSM, stand?

Grafische Oberfläche der Zughaltestellenbeschränkungen
Quelle: FFF-361

Was ist TSM und warum sollte mich das interessieren?

Der Train Supply Manager (TSM) (zu Deutsch: Zug-Versorgungs-Manager) ist eine Modifikation, die es Zughaltestellen ermöglicht, einen Zug anzufordern, wenn bestimmte Schaltungsbedingungen erfüllt sind. Ein einschlägiges Beispiel für eine solche Bedingung wäre, einen Zug anzufordern, wenn weniger als eine bestimmte Anzahl von Zügen auf dem Weg zur Haltestelle sind. Sie ermöglicht auch komplexere Schaltungszauberei, wie z. B. das Anfordern eines Zuges nur dann, wenn genügend Material vorhanden ist, um den Zug tatsächlich zu füllen, obwohl ich diese Funktionalität noch nie verwendet habe.

Der Traum von TSM ist die vollständige Implementierung eines Pull-basierten Logistiksystems anstelle des Push-basierten Systems, wie es bei einfacheren Konfigurationen üblich ist. Pushing ist die übliche Strategie, die man in vielen kleineren Fabriken findet, wo Züge am Außenposten aufgefüllt und dann weggeschoben werden, um am Zielort Schlange zu stehen. Bei einem Pull-System sind die Züge immer entweder lieferbereit, oder bereit, beladen zu werden, und werden nur dann angefordert (d. h. gepullt), wenn Ressourcen benötigt werden.

Der Unterschied liegt hierbei darin, wo das Timing der Züge festgelegt wird; bei einem Push-System hängt das Timing davon ab, wie schnell die Haltestellen die Züge füllen und auf die Reise schicken können, unabhängig davon, wie viele Ressourcen das Werk benötigt. Bei einem Pull-System wird der Zeitplan von den Zielhaltestellen bestimmt, so dass die Züge nur dann angefordert werden, wenn sie tatsächlich benötigt werden. Auf diese Weise kannst du die Anzahl der fahrenden Züge in einem bestimmten Netzwerk massiv reduzieren, und steuern, wo sich die Züge anstellen, um sicherzustellen, dass sie keine Staus verursachen. Das ist zwar auch in Vanilla möglich, indem man große Zugstapler einsetzt und sorgfältig plant, wo wartende Züge halten sollen, aber es ist nicht sehr ideal oder bequem. Bei diesem System werden die Züge sowohl an den Be- als auch an den Entladehaltestellen angefordert, was besonders für größere Basen wichtig ist, und der Grund, warum ich TSM so sehr liebe.

In dem Video haben die Be- und Entladestellen immer jeweils nur ein Zug in ihnen angehalten, so dass es zu keinem Rückstau kommt. Wenn eine Haltestelle frei wird, wird ein Zug von TSM aus dem Depot angefordert, um die Haltestelle, die ihn am dringendsten benötigt, wieder aufzufüllen.

Können Zughaltestellenbeschränkungen den TSM ersetzen?

Um zu verstehen, wann der TSM benötigt wird, anstatt nur die neuen Zughaltestellenbeschränkungen zu verwenden, habe ich die TSM-Konfiguration in Vanilla 1.1, ohne TSM, mit großem Erfolg nachgebildet!

Dieses Video zeigt den gleichen Aufbau von Kupferzügen beim Be- und Entladen, wieder mit nur je einem Zug an den Be- und Entladestellen, während die anderen im Depot warten.

Der Grund dafür, dass das so gut funktioniert und ein so guter Ersatz für TSM ist, liegt darin, dass die Züge an der Haltestelle des Depots warten, bis in der Be- oder Entladestation Platz ist. Würden die Züge an der Beladestelle warten, würde dies zu Staus führen und die Produktionskapazität des Produzenten einschränken. Die Verwendung von Depothaltestellen ermöglicht es den Zügen, eine Art „Bereitstellungsbereich“ zu haben, in dem sie warten können, bis sie gebraucht werden, und dem übrigen Zugnetz nicht im Weg stehen.

Wie wir sehen können, kann das gesamte System mit der neuen Vanilla-Funktion nachgebildet werden, wahrscheinlich mit besserer Leistung aufgrund des First-Party-Charakters. Dies berücksichtigt noch nicht einmal die größere Einfachheit der Zughaltestellenbeschränkungen im Vergleich zum Verständnis des TSM, der eine recht steile Lernkurve und eine nicht ganz perfekte Dokumentation aufweist. Der TSM kann in bestimmten Szenarien immer noch nützlich sein, z. B. wenn der Spieler Informationen darüber haben möchte, welche Anfragen derzeit unerfüllt sind, die der TSM über seine Schnittstelle bereitstellt, obwohl ich persönlich diese Funktionen nur selten nutze.

Schlussfolgerung

Bei Spielen mit Unterstützung für Mods kommt es häufig vor, dass die Spielentwickler interessante Mod-Funktionalitäten bemerken und in das Basisspiel implementieren. Das kann für die Mod-Entwickler etwas schmerzhaft sein, da ihre Mod meist überflüssig wird, aber am Ende ist es ihnen gelungen, das Spiel zu verbessern. Ihre Mod ist nun indirekt in das Spiel integriert, das sie lieben, was bedeutet, dass mehr Leute sie benutzen können, was großartig ist.

Besonderen Dank an sorahn auf dem Factorio-Discord-Server, der meine Fragen erkannt und mir geholfen hat, die Karte zu erstellen, die ich oben modifiziert habe, um zu veranschaulichen, wie der TSM in der “Double Request“-Konfiguration funktionieren kann, und um meine Ideen vor dem Bauen auf ihre Richtigkeit zu prüfen.

Dieses Feature wird für neuere Spieler (wie den jungen und naiven Conor_) fantastisch sein, um leichter in den Aufbau größerer und ausgeklügelterer Zugnetze einzusteigen. Es bietet eine weitere leicht zu erlernende und schwer zu beherrschende Funktion, die von engagierten Spielern genutzt und erforscht werden kann und gleichzeitig Neulingen den Einstieg in den Spielspaß erleichtert.

1.1 Leistungsverbesserungen Therenas

Die neuste experimentelle Version von Factorio brachte viele Änderungen mit sich, eine davon möchte ich mir heute näher anschauen. Sie versteckt sich klein im 1.1.0 Changelog und wurde in keiner Ausgabe von FFF vor der Veröffentlichung erwähnt. Es sind nur drei Wörter: Multithreaded Fließband Aktualisierungslogik. Ich bin hier um herauszufinden, was das bedeutet, und welche Wirkung es eigentlich hat.

Wie funktioniert diese Optimierung auf einem technischen Level?

Du wirst jetzt vermutlich keine Idee haben, was es bedeutet, Spiellogik zu multithreaden. Wieso sollte man nicht alles multithreaden, damit das Spiel alle Kerne deines PCs verwenden kann? Es zeigt sich, dass das nicht so einfach ist. Grundsätzlich muss das Spiel alle Maschinen, Fließbänder, Rohre, etc. jeden Tick aktualisieren. So vergeht Zeit im Spiel, und lässt dich überhaupt erst spielen. Die Reihenfolge, in der das passiert, ist wichtig. Als erstes bewegen die Fließbänder Gegenstände in die Richtung des Fließbandes, dann nimmt ein Greifarm eines auf und gibt es in eine Maschine, dann stellt die Maschine daraus etwas her.

Das fundamentale Problem, das Multithreading mit sich bringt, ist, dass es nichts über die Reihenfolge, in der Sachen passieren, garantieren kann. Wenn du das vorherige Beispiel multithreadest, kann es passieren, dass die Maschine versucht etwas herzustellen, bevor der Greifarm den Gegenstand einfügt. In diesem Fall hätte die Maschine nichts herstellen können, weil ihr die Zutaten gefehlt haben. Wenn das Einfügen zuerst passiert, dann würde das Herstellen weitergehen. Das ist ein Problem, weil es nicht deterministisch ist. Je nachdem wie die threaded Logik ausgegangen ist, stellt die Maschine entweder einen Gegenstand her oder nicht, was die ganze Simulation bricht.

Dieses Bespiel ist natürlich nur eine Illustration des Problems. Die eigentlichen Probleme, die dadurch entstehen, sind wesentlich komplizierter und technischer. Weiteres passieren die Aktionen, die ich als Beispiel verwendet habe nicht in einem einzelnen Tick für eine spezifische Maschine; sie sind eine Analogie, um das Problem zu veranschaulichen. Sie stimmen nicht unbedingt mit dem überein, wie das Spiel Sachen organisiert.

Oberflächlich sieht es daher so aus, als würde man nichts in einem Spiel wie Factorio multithreaden können, weil es die Simulation brechen würde. Alles hängt von allem ab, richtig? Also, nicht ganz. Es wird immer Teile geben, die unbedingt in einer linearen Reihenfolge ausgeführt werden müssen, aber du wirst immer Teile des ganzen finden, die wirklich unabhängig voneinander sind, wenn du genauer hinschaust. Die Fließbandlogik ist eines dieser Teile.

Wenn man darüber nachdenkt, hat nicht jedes Fließband mit jedem anderen auf der Karte zu tun. Sicherlich gibt es gigantische Netzwerke an Fließbändern, die miteinander verbunden sind, wie zum Beispiel wenn du eine Hauptleitung verwendest, aber es gibt auch Fließbandstrecken, die komplett unabhängig voneinander sind. Ziemlich viele sogar, da Züge oder Reihen an Maschinen dazu neigen, Fließbänder aufzuteilen.

Bildschirmfoto von zwei separaten Linien an Fließbänder, ineinander verflochten
In diesem Bildschirmfoto sind die blauen und roten Fließbänder Teile von zwei verschiedenen Fließbandsystemen. Achte darauf, wie sie durcheinander schlängeln, aber nie mit einander interagieren. Beachte auch wie die Maschine es ihnen erlaubt, indirekt miteinander zu agieren, während die Aufteilung für Multithreading beibehalten wird.

Das erlaubt uns die Logik zur Aktualisierung von Fließbändern zu parallelisieren (d. h. zu multithreaden). Wir müssen dabei vorsichtig sein; das heißt nicht, das wir einfach die Fließbänder irgendwann im Tick aktualisieren können. Es muss weiterhin den Schritt der Bewegung von Gegenständen geben, des Aufheben der Greifarme, der Herstellung von Gegenständen, in dieser Reihenfolge. Die Sache, die wir aufteilen können, ist die Bewegung der Gegenstände. Wenn wir zu diesem Punkt gelangen, teilen wir die Aufgabe so auf, dass jedes isolierte System an Fließbändern ihren eigenen Thread bekommt. Jeder Thread bewegt dann die Gegenstände in dem System, das ihm zugewiesen wurde, so das alle sich gleichzeitig bewegen, d. h. parallel gehandhabt werden. Wenn wir vorsichtig sind, nur Fließbänder aufzuteilen, die nicht mit anderen interagieren, können wir sie einzeln sicher aktualisieren.

Illustration des Relevanten Teil des Updateprozess
Diese Illustration zeigt, wie die Leistung verbessert wird. Statt die Zeit der Summe aller Aktualisierungen abwarten zu müssen, muss das Spiel nur warten, bis das am längsten brauchende Fließbandsystem fertig ist (Fließband #2 in diesem Falle). Das kann zu großen Leistungsverbesserungen führen, da es in der Realität wesentlich mehr als drei separate Systeme an Fließbändern gibt.

Dieser Ansatz ist ziemlich ähnlich zur Verbesserung der Flüssigkeitslogik, wie in FFF #271 besprochen. Dieser Post hat einige tiefe Einsichten in Sachen wie das Arbeitsspeicherlayout verändert wurde, um Cacheeffizienz zu verbessern, aber das ist nicht der Fokus des Artikels. Es gibt auch einen sehr interessanten Reddit-Thread von Varen/Raven über die Re-Implementation von Factorio unter Beachtung von Multithreading von Anfang an. Ließ dir diesen für weitere technische Konversationen über dieses Thema durch.

Aber wie groß ist der Unterschied nun?

All diese Theorie ist zwar nett und so, aber du wunderst dich jetzt sicher, wie viel sich dadurch wirklich verbessert hat. Wundere dich nicht mehr, ich habe Diagramme mitgebracht!

Vorab, diese Messungen wurden in den Versionen 1.0.0 beziehungsweiße 1.1.1 durchgeführt. Ich habe die Konsole im Spiel verwendet, um die Geschwindigkeit des Spiels zu erhöhen, und UPS von mehr als 60 zu erreichen. Die Zahlen wurden nicht mit sehr strengen Methoden gemessen, was heißt, dass sie eine nicht unbeachtliche Fehlerspannweite haben. Was auch beachtet werden muss, ist, dass die Entitätsaktualisierungsleistung laut dem Changelog ebenfalls verbessert wurde. Das ist auch inkludiert, Ich glaube aber nicht, dass meine Messungen genau genug waren, um eindeutige Schlüsse ziehen zu können

Ich habe drei verschiedene Speicherstände mit unterschiedlichen Charakteristiken gemessen, alle davon haben aber eine Menge an Fließbändern verwendet. Du kannst nun mal die Verbesserung der Leistung nicht an etwas nicht-existentem messen. Lass uns unsere Teilnehmer treffen, wobei alle von der großartigen FactorioBox-Webseite genommen wurden, welche eine kleine Sammlung an Speicherständen hat, die nützlich zum Benchmarken sind.

Als erstes habe ich die 10k SPM Basis von Stevetrov getestet. Sie verwendet sehr leistungsoptimierte Layouts, die fast nur auf Fließbändern aufbauen. Keine Züge waren in Verwendung, und Roboter wurden nur in sehr spezifischen Fällen verwendet, in denen sie bewiesen besser für die Leistung als Fließbänder waren. Das macht sie zu einem idealen Kandidaten, um die Wirkung, die die Änderungen haben, zu zeigen. Der Effekt wird in anderen Fällen, in denen Leistungskosten auf mehr Sachen, wie Roboter und Züge verteilt sind, nicht so deutlich.

Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1 Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1
Wie diese Diagramme zeigen, sind die Verbesserungen an der Fließbandaktualisierungszeit bei unserer ersten, etwas idealisierten Basis ziemlich beträchtlich, da wir Verbesserungen von etwa 150 % sehen! Das verbessert die UPS in diesem Spiel um zirka 26 %, was ziemlich viel ist, wenn man beachtet, das nur Code für Fließbänder geändert wurde. Interessanterweise ging die Entitätaktualisierungszeit sogar etwas nach oben, aber das ist innerhalb unserer Fehlerspannweite, also können wir keine Schlüsse daraus ziehen.

Danach habe ich eine Basis mit einem Layout, das näher an etwas was ich konstruieren würde ist, gemessen. Sie heißt nur cam6 auf FactorioBox, mit keinem Hinweis auf ihren Ursprung. Sie basiert hauptsächlich auf Fließbändern, mit ein paar Zügen und Robotern mit drinnen. Auch produziert sie ihren Strom mit nuklearen Reaktoren, welche dazu neigen, ein nicht unbeachtliches Stück Leistungskuchen abzuschneiden. Wie ich gesagt habe, hat sie all die Dinge, die du von einer normalen Factorio-Basis erwarten würdest, welche sie eine gute Repräsentation für die Auswirkungen, die du in deiner Basis erwarten kannst, darstellt.

Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1 Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1
Auf diese Diagramme von unserer normaleren Karte blickend sehen wir Verbesserungen der Fließbandaktualisierungszeit von etwa 100 %, was etwas weniger als bei der 10k SPM Megabasis ist, aber trotzdem eine erwähnenswerte Verbesserung ist. UPS steigt um etwa 16 %, und die Änderungen zur Entitätsaktualisierungszeit waren wieder in der Fehlerspannweite.

Als letztes hab ich mir einen etwas ungewöhnlichen Kandidaten angeschaut: Eine riesige, chaotische Fabrik mit dem Namen Besenovsky Pajzel, was vermutlich der Name des Erstellers ist. Sie ist als eine „riesige Karte (13300x7400 Kacheln) mit ineffizienter Produktion in einer Spanne von 2,4k bis 4k SPM.“ Also diese Karte ist ein Mix von all diesen Dingen, mit dem größten Unterschied zu den vorherigen beiden Speicherständen die weitläufige Verwendung von Zügen. Was wir davon erwarten ist, dass die Auswirkungen der Optimierungen in 1.1 kleiner ist, da die Sachen, die verbessert wurden, in dieser Fabrik weniger relevant sind.

Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1 Diagramm mit diversen Leistungsmaßstäben für 1.0 und 1.1
Die Untersuchung dieses letzten Datensatzes für unsere chaotischere Basis zeigt noch größere Leistungsverbesserungen zu den Fließbandaktualiesierungszeiten als bei der Megabasis von ungefähr 170 %. Das führt zu UPS Verbesserungen von 37 %, die vermutlich durch die Enititätsaktualisierungsgverbesserungen, welche auf dieser Karte einen Unterschied von 38 % zeigen, erhöht wurden.

Wenn wir den Durchschnitt dieser drei sehr ungenauen Messungen nehmen, bekommen wir eine Verbesserung an Fließbändern von 140 % und 26 % höhere UPS. Das ist natürlich nicht repräsentativ vom Durchschnitt aller Speicherstände, da wir nur diese drei besonderen Karten beachtet haben. Insgesamt hängt die Verbesserung durch 1.1 einigermaßen am Layout deiner Basis ab, aber es ist eine nette Verbesserung.

Im Endeffekt ist es nicht relevant, wie groß die Wirkung einer spezifischen Leistungsverbesserung ist; es ist die Summe aller kleinen Verbesserungen die das Spiel um eine Größenordnung schneller laufen lassen. Wir haben diesen Effekt vor ein paar Wochen in ALT-F4 #13 erkundet, und ich erwarte, dass diese Basis einen weiteren Tick an Verbesserung der Leistung erhält.

Beitragen

Wie immer suchen wir nach Leuten, die zu Alt-F4 beitragen wollen, sei es mit einem Artikel oder durch Hilfe bei Übersetzungen. Wenn du etwas Interessantes im Kopf hast, das du mit der Community in einer eleganten Art teilen möchtest, hier kannst du das tun. Falls du dir unsicher bist, beantworten wir gerne Fragen zu Inhalt und Struktur. Falls das nach etwas klingt, woran du interessiert bist, tritt unserem Discord bei um es nicht zu verpassen!