If-Anweisung vs if-else-Anweisung, welche ist schneller? [geschlossen]


81

Ich habe mich neulich mit einem Freund über diese beiden Schnipsel gestritten. Welches ist schneller und warum?

value = 5;
if (condition) {
    value = 6;
}

und:

if (condition) {
    value = 6;
} else {
    value = 5;
}

Was ist, wenn valuees sich um eine Matrix handelt?

Hinweis: Ich weiß, dass value = condition ? 6 : 5;es das gibt und ich erwarte, dass es schneller ist, aber es war keine Option.

Bearbeiten (vom Personal angefordert, da die Frage gerade gehalten wird):

  • Bitte antworten Sie, indem Sie entweder die von Mainstream-Compilern (z. B. g ++, clang ++, vc, mingw ) generierte x86-Assembly in optimierten und nicht optimierten Versionen oder die MIPS-Assembly berücksichtigen .
  • Wenn sich die Assembly unterscheidet, erklären Sie, warum eine Version schneller ist und wann ( z. B. "besser, da keine Verzweigung und Verzweigung das folgende Problem hat" ).

173
Die Optimierung wird all das umbringen ... es spielt keine Rolle ...
The Quantum Physicist

21
Sie könnten es profilieren, ich persönlich bezweifle, dass Sie mit einem modernen Compiler einen Unterschied feststellen würden.
George

25
Die Verwendung von value = condition ? 6 : 5;anstelle von if/elseführt höchstwahrscheinlich dazu, dass derselbe Code generiert wird. Sehen Sie sich die Assembly-Ausgabe an, wenn Sie mehr wissen möchten.
Jabberwocky

8
Das Wichtigste in diesem Fall ist, die Verzweigung zu vermeiden, die hier am teuersten ist. (Rohr neu laden, vorab abgerufene Anweisungen verwerfen usw.)
Tommylee2k

11
Das einzige Mal, wenn es sinnvoll ist, für eine solche Geschwindigkeit eine Mikrooptimierung durchzuführen, befindet sich eine Schleife, die viele Male ausgeführt wird. Entweder kann der Optimierer alle Verzweigungsbefehle optimieren, wie es gcc für dieses triviale Beispiel kann, oder in der realen Welt Die Leistung hängt stark von der korrekten Verzweigungsvorhersage ab (obligatorischer Link zu stackoverflow.com/questions/11227809/… ). Wenn Sie unvermeidlich innerhalb einer Schleife verzweigen, können Sie dem Verzweigungsprädiktor helfen, indem Sie ein Profil erstellen und damit neu kompilieren.
Davislor

Antworten:


280

TL; DR: In nicht optimiertem Code scheint der Code grundsätzlich neu geschrieben zu werden, ifohne elsedass er irrelevant effizienter erscheint, aber selbst wenn die grundlegendste Optimierungsstufe aktiviert ist value = condition + 5.


Ich habe es ausprobiert und die Assembly für den folgenden Code generiert:

int ifonly(bool condition, int value)
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return value;
}

int ifelse(bool condition, int value)
{
    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return value;
}

In gcc 6.3 mit deaktivierten Optimierungen ( -O0) ist der relevante Unterschied:

 mov     DWORD PTR [rbp-8], 5
 cmp     BYTE PTR [rbp-4], 0
 je      .L2
 mov     DWORD PTR [rbp-8], 6
.L2:
 mov     eax, DWORD PTR [rbp-8]

für ifonly, während ifelsehat

 cmp     BYTE PTR [rbp-4], 0
 je      .L5
 mov     DWORD PTR [rbp-8], 6
 jmp     .L6
.L5:
 mov     DWORD PTR [rbp-8], 5
.L6:
 mov     eax, DWORD PTR [rbp-8]

Letzteres sieht etwas weniger effizient aus, da es einen zusätzlichen Sprung hat, aber beide mindestens zwei und höchstens drei Aufgaben haben, es sei denn, Sie müssen wirklich jeden letzten Leistungsabfall herausholen (Hinweis: Wenn Sie nicht an einem Space Shuttle arbeiten, tun Sie dies nicht und selbst dann wahrscheinlich nicht) wird der Unterschied nicht spürbar sein.

