[C++] copy overhead vermijden bij return value

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
Ik wil de return data van een functie zonder onnodige stack copies in een vector duwen, en dacht hiervoor een reference variable te gebruiken. Zie hieronder.
Bestaat er geen cleanere oplossing? Ik heb het gevoel dat ik hier onnodige omwegen neem.
(ik duw het niet onmiddellijk in de vector omdat ik nog even doStuff wil aanroepen zonder in de vector te moeten gaan)

C++:
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
27
28
#include <vector>

using std::vector;

class HugeObj{
public:
    HugeObj(unsigned int i = 0) : i_(i){}
    void doStuff() { /* ... */}
private:
    unsigned int i_;
};

HugeObj produce(unsigned int i){
  return HugeObj(i);
}

int main(int argc, char* argv[])
{
    HugeObj& current = HugeObj();
    vector<HugeObj> storage;
    unsigned int i = 0;
    while(i!=100){
        current = produce(i);
        current.doStuff();
        storage.push_back(current);
        ++i;
    }
}

Acties:
  • 0 Henk 'm!

  • StM
  • Registratie: Februari 2005
  • Laatst online: 17-09 13:11

StM

Waarom maak je je object niet dynamisch aan en geef je hem de pointer?

edit: ik zie je wel meer rare dingen doen. Ga je eens inlezen op pointers.

[ Voor 35% gewijzigd door StM op 04-11-2009 15:34 ]


Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 21-08 21:44
Een C++ compiler zal in dit geval als optimalisaties aan staan hier al geen kopie teruggeven.
C++:
1
2
3
HugeObj produce(unsigned int i){ 
  return HugeObj(i); 
} 


Trouwens waarom bestaat die produce funtie in dit geval, of is dat normaal een factory functie die daar wordt gebruikt. En zo ja dan zou je daarvan altijd al een pointer naar een base class willen terug geven. Normaal vermijd je een kopie aanmaken door een const reference of een pointer uit een functie terug te geven.

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
Site.to.Make schreef op woensdag 04 november 2009 @ 15:32:
Waarom maak je je object niet dynamisch aan en geef je hem de pointer?

edit: ik zie je wel meer rare dingen doen. Ga je eens inlezen op pointers.
Ik weet hoe pointers werken, maar wilde in dit geval een oplossing zonder pointers proberen te maken. Kwestie van later niet meer te moeten destructen.
NC83 schreef op woensdag 04 november 2009 @ 15:36:
Een C++ compiler zal in dit geval als optimalisaties aan staan hier al geen kopie teruggeven.
C++:
1
2
3
HugeObj produce(unsigned int i){ 
  return HugeObj(i); 
} 


Trouwens waarom bestaat die produce funtie in dit geval, of is dat normaal een factory functie die daar wordt gebruikt. En zo ja dan zou je daarvan altijd al een pointer naar een base class willen terug geven. Normaal vermijd je een kopie aanmaken door een const reference of een pointer uit een functie terug te geven.
produce is hier slechts een artificieel voorbeeld, in mijn echte code doet die uiteraard iets nuttigs :) (een vector met een reeks pointers produceren om precies te zijn)
Maar als de compiler het optimaliseert is het natuurlijk ook goed, dan moet ik niet zo knoeien.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

NC83 schreef op woensdag 04 november 2009 @ 15:36:
Een C++ compiler zal in dit geval als optimalisaties aan staan hier al geen kopie teruggeven.
C++:
1
2
3
HugeObj produce(unsigned int i){ 
  return HugeObj(i); 
} 
Daar wel ja, maar dat is het punt niet. De push_back() zal sowieso resulteren in een kopie.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream> 
#include <vector>

using namespace std; 

struct Foo
{
    Foo() { std::cout << "Foo()" << std::endl; }
    Foo(const Foo &) { std::cout << "Foo(const Foo &)" << std::endl; }
};

Foo CreateFoo()
{
    return Foo();
}

int main (int argc, char * const argv[])
{
    const Foo & foo = CreateFoo();
    std::vector<Foo> v;
    v.push_back(foo);
}

Output:
Foo()
Foo(const Foo &)

Zelfs als je push_back(Foo()) doet hou je nog steeds het probleem, gezien de interne werking van std::vector (typisch een new(ptr) T(copy), met 'ptr' een pointer naar waar het object moet komen en 'copy' het argument van push_back()).

C++0x fixt hier wat issues mee. Ten eerste heb je emplacement construction waardoor je direct een T kunt constructen *in* de container, ipv er een naartoe te kopieëren. Daarnaast heb je r-value references, waardoor je goedkoop geheugen kunt moven van het ene object naar het andere.

