Entwicklertagebuch
Multi Core Prozessoren in Anno 1404

 

Dual Core, Quad Core und Multi Core

Prozessoren mit zwei oder mehreren CPU Kernen sind mittlerweile in den Mainstream Markt vorgedrungen. Konventionelle Einkern-Prozessoren sind an eine physikalische Grenze gestoßen, an der weitere Geschwindigkeitszuwächse nur noch in sehr geringem Maße möglich sind. Deshalb sind die Prozessorhersteller dazu übergegangen, mehrere Kerne auf einen Chip unterzubringen. Um diese neuen Leistungspotentiale nutzen zu können, bedarf es aber spezieller Unterstützung innerhalb des jeweiligen Programmcodes. Der große Vorteil eines solchen Systems liegt in der parallelen Abarbeitung verschiedenster Programmteile im Gegensatz zur ineffizienteren und langsameren sequentiellen Bearbeitung. Dieses bedarf eines gewissen Aufwandes, da parallel arbeitende Programmteile sich nicht gegenseitig in die Quere kommen dürfen, was im günstigsten Fall zu einem fehlerhaften Rendering führt, in den meisten Fällen aber einen Programmabsturz zur Folge hat.

Wir werden hier nun das proprietäre Multi Threading System vorstellen, welches wir für unsere neue 3D-Engine entwickelt haben und erstmals in Anno 1404 zum Einsatz kommt.

Der Scheduler

Ziel bei der Implementierung unseres Multi Threading Systems war es von vornherein, nicht an der Zahl der CPU Cores limitiert zu sein, sondern ein flexibles, skalierbares System zu entwickeln. Dazu wurde der Scheduler entwickelt. Der Scheduler detektiert die im System vorhandenen CPU Kerne und erstellt für jeden dieser Kerne einen Worker Thread. Um den Scheduler und dessen Worker Threads mit Arbeit zu versorgen, legt die Engine für verschiedene Systeme (Physiksimulation, Effekte, Animationen, usw.) jeweils eine sogenannte Job Queue an. Während des Programmablaufes fügt die Engine verschiedene Jobs oder Jobpakete in die jeweilige Queue ein. Diese Jobs werden dann von den Worker Threads aus den Queues geholt und abgearbeitet.

Jobs und Job Pakete

Ein Job kann ein beliebiger Programmteil innerhalb der Applikation sein. Da das Eintragen und Austragen der Jobs in eine Queue auch einen (wenn auch sehr kleinen) Overhead hat, ist es sinnvoller, sehr kleine Jobs in sogenannte Job Pakete zu packen. Job Pakete bestehen aus einer Sammlung einzelner Jobs, welche auf einmal abgearbeitet werden.

Die Barriere

Es kann durchaus passieren, dass bestimmte Jobs auf Ergebnisse von vorher ausgeführten Jobs innerhalb einer Queue angewiesen sind. Um dies zu realisieren, kann eine Barriere in eine Job Queue eingefügt werden. Sie veranlasst, dass alle Jobs, welche sich vor ihr innerhalb der Queue befinden, zuerst abgearbeitet werden. Somit liegen die Ergebnisse für die nach der Barriere folgenden Jobs auf jeden Fall vor und können von diesen weiterverarbeitet werden.

Bedeuten 4 Kerne gleich 4-fache Geschwindigkeit?

Nicht alle Programmteile lassen sich parallel ausführen. So können zurzeit z.B. alle Direct3D Device Zugriffe nicht parallel stattfinden. Aber auch voneinander abhängige Funktionen, die sich durch Barrieren nur sehr ineffizient darstellen lassen.

Deswegen müssen alle sequentiellen Programmteile, welche die Ergebnisse der Jobs weiterverarbeiten, spätestens zu diesem Zeitpunkt fertig abgearbeitet sein. Dies ist der Zeitpunkt der Synchronisierung. Dazu wartet der sequentielle Teil des Programmes auf das Beenden einer bestimmten Job Queue. Damit diese Wartezeit möglichst klein bleibt, arbeiten die Worker Threads diese Job Queue bevorzugt ab.