Selbst mit der niedrigsten Optimierungsstufe ( -O1) reduzieren sich beide Funktionen auf dasselbe:

test    dil, dil
setne   al
movzx   eax, al
add     eax, 5

Das ist im Grunde das Äquivalent von

return 5 + condition;

Angenommen, es conditionist null oder eins. Höhere Optimierungsstufen ändern die Ausgabe nicht wirklich, es sei denn, sie vermeiden dies, movzxindem sie das EAXRegister zu Beginn effizient auf Null setzen .


Haftungsausschluss: Sie sollten sich wahrscheinlich nicht 5 + conditionselbst schreiben (obwohl der Standard garantiert, dass die Konvertierung truein einen ganzzahligen Typ möglich ist 1), da Ihre Absicht für Personen, die Ihren Code lesen (einschließlich Ihres zukünftigen Selbst), möglicherweise nicht sofort offensichtlich ist. Mit diesem Code soll gezeigt werden, dass das, was der Compiler in beiden Fällen erzeugt, (praktisch) identisch ist. Ciprian Tomoiaga sagt es ganz gut in den Kommentaren:

Die Aufgabe eines Menschen besteht darin, Code für Menschen zu schreiben und den Compiler Code für die Maschine schreiben zu lassen .


50
Dies ist eine großartige Antwort und sollte akzeptiert werden.
dtell

10
Ich hätte nie gedacht, den Zusatz zu verwenden (<- was Python mit Ihnen macht.)
Ciprian Tomoiagă

26
@CiprianTomoiaga und wenn Sie keinen Optimierer schreiben, sollten Sie nicht! In fast allen Fällen sollten Sie den Compiler solche Optimierungen vornehmen lassen, insbesondere wenn diese die Lesbarkeit des Codes erheblich beeinträchtigen. Nur wenn bei Leistungstests Probleme mit einem bestimmten Code auftreten, sollten Sie überhaupt versuchen, ihn zu optimieren, ihn auch dann sauber und gut kommentiert zu halten und nur Optimierungen durchführen, die einen messbaren Unterschied machen.
Muzer

22
Ich wollte Muzer antworten, aber es hätte dem Thread nichts hinzugefügt. Ich möchte jedoch nur wiederholen, dass es die Aufgabe eines Menschen ist, Code für Menschen zu schreiben und den Compiler Code für die Maschine schreiben zu lassen . Ich sagte das von einem Compiler-Entwickler PoV (was ich nicht bin, aber ich habe ein bisschen über sie gelernt)
Ciprian Tomoiagă

10
Der in truekonvertierte Wert intergibt immer 1 Punkt. Wenn Ihr Zustand nur "wahr" und nicht der boolWert ist true, dann ist das natürlich eine ganz andere Sache.
TC

44

Die Antwort von CompuChip zeigt, dass intbeide für dieselbe Baugruppe optimiert sind, sodass es keine Rolle spielt.

Was ist, wenn der Wert eine Matrix ist?

Ich werde dies allgemeiner interpretieren, dh was ist, wenn valuees sich um einen Typ handelt, dessen Konstruktionen und Aufgaben teuer sind (und Umzüge billig sind).

dann

T value = init1;
if (condition)
   value = init2;

ist nicht optimal, da Sie in diesem Fall conditiondie unnötige Initialisierung durchführen init1und dann die Kopierzuweisung vornehmen.

T value;
if (condition)
   value = init2;
else
   value = init3;

Das ist besser. Aber immer noch nicht optimal, wenn die Standardkonstruktion teuer ist und wenn die Kopierkonstruktion teurer ist als die Initialisierung.

Sie haben die bedingte Operatorlösung, die gut ist:

T value = condition ? init1 : init2;

Wenn Ihnen der bedingte Operator nicht gefällt, können Sie eine Hilfsfunktion wie folgt erstellen:

