Häufig benutzte Variablen in Schleifen innen / außen deklarieren?
-
LordJaxom schrieb:
Du hast selbstverständlich den Releasemodus mit allen Optimierungen ausprobiert, oder?

Nein, ahbe ich nicht. Um an sowas zu denken, bin ich zu dämlich.

-
@Werner Salomon: Right Sir!

Jetzt wird es endlich praktisch.
-
Es gibt einen Fall, in dem beide Varianten gleich schnell sind, und zwar dann, wenn ich beide Varianten zusammen in ein Executable packe. Erstelle ich aber für beide Varianten getrennte Executables, ist die "Innen"-Variante immer langsamer als die "Außen"-Variante.
-
Tachyon schrieb:
LordJaxom schrieb:
Du hast selbstverständlich den Releasemodus mit allen Optimierungen ausprobiert, oder?

Nein, ahbe ich nicht. Um an sowas zu denken, bin ich zu dämlich.
Entschuldige, das wusste ich natürlich nicht. Kannst Du das in Deine Signatur schreiben? Dann sieht man es gleich und muss nicht fragen...
(Muss ich noch einen Smiley anbringen, um den humoristischen Charakter dieses Statements zu entschärfen?
)Spaß beiseite: Nicht nur Neulinge, auch erfahrene Forenbesucher dürfen gerne jegliche potentiell hilfreichen Angaben machen, um ein Problem oder eine Lösung nachzuvollziehen. Mich interessiert das Thema und natürlich auch Dein Test, sonst hätte ich nicht nachgefragt. Allerdings komme ich bei meinen lokalen Tests nur auf Faktor 1,5 - 2 (MSVC++ 8.0), da wäre es schon schön die Unterschiede zu kennen.
-
LordJaxom schrieb:
Tachyon schrieb:
LordJaxom schrieb:
Du hast selbstverständlich den Releasemodus mit allen Optimierungen ausprobiert, oder?

Nein, ahbe ich nicht. Um an sowas zu denken, bin ich zu dämlich.
Entschuldige, das wusste ich natürlich nicht. Kannst Du das in Deine Signatur schreiben? Dann sieht man es gleich und muss nicht fragen...
(Muss ich noch einen Smiley anbringen, um den humoristischen Charakter dieses Statements zu entschärfen?
)Sorry, ich habe meinerseits den Smily unterschlagen.

Immerhin scheinst Du auch einen Unterschied feststellen zu können. Ich habe hier einen relativ lahmen Firmenrechner mit einem P4. Vielleicht erklären sich daraus irgendwelche Unterschiede.
-
LordJaxom schrieb:
Spaß beiseite: Nicht nur Neulinge, auch erfahrene Forenbesucher dürfen gerne jegliche potentiell hilfreichen Angaben machen, um ein Problem oder eine Lösung nachzuvollziehen. Mich interessiert das Thema und natürlich auch Dein Test, sonst hätte ich nicht nachgefragt. Allerdings komme ich bei meinen lokalen Tests nur auf Faktor 1,5 - 2 (MSVC++ 8.0), da wäre es schon schön die Unterschiede zu kennen.
Hallo LordJaxom,
mein Code sieht so aus:
// includes .. struct S { std::string getString() const { return std::string( 1024, 'x' ); } }; int main() { S Foo; sam::Watch uhr; std::cout.setstate( std::ios_base::failbit ); { sam::Watch::Stopper stopper( uhr ); // std::string str; for (int i=0; i<10000; ++i) { std::string str = Foo.getString(); std::cout << str << std::endl; } } std::cout.clear(); std::cout << "benötigte Zeit. " << uhr << std::endl; return 0; }damit erreiche ich das Verhältnis 17msec für Variante 1 und 20msec für Variante 2 (MS-VC8). Die zuerst genannten Werte (14msec zu 17msec) erreiche ich mit einem leeren std::ostream.
Die Zeitmessung geschieht mit dem 'QueryPerformanceCounter' aus der WinAPI (Code nicht gepostet).
Ich denke, es hängt viel davon ab, wie die Funktion aussieht, die den String liefert.
Gruß
Werner
-
Tachyon schrieb:
Es gibt einen Fall, in dem beide Varianten gleich schnell sind, und zwar dann, wenn ich beide Varianten zusammen in ein Executable packe. Erstelle ich aber für beide Varianten getrennte Executables, ist die "Innen"-Variante immer langsamer als die "Außen"-Variante.
Was hat das mit der Executable zu tun?
Die innenvariante ist die schnellere wenn man sie nicht explizit aushebelt wie es erhard getan hat.
-
Werner Salomon schrieb:
Ich denke, es hängt viel davon ab, wie die Funktion aussieht, die den String liefert.
Das denke ich auch. Ich habe jetzt auch eine Version gebastelt, wo beide Varianten identische Ergebnisse liefern.
Wenn man direkt aus einem std::string kopiert sieht es wieder ganz anders aus.
-
Shade Of Mine schrieb:
Tachyon schrieb:
Es gibt einen Fall, in dem beide Varianten gleich schnell sind, und zwar dann, wenn ich beide Varianten zusammen in ein Executable packe. Erstelle ich aber für beide Varianten getrennte Executables, ist die "Innen"-Variante immer langsamer als die "Außen"-Variante.
Was hat das mit der Executable zu tun?
Die innenvariante ist die schnellere wenn man sie nicht explizit aushebelt wie es erhard getan hat.Das hat damit zu tun, wie und ob vor einer Variante schon Speicher allokiert wurde, oder nicht.
-
Wenn es wirklich auf diesen Geschwindigkeitsunterschied ankommt, sollte man im jeweils echten Fall beide Varianten bezüglich Zeitverbrauch testen. Dann kann man entsprechend den Prioritäten entscheiden.
-
Tachyon schrieb:
Das hat damit zu tun, wie und ob vor einer Variante schon Speicher allokiert wurde, oder nicht.
Äh... nein.
Die innen Variante ist nur abhängig von der RVO. Wenn RVO funktioniert ist die innenvariante schneller oder gleichschnell. Wenn RVO nicht funktioniert ist sie langsamer.
RVO heisst, dass man exakt N ctor und N dtor aufrufe hat (in der schleife) unabhängig von den externen faktoren.
wenn keine RVO funktioniert, hat man N ctor aufrufe, N copyctor aufrufe und 2*N dtor aufrufe.
während es bei der aussen variante 1 ctor, N op=, N ctor, N+1 dtor aufrufe sind.
und das hat nichts mit der funktion zu tun die die daten liefert - sofern sie ein passendes objekt liefert und wir keine konvertierungen haben. denn dann wird es komplex.
-
Oha, meine Frage war gar nicht so blöd

