2010-08-02 7 views
5

Ich habe die folgenden zwei Schleifen:Frage zur Schleifengeschwindigkeit

#include <iostream> 
#include <stdlib.h> 
#include <time.h> 

using namespace std; 
int main(){ 

    int start=clock(); 
    for (int i=0;i<100;i++) 
     cout<<i<<" "<<endl; 
    cout<<clock()-start<<"\n"<<endl; 
    cout<<"\n"; 

    int start1=clock(); 
    for (int j=0;j<100;++j) 
     cout<<j<<" "<<endl; 
    cout<<"\n"; 
    cout<<clock()-start1<<" \n "<<endl; 

    return 0; 
} 

Ich lief, dass dreimal. In den ersten beiden Läufen war die zweite Runde am schnellsten, aber im dritten Lauf war die erste Runde am schnellsten. Was bedeutet das? Welches ist besser? Kommt es auf die Situation an?

+4

100 Iterationen können zu klein sein, um Schlussfolgerungen zu ziehen ... – Vladimir

+0

Eine andere Möglichkeit, um herauszufinden, wie Code im Performance-Bereich ist es, die generierte Assembly zu betrachten. Sie werden wahrscheinlich das * genaue * gleiche Ding für jede Schleife erzeugt sehen. Pre-versus-Post-Inkrement für primitive Typen ist das gleiche. – GManNickG

+0

@GMan: Wie kann es gleich sein, wenn sich die Semantik unterscheidet? Einverstanden: Der Optimierer wird wahrscheinlich den Rückgabewert des nicht zu verwendenden Postfix erkennen und somit eine identische Disassemblierung erzeugen. – xtofl

Antwort

5

In Ihrem Fall ist es wahrscheinlich Standardmessfehler und es spielt keine Rolle, tun verwenden Sie Postinkrement oder Vorinkrement . Für Standardtypen (int, byte ...) spielt es keine Rolle.

ABER Sie sollten sich daran gewöhnen, Pre-Inkrement zu verwenden, da es Auswirkungen auf die Leistung gibt, wenn Sie sie in einer Klasse verwenden, abhängig davon, wie diese Operatoren implementiert sind. Post-Inkrementoperator i ++ muss eine Kopie des

Zum Beispiel Objekt machen:

class integer 
{ 
public: 
    integer(int t_value) 
    : m_value(t_value) 
    { 
    } 

    int get_value() { return m_value; } 

    integer &operator++() // pre increment operator 
    { 
    ++m_value; 
    return *this; 
    } 

    integer operator++(int) // post increment operator 
    { 
    integer old = *this; // a copy is made, it's cheap, but it's still a copy 
    ++m_value; 
    return old; // Return old copy 
    } 

private: 
    int m_value; 

Werfen Sie einen Blick auf die folgende Antwort

StackOverflow: i++ less efficient than ++i, how to show this?

+1

Nicht nur ist die Effizienz betroffen, es bedeutet sogar etwas anderes. – xtofl

+0

@xtofl: Wie so? (In der Diskussion geht es um Prä/Post-Inkrement als separaten Ausdruck.) – UncleBens

1

Es bedeutet, dass Sie statistische Methoden verwenden sollten, um herauszufinden, welche Schleife schneller ist (und ich würde hoffen, dass diese Techniken festgestellt haben, dass sie sich auf Augenhöhe befanden).

Sie haben keine Idee, was Ihre CPU über die Last hinaus tut, Sie setzen es hier unter.

Es könnte geplante Aufgaben starten, Interrupts behandeln, alle möglichen Dinge, die Ihre Ergebnisse verfälschen würde.

Sie müssen möglicherweise eine Million Läufe machen, die Ausreißer wegwerfen und den Rest mitteln, um eine annehmbare Probe zu erhalten.

Obendrein ist hundert Iterationen nicht viel, vor allem, da Sie Funktion tun zu cout ruft die Sumpf und kann die Zeitsteuerung macht die Schleife ausgegeben.

Wenn ich Prüfungen unter UNIX, I nicht benutze abgelaufene Zeit aus diesem Grund. Die System- und Benutzerzeit gibt an, wie viele Sekunden die CPU für den gegebenen Prozess verwendet wurde, unabhängig von der verstrichenen Zeit.

1

++ ich sollte den gleichen Maschinencode wie i ++ mit unbenutztem Ergebnis auf irgendeinen halbwegs ordentlichen Compiler ergeben (vorausgesetzt, es ist nicht überladen, was für int nicht der Fall ist). Und selbst wenn dies nicht der Fall ist (Sie müssten einen ziemlich dummen Compiler aus dem letzten Jahrhundert ausgraben), ist der Unterschied so gering, dass Sie sich nie darum kümmern sollten. Nun, es gibt Fälle, in denen Sie jedes winzige bisschen Leistung ausdrücken müssen, aber die wenigsten von uns kommen in diese Situation. Und selbst dann hat der Körper der Schleife viel mehr Optimierungspotenzial (selbst in Ihrem vereinfachten Beispiel - I/O ist viel billiger als das Kopieren eines Maschinenworts).

Oder in einem Satz: Vorzeitige Optimierung ist die Wurzel allen Übels.

4

Diese Schleifen sind äquivalent für die Induktionsvariable vom Typ int. Die Postinkrement- gegenüber den Pre-Inkrement-Fragen wurde hier mehrmals beantwortet. Versuchen Sie, die Archive ein wenig zu durchsuchen.

Auch die Inkrementierung dauert nur einen kleinen Bruchteil der Zeit im Vergleich zum Standard IO. Ihr Test misst die Geschwindigkeit des IO und nicht die Geschwindigkeit der Inkrement-Operationen.

10

Die Laufzeit Ihrer Loops wird überwiegend von Eingabe-Ausgabe-Operationen dominiert. Dies bedeutet, dass die Zeit, die Sie beobachten 1) hat nichts mit der tatsächlichen Leistung der Schleife zu tun (d. H. i++ vs ++j), 2) ist ziemlich unberechenbar und instabil (im Wesentlichen zufällig).

