Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

eXpl0it3r

Treue Seele

  • »eXpl0it3r« ist der Autor dieses Themas

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

1

08.08.2012, 20:33

Multidimensionale Arrays in C++

Edit by dot: Abgetrennt von hier

Ein std::vector aus std::vector ist keine gute Lösung für ein zweidimensionales Array. Verwend besser einen einfachen eindimensionalen std::vector in dem du deine zweidimensionalen Daten z.B. zeilenweise speicherst...
Wie kommst du jetzt grad darauf?
Es mag zwar nicht sehr schön aussehen und ist ohne Iteratornutzung evtl. etwas weniger performant als ein wirkliches 2D Array, aber für einen kleinen Datensatz spielt dies alles keine Rolle. Du kennst ja sich das bekannte Zitat:

Zitat

Premature optimization is the root of all evil.
Also verwirr die Anfänger doch nicht mit optimierungs Problemen. ;)
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (11.08.2012, 20:38)


dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

2

08.08.2012, 21:01

Ein std::vector aus std::vector ist keine gute Lösung für ein zweidimensionales Array. Verwend besser einen einfachen eindimensionalen std::vector in dem du deine zweidimensionalen Daten z.B. zeilenweise speicherst...
Wie kommst du jetzt grad darauf?
Es mag zwar nicht sehr schön aussehen und ist ohne Iteratornutzung evtl. etwas weniger performant als ein wirkliches 2D Array, aber für einen kleinen Datensatz spielt dies alles keine Rolle.

Mit dem gleichen Argument könnte man auch eine verschachtelte std::map verkaufen...
Es geht mir nicht um die Performance. Ein verschachtelter std::vector entspricht einem sog. jagged Array, was rein prinzipiell etwas fundamental anderes ist als ein mehrdimensionales Array. Der Grund, wieso ich davon abrate, ist in erster Linie, dass es strukturell nicht dem zu lösenden Problem entspricht. Die Tatsache dass es auch noch unnötig ineffizient ist, spielt hier vermutlich tatsächlich keine Rolle. (Übrigens potentiell nicht nur ein bisschen und mit Iteratoren hat das nix zu tun, aber egal)

Du kennst ja sich das bekannte Zitat:

Zitat

Premature optimization is the root of all evil.

Ja, das berühmte Zitat von Donald E. Knuth, das, völlig aus dem Kontext gerissen, immer und überall verwendet wird, um alles Mögliche zu rechtfertigen... ;)

Also verwirr die Anfänger doch nicht mit optimierungs Problemen. ;)

Wie gesagt, handelt es sich da imo nicht einfach um ein Optimierungsproblem, sondern um etwas Prinzipielles...

Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von »dot« (08.08.2012, 21:16)


eXpl0it3r

Treue Seele

  • »eXpl0it3r« ist der Autor dieses Themas

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

3

08.08.2012, 21:37

Wie gesagt, handelt es sich da imo nicht einfach um ein Optimierungsproblem, sondern um etwas Prinzipielles...
Prinzipien haben immer einen Hintergrund und der einzige Grund, warum jagged Arrays nicht gut sind, ist weil es nicht sehr performant für die CPU bzw. deren Cache ist und darum die ganze Zeit Daten vom langsamen RAM angefordert werden müssen.

Der Grund, wieso ich davon abrate, ist in erster Linie, dass es strukturell nicht dem zu lösenden Problem entspricht.
Wieso sollte dann ein eindimensionales Array von der Struktur her besser sein für das Problem?

Für den Vorschlag wäre es evtl. für Anfänger noch hilfreich, wenn du ein Beispiel geben kannst, weil "Verwend besser einen einfachen eindimensionalen std::vector in dem du deine zweidimensionalen Daten z.B. zeilenweise speicherst..." sagt ja jetzt wirklich nicht viel aus.

Zum Problem selbst ist weder ein eindimensionaler noch ein "zweidimensionaler" Vektor praktisch, denn die empfohlene Art TileMaps darzustellen in SFML 2 ist mit einem VertexArray. :)
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

4

08.08.2012, 21:50

Prinzipien haben immer einen Hintergrund und der einzige Grund, warum jagged Arrays nicht gut sind, ist weil es nicht sehr performant für die CPU bzw. deren Cache ist und darum die ganze Zeit Daten vom langsamen RAM angefordert werden müssen.