Das mit der Schleife mit dem string war nur ein vereinfachtes Beispiel. Genau genommen habe ich eine 2D Vektor Klasse, wo ich bei einem Vektor eine Komponente verändern will.
Ich habs mal getestet, alle vier Varianten bringen keine nennenswerten Unterschiede bei mir:
#include <iostream> #include <windows.h> 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 long LOOPCOUNT = 1000000; cout.setstate( ios_base::failbit ); // Variante 1 (innen) DWORD startticks = GetTickCount (); for (int i = 0; i < LOOPCOUNT; ++i) { Vector2D myVec = getSomeVector(); myVec.setX (3); cout << myVec.getX () << endl; } cout.clear(); cout << "Variante 1 (innen) brauchte: " << (GetTickCount ()-startticks) << "ms" << endl; cout.setstate( ios_base::failbit ); // Variante 2 (aussen) Vector2D myVec; startticks = GetTickCount (); for (int i = 0; i < LOOPCOUNT; ++i) { myVec = getSomeVector(); myVec.setX (3); cout << myVec.getX () << endl; } cout.clear(); cout << "Variante 2 (aussen) brauchte: " << (GetTickCount ()-startticks) << "ms" << endl; cout.setstate( ios_base::failbit ); // Variante 3 (set/get) startticks = GetTickCount (); for (int i = 0; i < LOOPCOUNT; ++i) { myVec.setY (getSomeVector().getY()); myVec.setX (3); cout << myVec.getX () << endl; } cout.clear(); cout << "Variante 3 (set/get) brauchte: " << (GetTickCount ()-startticks) << "ms" << endl; cout.setstate( ios_base::failbit ); // Variante 4 (member) startticks = GetTickCount (); for (int i = 0; i < LOOPCOUNT; ++i) { myVec.y = getSomeVector().y; myVec.x = 3; cout << myVec.getX () << endl; } cout.clear(); cout << "Variante 4 (member) brauchte: " << (GetTickCount ()-startticks) << "ms" << endl; return 0; }So wie es ausschaut bleibe ich bei der Innenvariante. Ist klar, verschmutzt keinen Scope und ist schnell genug

-
Man achte auf die Kapazität der Strings. Sie weist darauf hin dass in Beispiel 1 immer dynamisch Speicher angefordert / freigegeben wird. Damit erübrigt sich wohl die Frage, was schneller ist (Ich hab's nicht nachgemessen ;)).
using namespace std; for(int i = 0; i < 100; ++i) { string s; cout << s.capacity() << '\n'; s += string(50, 'a'); }using namespace std; string s; for(int i = 0; i < 100; ++i) { s.clear(); cout << s.capacity() << '\n'; s += string(50, 'a'); }
-
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: 203msCompiler/IDE: Code::Blocks 8.02 (Standard, ohne Optimierung)
Variante 1 (innen) brauchte: 203ms
Variante 2 (aussen) brauchte: 218ms
Variante 3 (set/get) brauchte: 188ms
Variante 4 (member) brauchte: 219msCompiler/IDE: Code::Blocks 8.02 (mit Optimierung -O3)
Variante 1 (innen) brauchte: 187ms
Variante 2 (aussen) brauchte: 203ms
Variante 3 (set/get) brauchte: 219ms
Variante 4 (member) brauchte: 203msCompiler/IDE: Code::Blocks 8.02 (mit Optimierung -O3 und Intel Pentium Pro)
Variante 1 (innen) brauchte: 63ms
Variante 2 (aussen) brauchte: 62ms
Variante 3 (set/get) brauchte: 62ms
Variante 4 (member) brauchte: 63msCompiler/IDE: MS VC++ 6.0

-
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: 203msCompiler/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: 63msCompiler/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ö!

-
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 328usw. 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.22sAuch 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.
