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

LukasBanana

Alter Hase

  • »LukasBanana« ist der Autor dieses Themas

Beiträge: 1 097

Beruf: Shader Tools Programmer

  • Private Nachricht senden

1

13.07.2013, 21:28

std::vector::push_back: Optimierungsfrage

Hi, ich habe mich gerade gefragt, ob ein aktueller C++ Compiler (z.B. VisualC++ 2008) folgendes automatisch optimieren kann:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Nicht optimiert:
for (int i = 0; i < VariableCount; ++i)
{
    MyStruct NewElement;
    {
        NewElement.Field0 = /* ... */
        NewElement.Field1 = /* ... */
        /* ... */
        NewElement.FieldN = /* ... */
    }
    MyContainer.push_back(NewElement);
}

// Optimiert:
for (int i = 0; i < VariableCount; ++i)
{
    MyContainer.resize(MyContainer.size() + 1);
    
    MyStruct& CurrentElement = MyContainer.back();
    {
        NewElement.Field0 = /* ... */
        NewElement.Field1 = /* ... */
        /* ... */
        NewElement.FieldN = /* ... */
    }
}

Wobei "VariableCount" große Werte annehmen kann (1000+).

Die Optimierung besteht hier also nur darin, dass nicht eine große Struktur mit N Elementen angelegt wird und dann durch "push_back" in den Vektor kopiert wird.
Sondern es soll direkt das neue Element des Vektors initialisiert und nicht erst viel hin und her kopiert werden.

Meine Frage also: kann ein aktueller Compiler so was automatisch optimieren? Schließlich kennt er die Standardbibliothek, also auch den std::vector und std::list etc.
Ich finde die nicht optimierte Variante nämlich etwas komfortabler zu schreiben also die untere.

TGGC

1x Rätselkönig

Beiträge: 1 799

Beruf: Software Entwickler

  • Private Nachricht senden

2

13.07.2013, 21:42

Mach doch eine Funktion, die dein Element dem Vektor hinzufuegt. Die kannst du dann beliebig optimieren ohne das der Aufruf komplizierter zu schreiben waere.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

3

13.07.2013, 21:50

Gib MyStruct einen entsprechenden Konstruktor und verwend v.emplace_back(...) oder zumindest v.push_back(MyStruct(...)) ;)
emplace_back() konstruiert das Objekt direkt im vector und die push_back() Variante mit dem temporären Objekt als Parameter sollte dem Compiler zumindest theoretisch erlauben, unnötige Kopien auszulassen und das Objekt ebenfalls direkt im vector zu konstruieren. Alle einigermaßen aktuellen Compiler (selbst VS 2008 ) machen von copy elision Gebrauch; wie gut das mit std::vector funktioniert, hängt natürlich von der jeweiligen Implementierung der Standardbibliothek ab, hab den konkreten Fall noch nie wirklich getestet...

Mit einem wirklich aktuellen Compiler, der initializer lists unterstützt, (VS 2013), kannst du auch sowas machen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>

struct A
{
  int a;
  float b;
  void* c;
};

int main()
{
  std::vector<A> v;

  v.push_back({0, 1.0f, nullptr});
}

Dieser Beitrag wurde bereits 13 mal editiert, zuletzt von »dot« (13.07.2013, 22:25)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

4

14.07.2013, 09:20

Ganz ehrlich? Wer macht sich Gedanken um solch premature optimizations?
Und ich mag mich irren, aber wäre es nicht cleverer den Vektor nur einmal zu resizen und dann darin den Elementen alle Werte zuzuweisen als das innerhalb der Schleife immer wieder zu machen?
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

TGGC

1x Rätselkönig

Beiträge: 1 799

Beruf: Software Entwickler

  • Private Nachricht senden

5

14.07.2013, 09:58

Kommt drauf an, was im Defaultkonstruktor passiert.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

6

14.07.2013, 10:05

Ganz ehrlich? Wer macht sich Gedanken um solch premature optimizations?

Zumindest ich hab das eigentlich so verstanden, dass es sich hier um eine Frage prinzipieller Natur handelt und seh eigentlich nicht wirklich, was das jetzt genau mit premature Optimization zu tun hätte. Passiert dir das nie, dass du dich bei gewissen grundlegenden Dingen, die du ständig machst, manchmal fragst, welche von all den verschiedenen Möglichkeiten das Gleiche zu tun nun eigentlich die effizienteste ist!? Mir geistern solche Fragen, selbst wenn ich gerade Python Skripte schreib, andauernd im Kopf herum... ;)

Und ich mag mich irren, aber wäre es nicht cleverer den Vektor nur einmal zu resizen und dann darin den Elementen alle Werte zuzuweisen als das innerhalb der Schleife immer wieder zu machen?