Das sind alles Gründe, aber nicht die einzigen. Ein imo noch viel wichtigerer Grund ist, dass jagged Arrays vergleichsweise sehr viel komplizierter sind als normale Arrays. Wieso sollte man eine kompliziertere einer sehr viel einfacheren Lösung vorziehen, wenn die einfachere sogar auch noch effizienter ist!?

Der Grund, wieso ich davon abrate, ist in erster Linie, dass es strukturell nicht dem zu lösenden Problem entspricht.
Wieso sollte dann ein eindimensionales Array von der Struktur her besser sein für das Problem?

Weil das Problem zweidimensionaler Natur ist und ein zweidimensionales Array hier die einfachste, intuitivste und effizienteste mir bekannte Lösung ist.

Für den Vorschlag wäre es evtl. für Anfänger noch hilfreich, wenn du ein Beispiel geben kannst, weil "Verwend besser einen einfachen eindimensionalen std::vector in dem du deine zweidimensionalen Daten z.B. zeilenweise speicherst..." sagt ja jetzt wirklich nicht viel aus.

Ich möchte unserem Anfänger eben die Chance geben, selbst zu denken und dabei wirklich was zu lernen anstatt vorgekaute Brocken zu kopieren. Für mich war genau diese Arraygeschichte damals eine sehr wichtige Erkenntnis...

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (08.08.2012, 21:56)


eXpl0it3r

Treue Seele

  • »eXpl0it3r« ist der Autor dieses Themas

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

5

08.08.2012, 22:25

dot, da ich mich für solche Design Prinzipien sehr interessiere, wäre ich froh wenn du das Ganze etwas ausführen könntest, weil bis jetzt habe ich noch nie gehört, dass zweidimensionale std::vector im Allgemeinen eine schlechte Datenstruktur ist. Auch bin ich mir über deine Definition/Vorstellung von Jagged Array nicht ganz bewusst, Google lieftert mir da nur Artikel zu C#.
Das Problem mit dem CPU Cache tritt eigentlich nur auf, wenn man falsch durch ein mehr dimensionales Array iteriert, sprich zuerst die Zeilen und dann die Spalten.
Ich studiere Informatik an der ETH Zürich und wir hatte bis jetzt nie irgendetwas in diese Richtung erwähnt bekommen, obwohl wir mit C# und C++ gearbeitet haben und uns auch von Grund auf mit Prozessoren beschäftigt haben.
Am Besten wären natürlich irgendwelche externe und gute Quellen (so in Richtung Bjarne Stroustrup oder Herb Sutter). ;)

Mit dem gleichen Argument könnte man auch eine verschachtelte std::map verkaufen...
Die STL wurde so entworfen, dass die Verwendung von dessen Containern keinen grossen Einfluss auf die Laufzeit oder den Speicher hat. Obwohl es natürlich in den meisten Fällen keinen Sinn macht std::map's zu verschachtel, so werden die Prinzipien von C++ damit nicht verletzt.
Ich kann hier zwar meistens nur Behauptungen aufstellen, weil ich nicht den Standard gelesen habe, aber ich vertraue darauf, dass die Implementierungen von der STL und die Optimierungen von den Compilers die beste Performance für den gegebenen Code generieren.

Zitat von »dot«

Das sind alles Gründe, aber nicht die einzigen. Ein imo noch viel wichtigerer Grund ist, dass jagged Arrays vergleichsweise sehr viel komplizierter sind als normale Arrays. Wieso sollte man eine kompliziertere einer sehr viel einfacheren Lösung vorziehen, wenn die einfachere sogar auch noch effizienter ist!?

Heisst das nur weil du nicht ganz verstehst wie hier im Hintergrund alles läufst, nimmst du eine einfachere Lösung, oder hab ich dich jetzt da falsch verstanden mit 'kompliziert'?

Ich möchte unserem Anfänger eben die Chance geben, selbst zu denken und dabei wirklich was zu lernen anstatt vorgekaute Brocken zu kopieren. Für mich war genau diese Arraygeschichte damals eine sehr wichtige Erkenntnis...