@Bob: er zit trouwens een fout in regel 19. Je mag een temporary niet aan een non-const reference binden. MSVC++ laat dit weliswaar toe, maar het is foute C++ code.

Overigens moet je niet vergeten dat vector al helemaal een slechte container is als het kopieëren van de objecten die je erin stopt duur is. Een vector zet alle objecten namelijk achter elkaar in het geheugen. Als je er een object aan toevoegt terwijl dat niet meer past (capacity() == size()), zal hij een nieuwe grotere buffer moeten alloceren en alle oude contents naar de nieuwe moeten kopieëren, waardoor hij dus elk object in de vector gaat copy constructen. Als je de eigenschappen van een vector wilt behouden, kun je het beste een vector van (smart)pointers gebruiken. Dan is een resize goedkoop, en hoef je ieder object maar 1 keer te constructen.

[ Voor 34% gewijzigd door .oisyn op 04-11-2009 16:13 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:13
Ja, de compiler is expliciet toegestaan om een temporary object helemaal weg te optimaliseren (zelfs als dat de werking van het programma verandert!) Dat staat bekend als Return Value Optimization.

In jouw code betekent dat dat de assignment op regel 23 weggeoptimaliseerd kan worden (ook al is produce ingewikkelder). Wellicht wordt er op regel 25 dan alsnog een kopie gemaakt om het object daadwerkelijk op te slaan in de vector (dat hangt af van de implementatiedetails, maar ik vermoed dat die kopie onvermijdelijk is, omdat op dat punt zowel current als storage[i] een kopie moeten bevatten, en aangezien geen van beide temporaries zijn, kun je er niet eentje weghalen).

Tenslotte zit er een fout in je voorbeeldcode, want regel 19 kan niet: als current een reference variable is, dan kun je 'm niet initialiseren met een temporary (want die levert een const reference op). Ik weet niet hoe je zo op deze code gekomen bent; als je in je echte code wel met een reference variable werkt, gaat het hele verhaal in deze thread niet op.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Soultaker schreef op woensdag 04 november 2009 @ 16:02:
In jouw code betekent dat dat de assignment op regel 23 weggeoptimaliseerd kan worden (ook al is produce ingewikkelder).
Nee, dat kan dus niet. 'current' is namelijk al geconstruct, en dus resulteert het hoe dan ook in een operator=(). Als je echt van RVO gebruik wilt maken zul je het resultaat van de functie moeten binden aan een object wat je aan het constructen bent, dus "HubeObj current = produce(i);".

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
Soultaker schreef op woensdag 04 november 2009 @ 16:02:


Tenslotte zit er een fout in je voorbeeldcode, want regel 19 kan niet: als current een reference variable is, dan kun je 'm niet initialiseren met een temporary (want die levert een const reference op). Ik weet niet hoe je zo op deze code gekomen bent; als je in je echte code wel met een reference variable werkt, gaat het hele verhaal in deze thread niet op.
Gek, het compileert en runt zonder troubles.
Vanuit het standpunt van de compiler kan het (volgens mijn basic compiler kennis) toch?
Object (temporary) wordt op de stack gemaakt.
Reference variable wordt er aan gehangen, het object heeft dus plots een 'touwtje'. (symbol?)
Wanneer current out of scope gaat kan het object nog steeds gedestroyed worden.

Op die manier zie ik geen probleem, maar als jullie beiden zeggen dat het niet kan/mag, zal mijn visual c++ compiler hier dingen doen die niet mogen...

Voor de volledigheid, heb de code nog wat uitgebreid om het probleem verder te testen, het compileert en runt.

C++:
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
27
28
29
30
31
32
33
34
#include <vector>
#include <iostream>

using std::vector;
using std::cout;
using std::endl;

class HugeObj{
public:
    HugeObj(unsigned int i = 0) : i_(i){}
    void doStuff(){cout << i_ << endl;}
    void setI(unsigned int i){i_ =i;}
private:
    unsigned int i_;
};

HugeObj produce(unsigned int i){
  return HugeObj(i);
}

int main(int argc, char* argv[])
{
    HugeObj& current = HugeObj();
    current.setI(3);

    vector<HugeObj> storage;
    unsigned int i = 0;
    while(i!=100){
        current = produce(i);
        current.doStuff();
        storage.push_back(current);
        ++i;
    }
}

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoals ik al zei, MSVC++ laat het toe, maar het mag officieel niet. Erg veel nut heeft het trouwens ook niet, je code is equivalent als je de reference weghaalt.

[ Voor 41% gewijzigd door .oisyn op 04-11-2009 16:34 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
.oisyn schreef op woensdag 04 november 2009 @ 16:31:
Zoals ik al zei, MSVC++ laat het toe, maar het mag officieel niet.
Ik ga weer op leescursus.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik had het er later bij-geëdit, wellicht stond het er nog niet bij toen jij m'n post las ;)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
.oisyn schreef op woensdag 04 november 2009 @ 16:38:
Ik had het er later bij-geëdit, wellicht stond het er nog niet bij toen jij m'n post las ;)
Zo doen we dat dus ... :)

Verder (ik zou beter m'n werk afmaken maar goed, ik leer hier nuttige dingen): wat bedoel je juist met de code is niet nuttig met die reference? In geval van optimalisatie is het wslk niet nuttig nee, bedoel je dat?

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik bedoel dat de reproduceerde assembly waarschijnlijk 100% identiek is. In beide gevallen leeft 'current' op de stack in de functie main(). In de variant met reference is het een temporary waar je een referentie naar hebt genaamd 'current' (waardoor die temporary de lifetime van 'current' meekrijgt), maar omdat de compiler weet waar dat ding op de stack staat en waar de reference naar wijst zal 'current' zich dus net zo gedragen als het geen reference was geweest.

Het is niet dat je op regel 29 de reference naar iets anders laat wijzen, want dat kan namelijk niet. Een reference laat je alleen bij construction naar iets wijzen, en vanaf dat moment doet het net alsof je tegen het object zelf praat. Op regel 29 krijg je dus gewoon een assignment aan de temporary die je op regel 23 gemaakt hebt.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 15-09 23:43
Nu ben ik weer helemaal mee. Mijn poging om het effect van rvalue references te verkrijgen sloeg dus nergens op, en is hier ook niet eens nodig bij de lijn current = produce(i);, omdat de compiler het beter kan ...

Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 21-08 21:44
Als je het wel naar iets anders wil laten wijzen zul je dus een pointer moeten gebruiken. Daarnaast als je je vector dan declreert om pointers naar hugeObj's te bevatten wordt de overhead van de vector al een stuk minder.

Hou er wel rekening mee dat als je de vector leeg maakt je eerst de objecten opruimte en dan pas vector.clear of swap doet met een lege vector.

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 17-09 14:05

.oisyn

Moderator Devschuur®

Demotivational Speaker

Of je gebruikt std::vector<std::tr1::shared_ptr<HugeObj>>, dan worden objecten automatisch opgeruimd ;). Maar een std::list<HugeObj> is handiger als je geen random access container nodig hebt.

[ Voor 29% gewijzigd door .oisyn op 04-11-2009 17:04 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 03:13
.oisyn schreef op woensdag 04 november 2009 @ 16:11:
Nee, dat kan dus niet. 'current' is namelijk al geconstruct, en dus resulteert het hoe dan ook in een operator=(). Als je echt van RVO gebruik wilt maken zul je het resultaat van de functie moeten binden aan een object wat je aan het constructen bent, dus "HubeObj current = produce(i);".
Hmm, ik had uit de losse pols geredeneerd dat current dan gedestruct zou worden voor de call, maar dat werkt natuurlijk niet zo. De (hier impliciete) assignment operator wordt gecalld, niet de copy constructor.

Re: vectors: als je in het echt (zoals in je voorbeeld) weet hoeveel elementen in je vector gaat stoppen kun je natuurlijk eerst genoeg ruimte reserveren (met reserve() of resize()) dat scheelt al de helft van het probleem. Verder zou ik me in 't algemeen niet zo druk maken om dit soort optimalisaties, eerlijk gezegd.

[ Voor 21% gewijzigd door Soultaker op 04-11-2009 17:11 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-09 22:43
Soultaker schreef op woensdag 04 november 2009 @ 17:09:
Verder zou ik me in 't algemeen niet zo druk maken om dit soort optimalisaties, eerlijk gezegd.
Zijn keuze voor de naam HugeObj suggereert dat het wel relevant is.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op woensdag 04 november 2009 @ 16:58:
Of je gebruikt std::vector<std::tr1::shared_ptr<HugeObj>>
Dat lijkt mij verreweg de beste oplossing en is iets dat ik zelf ook altijd doe.

C++ mantra: every problem can be solved by an extra layer of wrapping.

[ Voor 12% gewijzigd door Zoijar op 04-11-2009 19:07 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-09 22:43
Zoijar schreef op woensdag 04 november 2009 @ 18:52:
C++ mantra: every problem can be solved by an extra layer of wrapping.
... except too many layers of wrapping ? :P

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

farlane schreef op woensdag 04 november 2009 @ 21:11:
[...]

... except too many layers of wrapping ? :P
Nee, dat is het grappige: je hebt nooit te veel layers of wrapping :+
Pagina: 1