it-swarm-eu.dev

Vermeiden Sie Postfix Increment Operator

Ich habe gelesen, dass ich vermeiden Sie den Postfix-Inkrement-Operator aus Leistungsgründen (in bestimmten Fällen).

Beeinträchtigt dies jedoch nicht die Lesbarkeit des Codes? Meiner Meinung nach:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

Sieht besser aus als:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

Aber das ist wahrscheinlich nur aus Gewohnheit. Zugegeben, ich habe nicht viele verwendet gesehen ++i.

Ist die Leistung in diesem Fall so schlecht, um die Lesbarkeit zu beeinträchtigen? Oder bin ich nur blind und ++i ist besser lesbar als i++?

25
Mateen Ulhaq

Die Fakten:

  1. i ++ und ++ i sind gleichermaßen leicht zu lesen. Du magst keine, weil du nicht daran gewöhnt bist, aber es gibt im Grunde nichts, als das du sie falsch interpretieren kannst, also ist es keine Arbeit mehr zu lesen oder zu schreiben.

  2. Zumindest in einigen Fällen ist der Postfix-Operator weniger effizient.

  3. In 99,99% der Fälle spielt es jedoch keine Rolle, weil (a) es ohnehin auf einen einfachen oder primitiven Typ wirkt und es nur ein Problem ist, wenn es ein großes Objekt kopiert. (B) es wird nicht in einer Aufführung sein kritischer Teil von Code (c) Sie wissen nicht, ob der Compiler ihn optimieren wird oder nicht.

  4. Daher schlage ich vor, Präfixe zu verwenden, es sei denn, Sie benötigen speziell Postfix. Dies ist eine gute Angewohnheit, nur weil (a) es eine gute Angewohnheit ist, mit anderen Dingen präzise umzugehen, und (b) wenn Sie einmal in einem blauen Mond Postfix verwenden möchten und verstehe es falsch herum: Wenn du immer schreibst, was du meinst, ist das weniger wahrscheinlich. Es gibt immer einen Kompromiss zwischen Leistung und Optimierung.

Sie sollten Ihren gesunden Menschenverstand verwenden und nicht mikrooptimieren, bis Sie es brauchen, aber auch nicht offensichtlich ineffizient sein. In der Regel bedeutet dies: Schließen Sie zunächst jede Codekonstruktion aus, die selbst in nicht zeitkritischem Code inakzeptabel ineffizient ist (normalerweise stellt dies einen grundlegenden konzeptionellen Fehler dar, z. B. das Übergeben von 500-MB-Objekten als Wert ohne Grund). und zweitens wählen Sie von jeder anderen Art, den Code zu schreiben, die klarste.

Hier glaube ich jedoch, dass die Antwort einfach ist: Ich glaube, dass das Schreiben eines Präfixes, sofern Sie kein spezielles Postfix benötigen, (a) sehr geringfügig klarer und (b) sehr geringfügig effizienter ist, daher sollten Sie dies standardmäßig immer schreiben, aber Mach dir keine Sorgen, wenn du es vergisst.

Vor sechs Monaten dachte ich genauso wie Sie, dass i ++ natürlicher ist, aber es ist genau das, was Sie gewohnt sind.