Ja das ist gut, nur war deine Aussage meiner Ansicht nach sehr wage und ich bin mir jetzt immer noch nicht sicher, was du denn genau mit "zeilenweise speicherst" meintest. Wenn du jetzt meinst, dass jedes Element im Vector eine Zeile der TileMap enthalten soll, dann entspricht dies ja gerade wieder einem zweidimensionalem std::vector, wenn du aber meintest, man soll jeden Block in ein eindimensionales Array einfügen und dann die Zeilen und Spalten Berechnung selbst machen, dann ist dies überhaupt nicht offensichtlich, besonders für einen Anfänger. Ausserdem ist eine eigenhändige Berechnung der Position nicht empfohlen, da diese zu vielen Fehler führen kann und sobald eine Zeile oder Spalte eingefügt oder entfernt werden muss, wird das eine sehr haarsträubige Angelegenheit.

Zitat von »eXpl0it3r«

Zitat von »dot«

Der Grund, wieso ich davon abrate, ist in erster Linie, dass es strukturell nicht dem zu lösenden Problem entspricht.

Wieso sollte dann ein eindimensionales Array von der Struktur her besser sein für das Problem?
Weil das Problem zweidimensionaler Natur ist und ein zweidimensionales Array hier die einfachste, intuitivste und effizienteste mir bekannte Lösung ist.
Wie bist du jetzt wieder von einem eindimensionalen Array, wie vorgeschlagen in deinem ersten Post, auf ein zweidimensionales Array gekommen? Und wieso ist ein zweidimensionales Array besser als ein zweidimensionaler std::vector?
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »eXpl0it3r« (08.08.2012, 22:32)


dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

6

09.08.2012, 03:11

dot, da ich mich für solche Design Prinzipien sehr interessiere, wäre ich froh wenn du das Ganze etwas ausführen könntest, weil bis jetzt habe ich noch nie gehört, dass zweidimensionale std::vector im Allgemeinen eine schlechte Datenstruktur ist.

Ich würde auch gar nicht sagen, dass es im Allgemeinen eine schlechte Datenstruktur ist. In den üblichen Fällen, wo man ein herkömmliches, mehrdimensionales Array haben will, ist ein jagged Array allerdings tatsächlich keine gute Lösung.

Beachte: Wir sprechen hier von einem std::vector, dessen Elemente selbst vom Typ std::vector sind. Die entstehende Datenstruktur ist zwar zweidimensional, es handelt sich dabei aber eben um eine ganz bestimmte Art einer zweidimensionalen Datenstruktur die sich, wie wir gleich sehen werden, fundamental von dem, was man sich normalerweise unter einem zweidimensionalen Array vorstellt, unterscheidet.

Auch bin ich mir über deine Definition/Vorstellung von Jagged Array nicht ganz bewusst, Google lieftert mir da eigentlich nur Artikel zu C#.

Unter einem Jagged Array versteht man ein Array aus Arrays, wobei jedes dieser Unterarrays eine andere Länge haben kann (hence the name). Im zweidimensionalen Fall sieht das also z.B. so aus:

Quellcode

1
2
3
4
5
6
7
TTTTTTTTTTTTTTTTTTTT
TTTT

TTTTTTT
TT
TTT
T

Und genau so etwas ist auch unser std::vector<std::vector<T>>, denn ein std::vector<T> ist ein Array dynamischer Länge aus T und ein std::vector<std::vector<T>> ist damit ein Array dynamischer Länge aus Arrays dynamischer Länge aus T.

Eigentlich wollen wir aber einfach nur sowas

Quellcode

1
2
3
4
5
6
7
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTTTTTT

Natürlich kann man sowas als Spezialfall eines jagged Array betrachten und in einem std::vector<std::vector<T>> speichern. Das ist aber sicherlich keine besonders gute Lösung, denn wir bedienen uns damit einer für unsere Anwendung in absolut jeder nur erdenklichen Weise unnötig komplizierten Datenstruktur. Alles was wir eigentlich haben wollen, ist ein Speicherblock der M*N Elemente vom Typ T enthält. Ein std::vector<std::vector<T>> entspricht aber einem Speicherblock der die Adressen sowie Größen weiterer Speicherblöcke enthält, die dann erst die eigentlichen Daten enthalten. Ein std::vector<std::vector<T>> ist also schonmal gar kein zusammenhängender Block mehr, sondern im Allgemeinen fragmentiert; der Zugriff auf ein Element entspricht naturgemäß einer dopppelten Indirektion. Anstatt also direkt die Adresse des Elements auszurechnen und darauf zuzugreifen, müssen wir erst die Adresse der Adresse der Zeile/Spalte, die das Element enthält, berechnen, uns von dort die Adresse der Zeile/Spalte holen und dann die Adresse des Elements berechnen und darauf zugreifen. Die Speicherung der Länge jeder Zeile/Spalte ist natürlich zusätzlich redundant. Nichtmal ein einziger all der Aspekte, die sämtliche Komplexität dieser Datenstruktur ausmachen, ist für unsere Anwendung auch nur teilweise interessant.