Dadurch, dass bestimmte Programmteile immer seriell ablaufen müssen, ist der Performancezuwachs bei realen Softwareapplikationen mit steigender Anzahl der Kerne limitiert. Er ist also sehr stark davon abhängig, inwieweit die jeweilige Applikation den Programmcode parallelisieren kann – möglichst so, dass auch alle Kerne optimal ausgelastet werden.

Multi-Threading am Beispiel unserer 3D Engine

Es ist wichtig, bei dem Design der Softwareapplikation Elemente zu finden, die sich von Natur aus besonders gut zur parallelen Bearbeitung eignen, bzw. die Architektur entsprechend anzupassen. Da unsere 3D Engine vom eigentlichen Spiel abgekapselt ist, läuft sie generell in einem separaten Thread. Um jedoch weitere CPU Kerne optimal auszulasten, werden innerhalb der 3D Engine außerdem viele weitere Jobs parallel berechnet.

Das geht schon beim Empfang der Daten los: Das Spiel schickt der Engine nacheinander, welche Objekte es wie und wo gerendert haben möchte. Zusätzlich hat unsere Engine noch eine eigene Liste von Objekten, deren Position in der Welt statisch ist. Beispielsweise Berge, Bäume, Gräser und Gebäude. All diese Objekte werden in Job-Pakete zusammengefasst. Solche Objekte bestehen für die Engine aber tatsächlich aus mehreren Teilobjekten: Basismodell, Effekte, Billboards, kleine Fähnchen, Zierobjekte, Bodenplatten und vieles mehr – alle mit ihren ganz eigenen Parametern. Da die Anno-Welt extrem detailliert ist, kommen jedes Frame tausende solcher Objekte auf den Bildschirm. Für all diese Teilobjekte werten die Worker-Threads nun die Parameter aus: Sie schauen, ob ein Objekt überhaupt gerendert werden soll, transformieren es richtig, wählen eine LOD-Stufe oder färben es ein.

Nachdem nun die Job-Pakete abgearbeitet sind, liegen der Engine riesige Listen mit Objekten vor, welche zunächst sortiert werden müssen. Diese Sortierung sorgt für eine minimale Anzahl an aufwendigen State Changes, die sonst die Grafikkarte ausbremsen. Dazu schicken wir dem Scheduler die Objekt-Listen, damit die Worker-Threads sie sortieren: Nach Shader, Textur, ob einzeln oder per schnellem Instancing, etc.

Während diese Sortierung läuft, schickt die Engine weitere Aufgaben an den Scheduler: Die Cloth-Simulation für die Fahnen, Stoffe und Schiff-Segel eignet sich sehr gut für Multi-Core Prozessoren, da jede Simulation unabhängig berechnet werden kann. Ebenso werden die Animationen und Partikel Effekte multi-threaded berechnet und aktualisiert. Für andere Objekte berechnen die Worker-Threads bestimmte Parameter vor, welche später an die Shader mitgegeben werden. Selbst die miteinander verschmelzenden Selektionskreise der Schiffe werden parallel zu den anderen Aufgaben berechnet.

Zu guter letzt wartet die Engine auf die Fertigstellung aller Jobs und sendet die Daten zur Darstellung an das Direct3D Device.

von Burkhard Ratheiser (Head of R&D, Related Designs) und Frank Hoffmann (Junior Software Developer, Related Designs)

 

               

© © 2015 Ubisoft Entertainment. All Rights Reserved. Anno 2205, Ubisoft and the Ubisoft logo are trademarks of Ubisoft Entertainment in the US and/or other countries.
Anno, Blue Byte and the Blue Byte logo are trademarks of Ubisoft GmbH in the US and/or other countries.
Datenschutz | Nutzungsbedingungen | Impressum | Kontakt | Jobs | Presse | Händler | Support | Über Ubisoft