T create(bool condition)
{
  if (condition)
     return {init1};
  else
     return {init2};
}

T value = create(condition);

Je nachdem was init1und was init2können Sie auch berücksichtigen:

auto final_init = condition ? init1 : init2;
T value = final_init;

Aber ich muss noch einmal betonen, dass dies nur relevant ist, wenn Konstruktion und Aufgaben für den gegebenen Typ wirklich teuer sind. Und selbst dann wissen Sie es nur durch Profilerstellung .


3
Teuer und nicht optimiert. Wenn der Standardkonstruktor beispielsweise die Matrix auf Null setzt, kann der Compiler erkennen, dass es bei der Zuweisung nur darum geht, diese Nullen zu überschreiben, also überhaupt nicht auf Null zu setzen, und direkt in diesen Speicher schreiben. Natürlich sind Optimierer pingelige Biester, daher ist es schwer vorherzusagen, wann sie eintreten oder nicht ...
Matthieu M.

@MatthieuM. natürlich. Was ich mit "teuer" gemeint habe, ist "teuer in der Ausführung (nach einer Metrik, sei es entweder CPU-Takt, Ressourcennutzung usw.), auch nach Compiler-Optimierungen.
Bolov

Es scheint mir unwahrscheinlich, dass eine Standardkonstruktion teuer wäre, sich aber billig bewegt.
Plugwash

6
@plugwash Betrachten Sie eine Klasse mit einem sehr großen zugewiesenen Array. Der Standardkonstruktor weist das Array zu und initialisiert es, was teuer ist. Der Konstruktor move (not copy!) Kann nur Zeiger mit dem Quellobjekt austauschen und muss das große Array nicht zuweisen oder initialisieren.
TrentP

1
Solange die Teile einfach sind, würde ich definitiv die Verwendung des ?:Operators der Einführung einer neuen Funktion vorziehen . Schließlich werden Sie wahrscheinlich nicht nur die Bedingung an die Funktion übergeben, sondern auch einige Konstruktorargumente. Einige davon können create()je nach Zustand nicht einmal verwendet werden.
cmaster - wieder herstellen Monica

12

In der Pseudo-Assemblersprache

    li    #0, r0
    test  r1
    beq   L1
    li    #1, r0
L1:

kann oder kann nicht schneller sein als

    test  r1
    beq   L1
    li    #1, r0
    bra   L2
L1:
    li    #0, r0
L2:

abhängig davon, wie hoch entwickelt die tatsächliche CPU ist. Vom einfachsten zum schicksten:

  • Bei jeder CPU, die nach ungefähr 1990 hergestellt wurde, hängt eine gute Leistung von der Code-Anpassung im Befehls-Cache ab . Minimieren Sie daher im Zweifelsfall die Codegröße. Dies spricht für das erste Beispiel.

  • Bei einer einfachen " in der Reihenfolge, fünfstufigen Pipeline " -CPU, die immer noch ungefähr das ist, was Sie in vielen Mikrocontrollern erhalten, gibt es jedes Mal eine Pipelineblase, wenn ein Zweig - bedingt oder bedingungslos - genommen wird. Daher ist es auch wichtig, ihn zu minimieren die Anzahl der Verzweigungsanweisungen. Dies spricht auch für das erste Beispiel.

  • Etwas anspruchsvollere CPUs - ausgefallen genug, um " Out-of-Order-Ausführung " durchzuführen , aber nicht ausgefallen genug, um die bekanntesten Implementierungen dieses Konzepts zu verwenden - können Pipeline-Blasen verursachen, wenn sie auf Schreib-nach-Schreib-Gefahren stoßen . Dies spricht für das zweite Beispiel, in r0dem nur einmal geschrieben wird, egal was passiert. Diese CPUs sind normalerweise ausgefallen genug, um bedingungslose Verzweigungen im Anweisungsabruf zu verarbeiten. Sie tauschen also nicht nur die Schreib-nach-Schreib-Strafe gegen eine Verzweigungsstrafe.

    Ich weiß nicht, ob noch jemand diese Art von CPU herstellt. Die CPUs, die die "bekanntesten Implementierungen" der Ausführung außerhalb der Reihenfolge verwenden, können jedoch die weniger häufig verwendeten Anweisungen einschränken. Sie müssen sich daher bewusst sein, dass so etwas passieren kann. Ein echtes Beispiel sind falsche Datenabhängigkeiten von den Zielregistern in popcntund lzcntauf Sandy Bridge-CPUs .

  • Am höchsten Ende gibt die OOO-Engine für beide Codefragmente genau die gleiche Abfolge interner Operationen aus. Dies ist die Hardwareversion von "Keine Sorge, der Compiler generiert in beiden Fällen denselben Maschinencode." Die Codegröße spielt jedoch immer noch eine Rolle, und jetzt sollten Sie sich auch Gedanken über die Vorhersagbarkeit des bedingten Zweigs machen. Fehler bei der Verzweigungsvorhersage führen möglicherweise zu einer vollständigen Pipeline- Leerung , was sich nachteilig auf die Leistung auswirkt. Siehe Warum ist es schneller, ein sortiertes Array zu verarbeiten als ein unsortiertes Array? zu verstehen, wie viel Unterschied dies machen kann.

    Wenn die Verzweigung ist sehr unberechenbar, und Ihre CPU verfügt über ein bedingtes-Set oder bedingte Bewegungsbefehle, ist dies die Zeit , sie zu benutzen:

        li    #0, r0
        test  r1
        setne r0

    oder

        li    #0, r0
        li    #1, r2
        test  r1
        movne r2, r0

    Die Conditional-Set-Version ist außerdem kompakter als jede andere Alternative. Wenn diese Anweisung verfügbar ist, ist sie praktisch garantiert die richtige Sache für dieses Szenario, selbst wenn die Verzweigung vorhersehbar war. Die Conditional-Move-Version erfordert ein zusätzliches Scratch-Register und verschwendet immer lidie Versand- und Ausführungsressourcen eines Befehls. Wenn der Zweig tatsächlich vorhersehbar war, ist die Zweigversion möglicherweise schneller.


Ich würde Ihren zweiten Punkt dahingehend umformulieren, ob die CPU über eine nicht funktionsfähige Engine verfügt, die durch Schreib-nach-Schreib-Gefahren verzögert wird. Wenn eine CPU einen Out-of-Order - Motor hat , die solche Gefahren unverzüglich umgehen kann, dann ist es kein Problem, aber es gibt auch kein Problem , wenn die CPU nicht einen Out-of-Order - Motor hat überhaupt .
Supercat

@supercat Der Absatz am Ende soll diesen Fall behandeln, aber ich werde darüber nachdenken, wie ich ihn klarer machen kann.
zwol