Mit dem gleichen Argument könnte man auch eine verschachtelte std::map verkaufen...
Die STL wurde so entworfen, dass die Verwendung von dessen Containern keinen grossen Einfluss auf die Laufzeit oder den Speicher hat. Obwohl es natürlich in den meisten Fällen keinen Sinn macht std::map's zu verschachtel, so werden die Prinzipien von C++ damit nicht verletzt.
Ich kann hier zwar meistens nur Behauptungen aufstellen, weil ich nicht den Standard gelesen habe, aber ich vertraue darauf, dass die Implementierungen von der STL und die Optimierungen von den Compilers die beste Performance für den gegebenen Code generieren.

Der Standard macht keine Aussage darüber, wie eine Implementierung genau auszusehen hat, aber ja, der Compiler wird in der Tat relativ optimalen Code für deinen std::vector<std::vector<T>> generieren. Leider ist diese Datenstruktur eben für das zu lösende Problem nicht optimal. Du kannst aber natürlich darauf vertrauen, dass der Compiler den bestmöglichen Maschinencode für deine suboptimale Lösung erzeugen wird... ;)

Wenn du schreibst std::vector<std::vector<T>>, dann sagst du: Ich will ein Array dynamischer Länge aus Arrays dynamischer Länge aus T. Und genau das bekommst du auch. Dass du eigentlich ein Array dynamischer Größe aus NxM T wolltest, kann C++ nicht erraten.

Das Problem mit dem CPU Cache tritt eigentlich nur auf, wenn man falsch durch ein mehr dimensionales Array iteriert, sprich zuerst die Zeilen und dann die Spalten.

Richtig, wenn man von einem entsprechend großen, mehrdimensionalen Array in column major Memory Layout ausgeht. Ein std::vector aus std::vector ist nur eben etwas völlig anderes...

Zitat von »eXpl0it3r«

Zitat von »dot«

Der Grund, wieso ich davon abrate, ist in erster Linie, dass es strukturell nicht dem zu lösenden Problem entspricht.

Wieso sollte dann ein eindimensionales Array von der Struktur her besser sein für das Problem?
Weil das Problem zweidimensionaler Natur ist und ein zweidimensionales Array hier die einfachste, intuitivste und effizienteste mir bekannte Lösung ist.
Wie bist du jetzt wieder von einem eindimensionalen Array, wie vorgeschlagen in deinem ersten Post, auf ein zweidimensionales Array gekommen? Und wieso ist ein zweidimensionales Array besser als ein zweidimensionaler std::vector?

Ich meinte ein herkömmliches zweidimensionales Array, das üblicherweise eben über eine bijektive Abbildung zweidimensionaler Indices in einen zusammenhängenden eindimensionalen Speicherbereich implementiert wird...

Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von »dot« (09.08.2012, 03:44)


eXpl0it3r

Treue Seele

  • »eXpl0it3r« ist der Autor dieses Themas

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

7

09.08.2012, 22:50