EDIT 1: Scott Meyers, in "More Effective C++", dem ich in dieser Sache im Allgemeinen vertraue, sagt, dass Sie die Verwendung des Postfix-Operators bei benutzerdefinierten Typen generell vermeiden sollten (da die einzig vernünftige Implementierung der Postfix-Inkrementfunktion darin besteht, a zu erstellen Kopieren Sie das Objekt, rufen Sie die Präfix-Inkrementfunktion auf, um das Inkrement auszuführen, und geben Sie die Kopie zurück, aber Kopiervorgänge können teuer sein.

Wir wissen also nicht, ob es allgemeine Regeln gibt für (a) ob dies heute zutrifft, (b) ob es auch (weniger) für intrinsische Typen gilt (c) ob Sie "++" verwenden sollten Alles andere als eine leichte Iteratorklasse. Aber aus all den Gründen, die ich oben beschrieben habe, spielt es keine Rolle, tun Sie, was ich zuvor gesagt habe.

EDIT 2: Dies bezieht sich auf die allgemeine Praxis. Wenn Sie der Meinung sind, dass es in einem bestimmten Fall wichtig ist, sollten Sie es profilieren und sehen. Die Profilerstellung ist einfach und kostengünstig und funktioniert. Aus den ersten Prinzipien abzuleiten, was optimiert werden muss, ist schwierig und teuer und funktioniert nicht.

58
Jack V.

Codieren Sie immer zuerst den Programmierer und dann den Computer.

Wenn es einen Leistungsunterschied gibt, nachdem der Compiler sein Expertenauge auf Ihren Code geworfen hat, UND können Sie ihn messen UND es ist wichtig - dann können Sie es ändern.

62
Martin Beckett

GCC erzeugt für beide Schleifen den gleichen Maschinencode.

C-Code

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

Versammlungscode (mit meinen Kommentaren)

    cstring
LC0:
    .ascii "i = %d\12\0"
    .text
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    call    L9
"L00000000001$pb":
L9:
    popl    %ebx
    movl    $0, -16(%ebp)  // -16(%ebp) is "i" for the first loop 
    jmp L2
L3:
    movl    -16(%ebp), %eax   // move i for the first loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub    // call printf
    leal    -16(%ebp), %eax  // make the eax register point to i
    incl    (%eax)           // increment i
L2:
    cmpl    $41, -16(%ebp)  // compare i to the number 41
    jle L3              // jump to L3 if less than or equal to 41
    movl    $0, -12(%ebp)   // -12(%ebp) is "i" for the second loop  
    jmp L5
L6:
    movl    -12(%ebp), %eax   // move i for the second loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub     // call printf
    leal    -12(%ebp), %eax  // make eax point to i
    incl    (%eax)           // increment i
L5:
    cmpl    $41, -12(%ebp)   // compare i to 41 
    jle L6               // jump to L6 if less than or equal to 41
    movl    $0, %eax
    addl    $36, %esp
    popl    %ebx
    leave
    ret
    .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
    .indirect_symbol _printf
    hlt ; hlt ; hlt ; hlt ; hlt
    .subsections_via_symbols
13
bit-twiddler

Machen Sie sich keine Sorgen um die Leistung, sagen wir 97% der Zeit. Vorzeitige Optimierung ist die Wurzel allen Übels.

- Donald Knuth

Nun, da dies nicht im Weg ist, treffen wir unsere Wahl vernünftig:

  • ++i: Präfixinkrement, erhöht den aktuellen Wert und liefert das Ergebnis
  • i++: Postfix-Inkrement, kopiere den Wert, inkrementiere den aktuellen Wert, erhalte die Kopie

Sofern keine Kopie des alten Werts erforderlich ist, ist die Verwendung von Postfix-Inkrement eine Umgehungsmöglichkeit.

ngenauigkeit kommt von Faulheit. Verwenden Sie immer das Konstrukt, das Ihre Absicht auf direkteste Weise ausdrückt. Es besteht eine geringere Wahrscheinlichkeit, dass der zukünftige Betreuer Ihre ursprüngliche Absicht falsch versteht.

Obwohl es hier (wirklich) geringfügig ist, gab es Zeiten, in denen ich durch das Lesen von Code wirklich verwirrt war: Ich habe mich wirklich gefragt, ob die Absicht und der tatsächliche Ausdruck zusammenfallen, und natürlich nach ein paar Monaten sie (oder ich) erinnerte mich auch nicht ...

Es spielt also keine Rolle, ob es für Sie richtig aussieht oder nicht. Umarmung KUSS . In ein paar Monaten haben Sie Ihre alten Praktiken gemieden.

12
Matthieu M.

In C++ können Sie könnten einen erheblichen Leistungsunterschied bewirken, wenn Operatorüberladungen auftreten, insbesondere wenn Sie Vorlagencode schreiben und nicht wissen, welche Iteratoren übergeben werden könnten. Die Logik hinter einem Iterator X. kann sowohl substanziell als auch signifikant sein, dh langsam und vom Compiler nicht optimierbar.

Dies ist jedoch in C nicht der Fall, wo Sie wissen, dass es sich nur um einen trivialen Typ handelt und der Leistungsunterschied trivial ist und der Compiler leicht wegoptimieren kann.

Ein Tipp: Sie programmieren in C oder in C++, und Fragen beziehen sich auf das eine oder andere, nicht auf beide.

4
DeadMG

Die Leistung beider Operationen hängt stark von der zugrunde liegenden Architektur ab. Man muss einen Wert erhöhen, der im Speicher gespeichert ist, was bedeutet, dass der von Neumann-Engpass in beiden Fällen der begrenzende Faktor ist.

Im Fall von ++ i müssen wir

Fetch i from memory 
Increment i
Store i back to memory
Use i

Im Fall von i ++ müssen wir

Fetch i from memory
Use i
Increment i
Store i back to memory

Die Operatoren ++ und - verfolgen ihren Ursprung bis zum PDP-11-Befehlssatz. Der PDP-11 könnte ein automatisches Nachinkrementieren eines Registers durchführen. Es könnte auch eine automatische Vordekrementierung für eine effektive Adresse durchführen, die in einem Register enthalten ist. In beiden Fällen konnte der Compiler diese Operationen auf Maschinenebene nur nutzen, wenn die betreffende Variable eine "Register" -Variable war.

2
bit-twiddler

Wenn Sie wissen möchten, ob etwas langsam ist, testen Sie es. Nehmen Sie eine BigInteger oder eine gleichwertige Version, stecken Sie sie mit beiden Redewendungen in eine ähnliche for-Schleife, stellen Sie sicher, dass das Innere der Schleife nicht optimiert wird, und messen Sie beide.

Nachdem ich den Artikel gelesen habe, finde ich ihn aus drei Gründen nicht sehr überzeugend. Erstens sollte der Compiler in der Lage sein, die Erstellung eines Objekts zu optimieren, das niemals verwendet wird. Zwei, die i++ Konzept ist idiomatisch für numerisch für Schleifen, daher sind die Fälle, von denen ich sehe, dass sie tatsächlich betroffen sind, auf beschränkt. Drittens liefern sie ein rein theoretisches Argument ohne Zahlen, die es stützen könnten.

Insbesondere aufgrund des ersten Grundes gehe ich davon aus, dass sie, wenn Sie das Timing tatsächlich durchführen, direkt nebeneinander liegen.

2
jprete