Hängt am Ende natürlich, wie immer, stark von den konkreten Gegebenheiten ab, aber rein prinzipiell ist es selbstverständlich sinnvoller, den vector nur einmal zu resizen, wenn man die Größe vorher kennt. Wenn man die Größe vorher kennt und diese sich nie mehr ändern muss, könnte man evtl. überhaupt einfach nur einen unique_ptr verwenden statt einen vector, was möglicherweise nochmal ein wenig effizienter sein könnte. Aber das resize() in jeder Iteration ist vermutlich meistens schon so schnell, dass man kaum einen Unterschied merken wird, zumindest nicht im Vergleich zu push_back, da der vector seine Größe auf eine Art und Weise ändert, dass die amortisierte Laufzeit eines resize O(1) ist (ja, mit ausreichend kleinen Konstanten ;))...

Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von »dot« (14.07.2013, 10:32)


CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

7

14.07.2013, 13:26

Zur eingangs gezeigten optimierten Variante bzw. resize(): Prinzipiell haben diese beiden Varianten in Bezug auf Verhalten im Fehlerfall jeweils eine andere Bedeutung als die "unoptimierte" Variante. Ist der vector lokal und fliegt dieser somit im Fehlerfall weg, ist das egal. Übersteht der vector jedoch Fehler, hat er nach Auftreten eines solchen ggf. einen inkonsistenten Zustand: Seine Größe entspricht dann nicht mehr der Zahl gültiger/initialisierter Elemente. Eine sinnvolle und effiziente Variante wäre die folgende:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyContainer.reserve(VariableCount);
for (int i = 0; i < VariableCount; ++i)
{
    // ... irgendwas ... 
    {
        MyStruct NewElement;
        NewElement.Field0 = /* ... */
        NewElement.Field1 = /* ... */
        /* ... */
        NewElement.FieldN = /* ... */
        MyContainer.push_back(std::move(NewElement));
    }
    // ... irgendwas ...
}


Man beachte move und die Klammerung, damit NewElement nach dem move nicht weiter verwendet wird. Für optimalen Code erfordert das Move Semantics für MyStruct - die laut C++11 automatisch generiert werden sollten - die Deppen bei MS bringen das aber frühestens zum VC++ 2013.5 Tech Preview raus.

Eine zweite Variante, in der du auch von Return Value Optimization profitieren kannst und in der das move andernfalls implizit durchgeführt wird, ist der Einsatz eines Lambdas:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyContainer.reserve(VariableCount);
for (int i = 0; i < VariableCount; ++i)
{
    // ... irgendwas ... 
    MyContainer.push_back([&]() -> MyStruct // nervig: Rückgabetyp kann erst ab C++14 sicher weggelassen werden
    {
        MyStruct NewElement;
        NewElement.Field0 = /* ... */
        NewElement.Field1 = /* ... */
        /* ... */
        NewElement.FieldN = /* ... */
        return NewElement;
    }());
    // ... irgendwas ...
}


Ich behaupte nicht, dass das hübsch ist. Aber da implizites Move von temporären Objekten meiner Meinung nach explizitem Move grundsätzlich vorzuziehen ist, wäre das wohl die empfohlene Variante. (Explizites Move erfordert immer eine Zusatzklammerung analog zur oben gezeigten oder das Wissen, dass das verschobene Objekt im Anschluss nicht länger gültig ist.)

Nebenbemerkung: Für maximale Effizienz sollte vector eigentlich eine allocate()-Methode analog zu reserve() anbieten, die ein vector_range-Objekt zurückgibt, welches push_back(), emplace_back() etc. anbietet. Dann fiele in der Schleife tatsächlich sämtlicher Maschinencode für potentielle Reallokation weg; ohne eine solche Funktionalität ist der Compiler gezwungen, auch bei vorangestelltem reserve() in der Schleife immer Code für Reallokation zu generieren, der dann tatsächlich niemals ausgeführt wird.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »CodingCat« (14.07.2013, 13:35)


dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

8

14.07.2013, 13:35

In keiner der beiden Varianten wird es dem Compiler aber möglich sein, das Objekt direkt im vector zu konstruieren, oder überseh ich was!?

CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

9

14.07.2013, 13:41

In keiner der beiden Varianten wird es dem Compiler aber möglich sein, das Objekt direkt im vector zu konstruieren, oder überseh ich was!?

Nein, dazu bedarf es dann tatsächlich eines Konstruktors oder einer Initialisierungsliste (geht tatsächlich ab VC 2013!), jeweils mit emplace_back. In beiden Fällen ändert sich die Notation leider grundlegend, mit bedeutendem Informationsverlust.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

10

14.07.2013, 13:51

In keiner der beiden Varianten wird es dem Compiler aber möglich sein, das Objekt direkt im vector zu konstruieren, oder überseh ich was!?

Nein, dazu bedarf es dann tatsächlich eines Konstruktors oder einer Initialisierungsliste (geht tatsächlich ab VC 2013!), jeweils mit emplace_back. In beiden Fällen ändert sich die Notation leider grundlegend, mit bedeutendem Informationsverlust.

Dazu sollte man aber noch bemerken, dass sich bei ordentlicher Implementierung von Move Semantics der generierte Code einer Verschiebung des Objekts kaum von dem einer in-place Konstruktion mittels emplace_back-Forwarding unterscheiden sollte.
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

Werbeanzeige