So nach ein bisschen Chatten mit ein paar anderen erfahrenen Entwicklern und etwas nachschlagen im Standard stimme ich dir zu, dass std::vector<std::vector<T> > nicht eine der besten Datenstrukturen ist.
Nun kommt aber das grosse ABER, der einzige Grund warum, dass ein "2D" vector schlecht ist, ist das Memory Layout, was natürlich zu einer etwas schlechteren Performance führt.
Es mag wohl nun gut sein sich selbst Prinzipien anzueignen, welche solche nicht optimalen Strukturen verhindert, jedoch zum Preis von etwas mehr Programmieraufwand und möglichen Fehlernquellen, jedoch ist es keine Hilfe für Anfänger solche Optimierungsprinzipien bei bringen zu wollen. Für einen Anfänger ist es bereits genug schwer zu verstehen, wie man mit Indizes umgeht und dann sollte er das ganze 1D zu 2D bzw. umgekehrt selbst von Hand machen und dabei nie einen Fehler machen und das Ganze nur um etwas mehr Performance für die Applikation zu erhalten, welche wahrscheinlich 200 andere schlechte und evtl. sogar gefährliche Codestellen hat?
Noch zwei weiter Punkte, der Gewinn durch das Verwenden von einem 1D Array/Vector ist meist sehr minimal vorallem im Vergleich zum zusätzlichen Aufwand den man hat, da man selten mit 10'000 von Elementen zu tun hat. Obwohl ich deinen Code (Art) nicht kenne, so wage ich trotzdem zu behaupten, dass du an anderen Stellen viel schlimmere schlecht performante Operationen durchführst und sich somit der ganze Aufwand eigentlich gar nicht gross lohnt. (Das ist nur eine Behaupt basierend auf der Tatsache, dass es keinen "perfekten" Code gibt.) ;)

Zitat von »dot«

Richtig, wenn man von einem entsprechend großen, mehrdimensionalen Array in column major Memory Layout ausgeht. Ein std::vector aus std::vector ist nur eben etwas völlig anderes...

Ich meinte ein herkömmliches zweidimensionales Array, das üblicherweise eben über eine bijektive Abbildung zweidimensionaler Indices in einen zusammenhängenden eindimensionalen Speicherbereich implementiert wird...
Zja der Standard sagt aber etwas anderes über mehrdimensionale Arrays:

Zitat von »ISO/IEC 14882-2011 § 8.3.4-7«

A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i×j ×. . .×k, then E appearing in an expression that is subject to the array-to-pointer conversion (4.2) is converted to a pointer to an (n−1)-dimensional array with rank j ×. . .×k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n − 1)-dimensional array, which itself is immediately converted into a pointer.
Das heisst somit, dass ein zweidimentionales Array (Bsp. int a[10][10];) ebenfalls einen fragmentierten Speicherbereich aufweist.

Fazit
Obwohl std::vector<std::vector<T> > und T[x][y] nicht optimal im Speicher ausgelegt werden und deshalb zu gewissen Performance einbrüchen führen kann, insbesondere bei grossen Datenmengen (n > 10000), so ist es für kleine Datenmengen durchaus in Ordnung diese zu verwenden. Ausnahmen wie sehr Zeitkritische Prozeduren o.ä. werden hier ausgeklammert.
Für maximale Performance ist es empfohlen std::vector<T> oder T[x][y] zu verwenden, wobei hier angemerkt werden sollte, dass std::vector<T> keinerlei Nachteilen über einem nativ Array haben und sogar in Verbindung mit Iteratoren schneller sein können (entweder hier oder hier erwähnt), ausserdem bietet std::vector<T> weiter Funktionalitäten an und das komplette manualle Speichermanagment entfällt.
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

idontknow

unregistriert

8

09.08.2012, 23:16

std::tr1::array<std::tr1::array<T, Width>, Height>. So einfach. Diskussion vorbei.

edit: fürn TE: http://msdn.microsoft.com/en-us/library/bb983093.aspx
edit2: ich geh bei nem einfacheren Spiel vom TE einfach mal davon aus, dass jedes level gleich groß ist. Ansonsten is tr1::array natürlich quatsch.

eXpl0it3r

Treue Seele

  • »eXpl0it3r« ist der Autor dieses Themas

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

9

09.08.2012, 23:37

std::tr1::array<std::tr1::array<T, Width>, Height>. So einfach. Diskussion vorbei.
Das bringt genau nichts, da std::array genau so fragementierte Speicherbereiche hat... :pinch:
Für VS 2010 und GCC 4.3 kann das tr1 überigends ruhig weggelassen werden, aber man darf nicht vergessen <array> zu importieren. ;)

Hier noch eine weitere Referenzdazu.
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

idontknow

unregistriert

10

09.08.2012, 23:39

Das Problem mit den jagged Arrays ist zumindest weg, bzw., dass Zeilen theoretisch eine unterschiedliche Länge haben können. letztlich wäre das beste was dot gesagt hat. Finde die Indexberechnung von 2D in 1D jetzt auch nicht so wild als dass man einen Anfänger damit überlasten würde oder sowas..

Werbeanzeige