Häufig benutzte Variablen in Schleifen innen / außen deklarieren?



  • Erhard Henkes schrieb:

    Genau genommen habe ich eine 2D Vektor Klasse, wo ich bei einem Vektor eine Komponente verändern will.

    Na, dann lag ich mit meiner Testklasse xINT doch gar nicht so schlecht. 😉

    verschmutzt keinen Scope

    Wenn sonst alles sauber bleibt. 😃

    Ich habe mal das Beispiel oben laufen lassen.

    Variante 1 (innen) brauchte: 203ms
    Variante 2 (aussen) brauchte: 219ms
    Variante 3 (set/get) brauchte: 250ms
    Variante 4 (member) brauchte: 203ms

    Compiler/IDE: Code::Blocks 8.02

    Variante 1 (innen) brauchte: 63ms
    Variante 2 (aussen) brauchte: 62ms
    Variante 3 (set/get) brauchte: 62ms
    Variante 4 (member) brauchte: 63ms

    Compiler/IDE: MS VC++ 6.0

    #include <iostream>
    #include <ctime>
    
    int main()
    {
    
    	using namespace std;
    
    	time_t t = clock();
    
    	for(int i = 0; i < 1000000; ++i)
    	{
    		string s;
    
    		s += string(50, 'a');
    	}
    
    	cout << clock() - t << '\n';
    
    	t = clock();
    
    	string s;
    
    	for(int i = 0; i < 1000000; ++i)
    	{
    		s.clear();
    
    		s += string(50, 'a');
    	}
    
    	cout << clock() - t << '\n';
    
        return 0;
    }
    

    Visual C++ 2008 Compiler

    Optimiert mit /Ox

    968ms
    672ms



  • fricky schrieb:

    Tachyon schrieb:

    Die Variante 2 ist besser, da bei der ersten Variante bei jedem Durchlauf Objekte auf dem Stack konstruiert und wieder zerstört werden müssen.

    ist aber nur blöd bei c++ objekten, die konstruktoren/destruktoren aufrufen. bei einfachen typen wie int/long/char usw. macht das nix.
    🙂

    int, long, char,... sind auch objekte in c++, wo auch ein c'tor und d'tor aufgerufen werden muss
    z.B. int i(5); ist ein c'tor aufruf wie ausm lehrbuch mit einer einparametrigen übergabe.
    ... alles ist ein objekt und ne menge ein stream.



  • ... alles ist ein objekt

    nö! 🙄


  • Mod

    cubeman schrieb:

    So wie es ausschaut bleibe ich bei der Innenvariante. Ist klar, verschmutzt keinen Scope und ist schnell genug 👍

    So wie es ausschaut, ist das grober Unfug. Ich hab mal die couts durch

    cout << (GetTickCount ()-startticks) << '\t';
    

    (und beim letzten dann endl) ersetzt, und das ganze in eine Schleife for(;;) gepackt. Ergebnis bei mir Visual C++ 9.0 Express optimiert

    391     312     313     297
    390     313     312     329
    343     313     344     328
    328     312     313     359
    313     312     328     328
    313     312     344     328
    312     313     328     328
    313     375     312     313
    312     328     313     359
    313     359     297     312
    344     328     313     312
    328     313     297     328
    359     297     313     328
    328     328     313     375
    312     313     312     359
    313     312     375     313
    297     312     438     328
    297     375     297     312
    344     391     296     313
    359     313     297     328
    422     312     313     297
    468     328     329     359
    312     313     328     344
    312     313     328     328
    344     312     360     312
    313     328     359     313
    312     328     313     312
    313     344     312     313
    312     344     328     312
    313     359     297     328
    344     297     312     313
    359     313     312     391
    312     329     312     344
    312     344     344     328
    312     313     359     313
    312     313     344     328
    312     328     328     391
    328     344     312     329
    312     375     297     328
    344     312     375     328
    360     312     313     375
    312     297     328     328
    

    usw. hier kann man viel erkennen, unter anderem das Timerintervall von Windows =16ms - aber eine Geschwindigkeitsmessung ist das nicht, schon gar nicht von dem, was du messen wolltest. Ich würde mal vermuten, dass hier viel Zeit mit den Streams verbracht wird.



  • camper schrieb:

    cubeman schrieb:

    So wie es ausschaut bleibe ich bei der Innenvariante. Ist klar, verschmutzt keinen Scope und ist schnell genug 👍

    So wie es ausschaut, ist das grober Unfug. Ich hab mal die couts durch

    So wie es ausschaut ist es aber so! Okay, die streams könnten das Ergebnis verfälschen, da hast du natürlich recht.

    #include <iostream>
    #include <ctime>
    
    using namespace std;
    
    class Vector2D
    {
    public:
    	Vector2D(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
    	int getX() { return x; }
    	int getY() { return y; }
    	void setX(int newx) { x = newx; }
    	void setY(int newy) { y = newy; }
    	int x, y;	// zum Testen public
    };
    
    Vector2D& getSomeVector()
    {
    	static Vector2D s_foo(1,2);
    	return s_foo;
    }
    
    int main()
    {
    	const unsigned long LOOPCOUNT = 1000000000;
    
    	// Variante 1 (innen)
    	clock_t startticks = clock ();
    	for (unsigned long l = 0; l < LOOPCOUNT; ++l)
    	{
    		Vector2D myVec = getSomeVector();
    		myVec.setX (3);
    	}
    	cout << "Variante 1 (innen) brauchte:   " << ((clock ()-startticks)/ (double) CLOCKS_PER_SEC) << "s" << endl;
    
    	// Variante 2 (aussen)
    	Vector2D myVec;
    	startticks = clock ();
    	for (unsigned long l = 0; l < LOOPCOUNT; ++l)
    	{
    		myVec = getSomeVector();
    		myVec.setX (3);
    	}
    	cout << "Variante 2 (aussen) brauchte:  " << ((clock ()-startticks)/ (double) CLOCKS_PER_SEC) << "s" << endl;
    
    	// Variante 3 (set/get)
    	startticks = clock ();
    	for (unsigned long l = 0; l < LOOPCOUNT; ++l)
    	{
    		myVec.setY (getSomeVector().getY());
    		myVec.setX (3);
    	}
    	cout << "Variante 3 (set/get) brauchte: " << ((clock ()-startticks)/ (double) CLOCKS_PER_SEC) << "s" << endl;
    
    	// Variante 4 (member)
    	startticks = clock ();
    	for (unsigned long l = 0; l < LOOPCOUNT; ++l)
    	{
    		myVec.y = getSomeVector().y;
    		myVec.x = 3;
    	}
    	cout << "Variante 4 (member) brauchte:  " << ((clock ()-startticks)/ (double) CLOCKS_PER_SEC) << "s" << endl;
    	return 0;
    }
    

    Besser?

    So, das hab ich mal auf meinem P233 MMX laufen lassen:

    #g++ --pedantic loopvar.cpp -O3 -o loopvar
    #./loopvar
    Variante 1 (innen) brauchte:   18.58s
    Variante 2 (aussen) brauchte:  23.21s
    Variante 3 (set/get) brauchte: 23.35s
    Variante 4 (member) brauchte:  23.22s
    

    Auch unter VS2005 ist Variante 1 bei mir zumindest gleich schnell!
    👍



  • princess schrieb:

    fricky schrieb:

    Tachyon schrieb:

    Die Variante 2 ist besser, da bei der ersten Variante bei jedem Durchlauf Objekte auf dem Stack konstruiert und wieder zerstört werden müssen.

    ist aber nur blöd bei c++ objekten, die konstruktoren/destruktoren aufrufen. bei einfachen typen wie int/long/char usw. macht das nix.
    🙂

    int, long, char,... sind auch objekte in c++, wo auch ein c'tor und d'tor aufgerufen werden muss

    aber nur theoretisch. in wirklichkeit passiert da fast nix.
    🙂


  • Mod

    cubeman schrieb:

    So wie es ausschaut ist es aber so! Okay, die streams könnten das Ergebnis verfälschen, da hast du natürlich recht.

    was denn nun? entweder ist dein Meßverfahren brauchbar oder es ist unbrauchbar. Ich behaupte Letzteres. Mit unbrauchbaren Meßverfahren kann man nichts beweisen.
    Vom theoretischen Standpunkt aus gibt es zwischen den einzelnen Varianten keinen prinzipiellen Unterschied, der unterschiedliche Laufzeiten bedingt. Das wiederum liegt am atypischen Fall der Refernzrückgabe in getSomeVector und der Tatsache, das der Funktionsaufruf wahrscheinlich ohnehin inline-substituiert wird, was as-if-Optimierungen erleichtert.

    Aber es wäre ja auch langweilig, Code zu testen, der einigermaßen normal geschrieben wurde...

    Ich widerspreche damit nicht der Schlussfolgerung selbst - aber es kann doch nicht so schwer sein, den Test so zu gestalten, dass er diese Argumentation tatsächlich stützt...



  • Natürlich ist das Beispiel nicht ideal, da es nur zusammengeschustert ist für diesen Test...

    Kann ich aber daraus ableiten, dass das Deklarieren von Variablen im Schleifenkörper keine dramatischen Performanceeinbußen bringt? Ich denke ja.

    camper schrieb:

    Aber es wäre ja auch langweilig, Code zu testen, der einigermaßen normal geschrieben wurde...

    Ich widerspreche damit nicht der Schlussfolgerung selbst - aber es kann doch nicht so schwer sein, den Test so zu gestalten, dass er diese Argumentation tatsächlich stützt...

    Vielleicht hast DU ja ein aussagekräftiges Beispiel (ohne dass ich das negativ meine!).



  • cubeman schrieb:

    Kann ich aber daraus ableiten, dass das Deklarieren von Variablen im Schleifenkörper keine dramatischen Performanceeinbußen bringt? Ich denke ja.

    Du könntest meinen Post lesen, der erklärt sehr schön wie es aussieht, weil dass was ihr hier an code gepostet habt ist zwar lustig, geht aber komplett am thema vorbei.

    Das einzige worum es hier gehen sollte ist RVO bzw. NRVO. Denn die entscheidet welche variante schneller ist. Dazu muss man aber (N)RVO erstmal verstanden haben...



  • Zunächst mal danke für das viele Feedback!
    Ok, hab mir das hier durchgelesen.

    Kann man also zusammenfassend sagen dass es

    • es auf die Funktion ankommt, die ein Objekt liefert, und
    • es zusätzlich Optimierungssache des Compilers ist?

    Am besten ist es wohl, im konkreten Fall einfach zu messen...


Anmelden zum Antworten