Mit anderen Worten, Ihr Experiment ist bedeutungslos. Es bedeutet absolut nichts.

Schließlich, in Situationen, in denen das Ergebnis des eingebauten ++-Operators nicht verwendet wird, gibt es absolut keinen Unterschied zwischen Postfix und Präfix Inkrement. In jedem vernünftigen Compiler haben beide Loops eine absolut identische Performance.

+2

Ich wette, dass, wenn er das zur Assemblersprache kompiliert, dass die zwei Schleifen genau dasselbe sind – Jaka

-1

++ i ist Vorinkrement
i ++ Postinkrement ist

siehe Operator für Details

+0

Ich denke, dass er das weiß. – GManNickG

+0

Einfache Antwort. Schlau. Wenn du etwas hinzugefügt hättest, was die Bedeutung von pre- vs. post inkrementieren würde, hätte ich dich + 1d. – xtofl

1

Von dem, was ich sehe, die einzige Unterschied in den Schleifen ist das Vor/Nach-Inkrement der Schleifenvariablen. Der Großteil der Prozesszeit wird für den Cout ausgegeben. Inkrementieren wird im Vergleich zu vernachlässigbarer Zeit sein.

3

Old GCC 3.4.4 tut dies:

Erste Schleife:

.L11: 
     cmpl $99, -8(%ebp) 
     jg  .L12 
     subl $8, %esp 
     pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 
     subl $12, %esp 
     pushl $.LC0 
     subl $12, %esp 
     pushl -8(%ebp) 
     pushl $_ZSt4cout 
.LCFI7: 
     call _ZNSolsEi 
     addl $20, %esp 
     pushl %eax 
     call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc 
     addl $20, %esp 
     pushl %eax 
.LCFI8: 
     call _ZNSolsEPFRSoS_E 
     addl $16, %esp 
     leal -8(%ebp), %eax 
     incl (%eax) 
     jmp  .L11 
.L12: 

Zweite Schleife:

.L14: 
     cmpl $99, -12(%ebp) 
     jg  .L15 
     subl $8, %esp 
     pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 
     subl $12, %esp 
     pushl $.LC0 
     subl $12, %esp 
     pushl -12(%ebp) 
     pushl $_ZSt4cout 
.LCFI13: 
     call _ZNSolsEi 
     addl $20, %esp 
     pushl %eax 
     call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc 
     addl $20, %esp 
     pushl %eax 
.LCFI14: 
     call _ZNSolsEPFRSoS_E 
     addl $16, %esp 
     leal -12(%ebp), %eax 
     incl (%eax) 
     jmp  .L14 
.L15: 

Können Sie Unterschiede finden? :) (abgesehen davon, dass i und j an verschiedenen Positionen auf dem Stack liegen -8 (% ebp) und -12 (% ebp))