Ich weiß nicht, welche aktuellen CPUs einen Cache haben, der dazu führen würde, dass sequentiell ausgeführter Code beim zweiten Mal schneller ausgeführt wird als beim ersten Mal (einige Flash-basierte ARM-Teile verfügen über eine Schnittstelle, die einige Zeilen Flash-Daten puffern kann, aber Sie können Code nacheinander so schnell abrufen, wie sie ausgeführt werden. Der Schlüssel, um verzweigungsintensiven Code auf diesen schnell auszuführen, besteht darin, ihn in den Arbeitsspeicher zu kopieren. CPUs ohne Ausführung außerhalb der Reihenfolge sind weitaus häufiger als solche, die durch Schreib-nach-Schreib-Gefahren verzögert würden.
Supercat

Dies ist sehr aufschlussreich
Julien__

9

In nicht optimiertem Code weist das erste Beispiel eine Variable immer einmal und manchmal zweimal zu. Das zweite Beispiel weist eine Variable nur einmal zu. Die Bedingung ist für beide Codepfade gleich, daher sollte dies keine Rolle spielen. Im optimierten Code hängt dies vom Compiler ab.

Wenn Sie diesbezüglich betroffen sind, generieren Sie wie immer die Assembly und sehen Sie, was der Compiler tatsächlich tut.


1
Wenn Sie sich Gedanken über die Leistung machen, werden sie nicht unoptimiert kompiliert. Aber wie "gut" der Optimierer ist, hängt natürlich vom Compiler / der Version ab.
old_timer

AFAIK Es gibt keinen Kommentar zu der Compiler- / CPU-Architektur usw., sodass der Compiler möglicherweise keine Optimierung durchführt. Sie können auf einem 8-Bit-PIC bis zu einem 64-Bit-Xeon kompiliert werden.
Neil

8

Was würde Sie denken lassen, dass einer von ihnen selbst der eine Liner schneller oder langsamer ist?

unsigned int fun0 ( unsigned int condition, unsigned int value )
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{

    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
    value = condition ? 6 : 5;
    return(value);
}

Mehr Codezeilen in einer höheren Sprache bieten dem Compiler mehr Möglichkeiten zum Arbeiten. Wenn Sie also eine allgemeine Regel dazu erstellen möchten, geben Sie dem Compiler mehr Code zum Arbeiten. Wenn der Algorithmus der gleiche ist wie in den obigen Fällen, würde man erwarten, dass der Compiler mit minimaler Optimierung dies herausfindet.

00000000 <fun0>:
   0:   e3500000    cmp r0, #0
   4:   03a00005    moveq   r0, #5
   8:   13a00006    movne   r0, #6
   c:   e12fff1e    bx  lr

00000010 <fun1>:
  10:   e3500000    cmp r0, #0
  14:   13a00006    movne   r0, #6
  18:   03a00005    moveq   r0, #5
  1c:   e12fff1e    bx  lr

00000020 <fun2>:
  20:   e3500000    cmp r0, #0
  24:   13a00006    movne   r0, #6
  28:   03a00005    moveq   r0, #5
  2c:   e12fff1e    bx  lr

Keine große Überraschung, dass die erste Funktion in einer anderen Reihenfolge ausgeführt wurde, jedoch zur gleichen Ausführungszeit.

0000000000000000 <fun0>:
   0:   7100001f    cmp w0, #0x0
   4:   1a9f07e0    cset    w0, ne
   8:   11001400    add w0, w0, #0x5
   c:   d65f03c0    ret

0000000000000010 <fun1>:
  10:   7100001f    cmp w0, #0x0
  14:   1a9f07e0    cset    w0, ne
  18:   11001400    add w0, w0, #0x5
  1c:   d65f03c0    ret

0000000000000020 <fun2>:
  20:   7100001f    cmp w0, #0x0
  24:   1a9f07e0    cset    w0, ne
  28:   11001400    add w0, w0, #0x5
  2c:   d65f03c0    ret

Hoffentlich haben Sie die Idee, dass Sie dies einfach hätten versuchen können, wenn nicht offensichtlich gewesen wäre, dass die verschiedenen Implementierungen nicht wirklich unterschiedlich waren.

Was eine Matrix angeht, bin ich mir nicht sicher, wie das wichtig ist.

if(condition)
{
 big blob of code a
}
else
{
 big blob of code b
}

Ich werde einfach den gleichen Wenn-Dann-Sonst-Wrapper um die großen Code-Blobs legen, sei es Wert = 5 oder etwas Komplizierteres. Ebenso wird der Vergleich, selbst wenn es sich um einen großen Code-Blob handelt, noch berechnet werden muss, und gleich oder ungleich etwas wird oft mit dem Negativ kompiliert, wenn (Bedingung) etwas getan wird, wird oft so kompiliert, als ob Bedingung nicht gehe.

00000000 <fun0>:
   0:   0f 93           tst r15     
   2:   03 24           jz  $+8         ;abs 0xa
   4:   3f 40 06 00     mov #6, r15 ;#0x0006
   8:   30 41           ret         
   a:   3f 40 05 00     mov #5, r15 ;#0x0005
   e:   30 41           ret         

00000010 <fun1>:
  10:   0f 93           tst r15     
  12:   03 20           jnz $+8         ;abs 0x1a
  14:   3f 40 05 00     mov #5, r15 ;#0x0005
  18:   30 41           ret         
  1a:   3f 40 06 00     mov #6, r15 ;#0x0006
  1e:   30 41           ret         

00000020 <fun2>:
  20:   0f 93           tst r15     
  22:   03 20           jnz $+8         ;abs 0x2a
  24:   3f 40 05 00     mov #5, r15 ;#0x0005
  28:   30 41           ret         
  2a:   3f 40 06 00     mov #6, r15 ;#0x0006
  2e:   30 41

Wir haben diese Übung gerade mit jemand anderem über Stackoverflow durchgeführt. Interessanterweise stellte dieser Mips-Compiler in diesem Fall nicht nur fest, dass die Funktionen gleich waren, sondern ließ eine Funktion einfach zur anderen springen, um Code-Platz zu sparen. Hab das hier aber nicht gemacht

00000000 <fun0>:
   0:   0004102b    sltu    $2,$0,$4
   4:   03e00008    jr  $31
   8:   24420005    addiu   $2,$2,5

0000000c <fun1>:
   c:   0004102b    sltu    $2,$0,$4
  10:   03e00008    jr  $31
  14:   24420005    addiu   $2,$2,5

00000018 <fun2>:
  18:   0004102b    sltu    $2,$0,$4
  1c:   03e00008    jr  $31
  20:   24420005    addiu   $2,$2,5

einige weitere Ziele.

00000000 <_fun0>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   0bf5 0004       tst 4(r5)
   8:   0304            beq 12 <_fun0+0x12>
   a:   15c0 0006       mov $6, r0
   e:   1585            mov (sp)+, r5
  10:   0087            rts pc
  12:   15c0 0005       mov $5, r0
  16:   1585            mov (sp)+, r5
  18:   0087            rts pc

0000001a <_fun1>:
  1a:   1166            mov r5, -(sp)
  1c:   1185            mov sp, r5
  1e:   0bf5 0004       tst 4(r5)
  22:   0204            bne 2c <_fun1+0x12>
  24:   15c0 0005       mov $5, r0
  28:   1585            mov (sp)+, r5
  2a:   0087            rts pc
  2c:   15c0 0006       mov $6, r0
  30:   1585            mov (sp)+, r5
  32:   0087            rts pc

00000034 <_fun2>:
  34:   1166            mov r5, -(sp)
  36:   1185            mov sp, r5
  38:   0bf5 0004       tst 4(r5)
  3c:   0204            bne 46 <_fun2+0x12>
  3e:   15c0 0005       mov $5, r0
  42:   1585            mov (sp)+, r5
  44:   0087            rts pc
  46:   15c0 0006       mov $6, r0
  4a:   1585            mov (sp)+, r5
  4c:   0087            rts pc

00000000 <fun0>:
   0:   00a03533            snez    x10,x10
   4:   0515                    addi    x10,x10,5
   6:   8082                    ret

00000008 <fun1>:
   8:   00a03533            snez    x10,x10
   c:   0515                    addi    x10,x10,5
   e:   8082                    ret

00000010 <fun2>:
  10:   00a03533            snez    x10,x10
  14:   0515                    addi    x10,x10,5
  16:   8082                    ret

und Compiler

Mit diesem i-Code würde man erwarten, dass auch die verschiedenen Ziele übereinstimmen

define i32 @fun0(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %. = select i1 %1, i32 6, i32 5
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
  %1 = icmp eq i32 %condition, 0
  %. = select i1 %1, i32 5, i32 6
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %2 = select i1 %1, i32 6, i32 5
  ret i32 %2
}


00000000 <fun0>:
   0:   e3a01005    mov r1, #5
   4:   e3500000    cmp r0, #0
   8:   13a01006    movne   r1, #6
   c:   e1a00001    mov r0, r1
  10:   e12fff1e    bx  lr

00000014 <fun1>:
  14:   e3a01006    mov r1, #6
  18:   e3500000    cmp r0, #0
  1c:   03a01005    moveq   r1, #5
  20:   e1a00001    mov r0, r1
  24:   e12fff1e    bx  lr

00000028 <fun2>:
  28:   e3a01005    mov r1, #5
  2c:   e3500000    cmp r0, #0
  30:   13a01006    movne   r1, #6
  34:   e1a00001    mov r0, r1
  38:   e12fff1e    bx  lr


fun0:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB0_2
    mov.w   #5, r15
.LBB0_2:
    pop.w   r4
    ret

fun1:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #5, r15
    cmp.w   #0, r12
    jeq .LBB1_2
    mov.w   #6, r15
.LBB1_2:
    pop.w   r4
    ret


fun2:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB2_2
    mov.w   #5, r15
.LBB2_2:
    pop.w   r4
    ret

Technisch gesehen gibt es bei einigen dieser Lösungen einen Leistungsunterschied. Manchmal ist das Ergebnis 5, wenn ein Sprung über das Ergebnis 6 Code ist, und umgekehrt, ist ein Zweig schneller als die Ausführung? man könnte argumentieren, aber die Ausführung sollte variieren. Dies ist jedoch eher eine if-Bedingung als eine if not-Bedingung im Code, die dazu führt, dass der Compiler die if-Funktion ausführt, wenn dieser Sprung über else ausgeführt wird. Dies ist jedoch nicht unbedingt auf den Codierungsstil zurückzuführen, sondern auf den Vergleich und die Fälle if und else in welcher Syntax auch immer.


0

Ok, da Assembly eines der Tags ist, gehe ich einfach davon aus, dass Ihr Code Pseudocode ist (und nicht unbedingt c), und übersetze ihn vom Menschen in 6502-Assembly.

1. Option (ohne sonst)

        ldy #$00
        lda #$05
        dey
        bmi false
        lda #$06
false   brk

2. Option (mit sonst)

        ldy #$00
        dey
        bmi else
        lda #$06
        sec
        bcs end
else    lda #$05
end     brk

Annahmen: Bedingung ist im Y-Register. Setzen Sie dies in der ersten Zeile einer der beiden Optionen auf 0 oder 1, das Ergebnis wird im Akkumulator angezeigt.

Nachdem wir die Zyklen für beide Möglichkeiten eines jeden Falles gezählt haben, sehen wir, dass das 1. Konstrukt im Allgemeinen schneller ist; 9 Zyklen, wenn die Bedingung 0 ist, und 10 Zyklen, wenn die Bedingung 1 ist, während Option zwei ebenfalls 9 Zyklen ist, wenn die Bedingung 0 ist, aber 13 Zyklen, wenn die Bedingung 1 ist ( Zykluszählungen enthalten nicht die BRKam Ende ).

Fazit: If onlyist schneller als If-Elsekonstruieren.

Der Vollständigkeit halber hier eine optimierte value = condition + 5Lösung:

ldy #$00
lda #$00
tya
adc #$05
brk

Dies verkürzt unsere Zeit auf 8 Zyklen ( wieder ohne die BRKam Ende ).


6
Unglücklicherweise für diese Antwort führt das Einspeisen des gleichen Quellcodes in einen C-Compiler (oder in einen C ++ - Compiler) zu einer völlig anderen Ausgabe als das Einspeisen in Glen's Gehirn. Es gibt keinen Unterschied, kein "Optimierungs" -Potential zwischen den Alternativen auf Quellcode-Ebene. Verwenden Sie einfach die am besten lesbare (vermutlich die if / else).
Quuxplusone

1
@Ja. Der Compiler optimiert entweder beide Varianten auf die schnellste Version oder fügt zusätzlichen Aufwand hinzu, der den Unterschied zwischen beiden bei weitem überwiegt. Oder beides.
Jpaugh

1
Die Annahme, dass es " nicht unbedingt C " ist, scheint eine vernünftige Wahl zu sein, da die Frage als C ++ gekennzeichnet ist (leider gelingt es ihr nicht, die Typen der beteiligten Variablen zu deklarieren ).
Toby Speight
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.