Verwechseln Sie dies zunächst nicht mit datengesteuertem Design.
Mein Verständnis von datenorientiertem Design ist, dass es darum geht, Ihre Daten für eine effiziente Verarbeitung zu organisieren. Insbesondere in Bezug auf Cache-Fehler usw. Bei Data Driven Design geht es dagegen darum, dass Daten einen Großteil Ihres Programmverhaltens steuern (sehr gut beschrieben durch Andrew Keiths Antwort ).
Angenommen, Ihre Anwendung enthält Kugelobjekte mit Eigenschaften wie Farbe, Radius, Sprungkraft, Position usw.
Objektorientierter Ansatz
In OOP würden Sie Bälle wie folgt beschreiben:
class Ball {
Point position;
Color color;
double radius;
void draw();
};
Und dann würden Sie eine Sammlung solcher Bälle erstellen:
vector<Ball> balls;
Datenorientierter Ansatz
In Data Oriented Design schreiben Sie den Code jedoch eher wie folgt:
class Balls {
vector<Point> position;
vector<Color> color;
vector<double> radius;
void draw();
};
Wie Sie sehen, gibt es keine Einheit mehr, die einen Ball darstellt. Ballobjekte existieren nur implizit.
Dies kann in Bezug auf die Leistung viele Vorteile haben. Normalerweise möchten wir viele Bälle gleichzeitig operieren. Hardware möchte normalerweise, dass große, kontinuierliche Speicherblöcke effizient arbeiten.
Zweitens können Sie Operationen ausführen, die nur einen Teil der Eigenschaften eines Balls betreffen. Wenn Sie beispielsweise die Farben aller Kugeln auf verschiedene Weise kombinieren, soll Ihr Cache nur Farbinformationen enthalten. Wenn jedoch alle Ball-Eigenschaften in einer Einheit gespeichert sind, ziehen Sie auch alle anderen Eigenschaften eines Balls ein. Auch wenn du sie nicht brauchst.
Cache-Verwendungsbeispiel
Angenommen, jeder Ball benötigt 64 Bytes und ein Punkt 4 Bytes. Ein Cache-Slot benötigt beispielsweise auch 64 Bytes. Wenn ich die Position von 10 Bällen aktualisieren möchte, muss ich 10 * 64 = 640 Bytes Speicher in den Cache ziehen und 10 Cache-Fehler erhalten. Wenn ich jedoch die Positionen der Kugeln als separate Einheiten bearbeiten kann, dauert dies nur 4 * 10 = 40 Bytes. Das passt in einen Cache-Abruf. Somit erhalten wir nur 1 Cache-Fehler, um alle 10 Bälle zu aktualisieren. Diese Zahlen sind willkürlich - ich gehe davon aus, dass ein Cache-Block größer ist.
Es zeigt jedoch, wie sich das Speicherlayout stark auf die Cache-Treffer und damit auf die Leistung auswirken kann. Dies wird nur an Bedeutung gewinnen, wenn sich der Unterschied zwischen CPU- und RAM-Geschwindigkeit vergrößert.
So gestalten Sie den Speicher
In meinem Ball-Beispiel habe ich das Problem stark vereinfacht, da Sie normalerweise für jede normale App wahrscheinlich gleichzeitig auf mehrere Variablen zugreifen. ZB werden Position und Radius wahrscheinlich häufig zusammen verwendet. Dann sollte Ihre Struktur sein:
class Body {
Point position;
double radius;
};
class Balls {
vector<Body> bodies;
vector<Color> color;
void draw();
};
Der Grund, warum Sie dies tun sollten, ist, dass, wenn Daten zusammen in separaten Arrays abgelegt werden, das Risiko besteht, dass sie um dieselben Slots im Cache konkurrieren. Wenn Sie also einen laden, wird der andere weggeworfen.
Im Vergleich zur objektorientierten Programmierung beziehen sich die Klassen, die Sie am Ende erstellen, nicht auf die Entitäten in Ihrem mentalen Modell des Problems. Da Daten basierend auf der Datennutzung zusammengefasst werden, haben Sie nicht immer sinnvolle Namen, um Ihren Klassen in datenorientiertem Design zu geben.
Beziehung zu relationalen Datenbanken
Das Denken hinter Data Oriented Design ist sehr ähnlich zu dem, was Sie über relationale Datenbanken denken. Das Optimieren einer relationalen Datenbank kann auch eine effizientere Verwendung des Caches beinhalten, obwohl in diesem Fall der Cache kein CPU-Cache, sondern Seiten im Speicher ist. Ein guter Datenbankdesigner wird wahrscheinlich auch Daten, auf die selten zugegriffen wird, in eine separate Tabelle aufteilen, anstatt eine Tabelle mit einer großen Anzahl von Spalten zu erstellen, wenn nur einige der Spalten jemals verwendet werden. Er kann sich auch dafür entscheiden, einige der Tabellen zu denormalisieren, damit nicht von mehreren Stellen auf der Festplatte auf Daten zugegriffen werden muss. Genau wie bei Data Oriented Design werden diese Entscheidungen getroffen, indem untersucht wird, wie die Datenzugriffsmuster aussehen und wo der Leistungsengpass liegt.