In C ++, was ist schneller? (2 * i + 1) oder (i 1 | 1)?

8

Ich verstehe, dass die Antwort wahrscheinlich Hardware-spezifisch ist, aber ich bin neugierig, ob es eine allgemeinere Intuition gibt, die ich vermisse?

Ich habe diese Frage & amp; Angesichts der Antwort frage ich mich nun, ob ich meinen Ansatz im Allgemeinen ändern sollte, um "(i & lt; & lt; 1 | 1)" anstelle von "(2 * i + 1)" zu verwenden?

    
M. Tibbits 07.12.2010, 04:44
quelle

8 Antworten

8
___ antwort4378849 ___

Nur ein Experiment bezüglich der Antworten zu "... es wird LEA " verwendet:
Der folgende Code:

%Vor%

wird mit gcc -fomit-frame-pointer -O8 -m{32|64} (für 32bit oder 64bit) in den folgenden Assembly-Code kompilieren:

  1. x86, 32bit:
    int main(int argc, char **argv)
    {
    #ifdef USE_SHIFTOR
    return (argc << 1 | 1);
    #else
    return (2 * argc + 1);
    #endif
    }
    
  2. x86, 64Bit:
    080483a0 <main>:
    80483a0:    8b 44 24 04             mov    0x4(%esp),%eax
    80483a4:    8d 44 00 01             lea    0x1(%eax,%eax,1),%eax
    80483a8:    c3                      ret
  3. x86, 64Bit, -DUSE_SHIFTOR :
    00000000004004c0 <main>:
    4004c0: 8d 44 3f 01             lea    0x1(%rdi,%rdi,1),%eax
    4004c4: c3                      retq
  4. x86, 32bit, -DUSE_SHIFTOR :
    080483a0 <main>:
    80483a0:    8b 44 24 04             mov    0x4(%esp),%eax
    80483a4:    01 c0                   add    %eax,%eax
    80483a6:    83 c8 01                or     
    00000000004004c0 <main>:
    4004c0: 8d 04 3f                lea    (%rdi,%rdi,1),%eax
    4004c3: 83 c8 01                or     %pre%x1,%eax
    4004c6: c3                      retq
    x1,%eax 80483a9: c3 ret

Tatsächlich stimmt es, dass die meisten Fälle LEA verwenden. Dennoch ist der Code für beide Fälle gleich nicht . Dafür gibt es zwei Gründe:

    Der Zusatz
  1. kann überlaufen und umlaufen, während Bitoperationen wie << oder | nicht möglich sind
  2. (x + 1) == (x | 1) ist nur wahr, wenn !(x & 1) else die Addition auf das nächste Bit überträgt. Im Allgemeinen führt das Hinzufügen von nur dazu, dass in der Hälfte der Fälle das niedrigste Bit gesetzt wird.

Während wir (und der Compiler, wahrscheinlich) wissen, dass der zweite notwendigerweise anwendbar ist, ist der erste immer noch eine Möglichkeit. Der Compiler erzeugt daher einen anderen Code, da die "oder-Version" erfordert, dass Bit 0 auf 1 gesetzt wird.

    
___ qstntxt ___

Ich verstehe, dass die Antwort wahrscheinlich Hardware-spezifisch ist, aber ich bin neugierig, ob es eine allgemeinere Intuition gibt, die ich vermisse?

Ich habe diese Frage & amp; Angesichts der Antwort frage ich mich nun, ob ich meinen Ansatz im Allgemeinen ändern sollte, um "(i & lt; & lt; 1 | 1)" anstelle von "(2 * i + 1)" zu verwenden?

    
___ qstnhdr ___ In C ++, was ist schneller? (2 * i + 1) oder (i 1 | 1)? ___ antwort4373527 ___

Ausgabe von gcc mit der Option -S (keine Compilerflags angegeben):

%Vor%

Ich bin nicht sicher, welches ist was, aber ich glaube nicht, dass es wichtig ist.

Wenn der Compiler überhaupt keine Optimierungen vornimmt, würde die zweite wahrscheinlich zu schnelleren Assemblerbefehlen führen. Wie lange jede Anweisung dauert, hängt vollständig von der Architektur ab. Die meisten Compiler optimieren sie so, dass sie die gleichen Anweisungen auf Baugruppenebene haben.

    
___ answer12025811 ___

Ich habe das gerade mit gcc-4.7.1 unter Verwendung der Quelle von FrankH getestet, der generierte Code ist

%Vor%

egal, ob die Shift- oder die Multiplikationsversion verwendet wird.

    
___ antwort4376029 ___

%code% ist möglicherweise schneller als die anderen beiden, weil Addition schneller ist als Multiplikation und schneller sein kann als shift.

    
___ answer4373837 ___

Niemand interessiert sich. Noch sollten sie.
Hör auf, dir darüber Sorgen zu machen und deinen Code korrekt, einfach und fertig zu machen.

    
___ tag123c ___ C ++ ist eine universelle Programmiersprache. Es wurde ursprünglich als Erweiterung von C entworfen und behält eine ähnliche Syntax, ist aber jetzt eine völlig andere Sprache. Verwenden Sie dieses Tag für Fragen zu Code, der mit einem C ++ - Compiler kompiliert werden soll. ___ tag123performance ___ Für Fragen zur Messung oder Verbesserung der Code- und Anwendungseffizienz. ___ tag123bitshift ___ Eine Bit-Shift-Operation verschiebt die in einer Binärzahl oder einem Bitmuster enthaltenen Bits nach links oder rechts. ___ answer4377505 ___

Je schneller die erste Form ist (diejenige mit der Rechtsverschiebung), tatsächlich dauert die Ausführung der shr-Anweisung im schlimmsten Fall 4 Taktzyklen, im besten Fall die Mul 10. Die beste Form sollte jedoch vom Compiler entschieden werden, da sie eine vollständige Sicht auf die anderen (Assembly-) Anweisungen hat.

    
___ answer4373506 ___

Da der ISO-Standard keine Leistungsanforderungen vorschreibt, hängt dies von der Implementierung, den ausgewählten Compiler-Flags, der Ziel-CPU und möglicherweise der Phase des Mondes ab.

Diese Art von Optimierungen (die ein paar Zyklen sparen) verblassen fast immer in der Geringfügigkeit in Bezug auf den Return on Investment, gegen Makro-Level-Optimierungen wie die Algorithmusauswahl.

Achten Sie in erster Linie auf die Lesbarkeit des Codes. Wenn Sie beabsichtigen, Bits und %code% zu verschieben, verwenden Sie die Bit-Shift-Version. Wenn Sie beabsichtigen, zu multiplizieren, verwenden Sie die %code% Version. Sorgen Sie sich erst um die Leistung, wenn Sie festgestellt haben, dass ein Problem vorliegt.

Jeder ordentliche Compiler wird es weit besser optimieren als Sie es können: -)

    
___ answer4373500 ___

Jeder außer dem hirntoten Compiler sieht diese Ausdrücke als äquivalent und kompiliert sie zu demselben ausführbaren Code.

In der Regel lohnt es sich nicht, sich allzu viele Gedanken über die Optimierung einfacher arithmetischer Ausdrücke wie dieser zu machen, da Compiler am besten optimiert werden können. (Im Gegensatz zu vielen anderen Fällen, in denen ein "intelligenter Compiler" das Richtige tun könnte, fällt jedoch ein tatsächlicher Compiler aus.)

Dies wird übrigens zu dem gleichen Paar von Anweisungen auf PPC, Sparc und MIPS führen: eine Verschiebung, gefolgt von einem Add. Auf dem ARM wird es auf einen einzelnen fusionierten Shift-Add-Befehl herunterkochen, und auf x86 wird es wahrscheinlich ein einzelnes %code% op.

sein     
___ tag123assembly ___ Assemblersprache (asm) Programmierfragen. Achten Sie darauf, auch mit dem Prozessor und / oder Befehlssatz, die Sie verwenden, sowie den Assembler TAG. WARNUNG: Verwenden Sie für .NET-Assemblies stattdessen das Tag [.net-assembly]. Verwenden Sie für Java ASM stattdessen das Tag [java-bytecode-asm]. ___
FrankH. 07.12.2010, 16:14
quelle
13

Da der ISO-Standard keine Leistungsanforderungen vorschreibt, hängt dies von der Implementierung, den ausgewählten Compiler-Flags, der Ziel-CPU und möglicherweise der Phase des Mondes ab.

Diese Art von Optimierungen (die ein paar Zyklen sparen) verblassen fast immer in der Geringfügigkeit in Bezug auf den Return on Investment, gegen Makro-Level-Optimierungen wie die Algorithmusauswahl.

Achten Sie in erster Linie auf die Lesbarkeit des Codes. Wenn Sie beabsichtigen, Bits und OR zu verschieben, verwenden Sie die Bit-Shift-Version. Wenn Sie beabsichtigen, zu multiplizieren, verwenden Sie die * Version. Sorgen Sie sich erst um die Leistung, wenn Sie festgestellt haben, dass ein Problem vorliegt.

Jeder ordentliche Compiler wird es weit besser optimieren als Sie es können: -)

    
paxdiablo 07.12.2010 04:50
quelle
5

Jeder außer dem hirntoten Compiler sieht diese Ausdrücke als äquivalent und kompiliert sie zu demselben ausführbaren Code.

In der Regel lohnt es sich nicht, sich allzu viele Gedanken über die Optimierung einfacher arithmetischer Ausdrücke wie dieser zu machen, da Compiler am besten optimiert werden können. (Im Gegensatz zu vielen anderen Fällen, in denen ein "intelligenter Compiler" das Richtige tun könnte, fällt jedoch ein tatsächlicher Compiler aus.)

Dies wird übrigens zu dem gleichen Paar von Anweisungen auf PPC, Sparc und MIPS führen: eine Verschiebung, gefolgt von einem Add. Auf dem ARM wird es auf einen einzelnen fusionierten Shift-Add-Befehl herunterkochen, und auf x86 wird es wahrscheinlich ein einzelnes LEA op.

sein     
Crashworks 07.12.2010 04:48
quelle
4
___ antwort4378849 ___

Nur ein Experiment bezüglich der Antworten zu "... es wird %code% " verwendet:
Der folgende Code:

%Vor%

wird mit %code% (für 32bit oder 64bit) in den folgenden Assembly-Code kompilieren:

  1. x86, 32bit:
    .LCFI3:
            movl    8(%ebp), %eax
            addl    %eax, %eax
            orl     , %eax
            popl    %ebp
            ret
    
    .LCFI1:
            movl    8(%ebp), %eax
            addl    %eax, %eax
            addl    , %eax
            popl    %ebp
            ret
    
  2. x86, 64Bit: %pre%
  3. x86, 64Bit, %code% : %pre%
  4. x86, 32bit, %code% : %pre%

Tatsächlich stimmt es, dass die meisten Fälle %code% verwenden. Dennoch ist der Code für beide Fälle gleich nicht . Dafür gibt es zwei Gründe:

    Der Zusatz
  1. kann überlaufen und umlaufen, während Bitoperationen wie %code% oder %code% nicht möglich sind
  2. %code% ist nur wahr, wenn %code% else die Addition auf das nächste Bit überträgt. Im Allgemeinen führt das Hinzufügen von nur dazu, dass in der Hälfte der Fälle das niedrigste Bit gesetzt wird.

Während wir (und der Compiler, wahrscheinlich) wissen, dass der zweite notwendigerweise anwendbar ist, ist der erste immer noch eine Möglichkeit. Der Compiler erzeugt daher einen anderen Code, da die "oder-Version" erfordert, dass Bit 0 auf 1 gesetzt wird.

    
___ qstntxt ___

Ich verstehe, dass die Antwort wahrscheinlich Hardware-spezifisch ist, aber ich bin neugierig, ob es eine allgemeinere Intuition gibt, die ich vermisse?

Ich habe diese Frage & amp; Angesichts der Antwort frage ich mich nun, ob ich meinen Ansatz im Allgemeinen ändern sollte, um "(i & lt; & lt; 1 | 1)" anstelle von "(2 * i + 1)" zu verwenden?

    
___ qstnhdr ___ In C ++, was ist schneller? (2 * i + 1) oder (i 1 | 1)? ___ antwort4373527 ___

Ausgabe von gcc mit der Option -S (keine Compilerflags angegeben):

%Vor%

Ich bin nicht sicher, welches ist was, aber ich glaube nicht, dass es wichtig ist.

Wenn der Compiler überhaupt keine Optimierungen vornimmt, würde die zweite wahrscheinlich zu schnelleren Assemblerbefehlen führen. Wie lange jede Anweisung dauert, hängt vollständig von der Architektur ab. Die meisten Compiler optimieren sie so, dass sie die gleichen Anweisungen auf Baugruppenebene haben.

    
___ answer12025811 ___

Ich habe das gerade mit gcc-4.7.1 unter Verwendung der Quelle von FrankH getestet, der generierte Code ist

%Vor%

egal, ob die Shift- oder die Multiplikationsversion verwendet wird.

    
___ antwort4376029 ___

%code% ist möglicherweise schneller als die anderen beiden, weil Addition schneller ist als Multiplikation und schneller sein kann als shift.

    
___ answer4373837 ___

Niemand interessiert sich. Noch sollten sie.
Hör auf, dir darüber Sorgen zu machen und deinen Code korrekt, einfach und fertig zu machen.

    
___ tag123c ___ C ++ ist eine universelle Programmiersprache. Es wurde ursprünglich als Erweiterung von C entworfen und behält eine ähnliche Syntax, ist aber jetzt eine völlig andere Sprache. Verwenden Sie dieses Tag für Fragen zu Code, der mit einem C ++ - Compiler kompiliert werden soll. ___ tag123performance ___ Für Fragen zur Messung oder Verbesserung der Code- und Anwendungseffizienz. ___ tag123bitshift ___ Eine Bit-Shift-Operation verschiebt die in einer Binärzahl oder einem Bitmuster enthaltenen Bits nach links oder rechts. ___ answer4377505 ___

Je schneller die erste Form ist (diejenige mit der Rechtsverschiebung), tatsächlich dauert die Ausführung der shr-Anweisung im schlimmsten Fall 4 Taktzyklen, im besten Fall die Mul 10. Die beste Form sollte jedoch vom Compiler entschieden werden, da sie eine vollständige Sicht auf die anderen (Assembly-) Anweisungen hat.

    
___ answer4373506 ___

Da der ISO-Standard keine Leistungsanforderungen vorschreibt, hängt dies von der Implementierung, den ausgewählten Compiler-Flags, der Ziel-CPU und möglicherweise der Phase des Mondes ab.

Diese Art von Optimierungen (die ein paar Zyklen sparen) verblassen fast immer in der Geringfügigkeit in Bezug auf den Return on Investment, gegen Makro-Level-Optimierungen wie die Algorithmusauswahl.

Achten Sie in erster Linie auf die Lesbarkeit des Codes. Wenn Sie beabsichtigen, Bits und %code% zu verschieben, verwenden Sie die Bit-Shift-Version. Wenn Sie beabsichtigen, zu multiplizieren, verwenden Sie die %code% Version. Sorgen Sie sich erst um die Leistung, wenn Sie festgestellt haben, dass ein Problem vorliegt.

Jeder ordentliche Compiler wird es weit besser optimieren als Sie es können: -)

    
___ answer4373500 ___

Jeder außer dem hirntoten Compiler sieht diese Ausdrücke als äquivalent und kompiliert sie zu demselben ausführbaren Code.

In der Regel lohnt es sich nicht, sich allzu viele Gedanken über die Optimierung einfacher arithmetischer Ausdrücke wie dieser zu machen, da Compiler am besten optimiert werden können. (Im Gegensatz zu vielen anderen Fällen, in denen ein "intelligenter Compiler" das Richtige tun könnte, fällt jedoch ein tatsächlicher Compiler aus.)

Dies wird übrigens zu dem gleichen Paar von Anweisungen auf PPC, Sparc und MIPS führen: eine Verschiebung, gefolgt von einem Add. Auf dem ARM wird es auf einen einzelnen fusionierten Shift-Add-Befehl herunterkochen, und auf x86 wird es wahrscheinlich ein einzelnes %code% op.

sein     
___ tag123assembly ___ Assemblersprache (asm) Programmierfragen. Achten Sie darauf, auch mit dem Prozessor und / oder Befehlssatz, die Sie verwenden, sowie den Assembler TAG. WARNUNG: Verwenden Sie für .NET-Assemblies stattdessen das Tag [.net-assembly]. Verwenden Sie für Java ASM stattdessen das Tag [java-bytecode-asm]. ___
Jonathan Sternberg 07.12.2010 04:54
quelle
1

Ich habe das gerade mit gcc-4.7.1 unter Verwendung der Quelle von FrankH getestet, der generierte Code ist

%Vor%

egal, ob die Shift- oder die Multiplikationsversion verwendet wird.

    
hirschhornsalz 19.08.2012 10:27
quelle
0

Niemand interessiert sich. Noch sollten sie.
Hör auf, dir darüber Sorgen zu machen und deinen Code korrekt, einfach und fertig zu machen.

    
Stephen Hazel 07.12.2010 06:02
quelle
0
___ antwort4378849 ___

Nur ein Experiment bezüglich der Antworten zu "... es wird i + i + 1 " verwendet:
Der folgende Code:

%Vor%

wird mit %code% (für 32bit oder 64bit) in den folgenden Assembly-Code kompilieren:

  1. x86, 32bit: %pre%
  2. x86, 64Bit: %pre%
  3. x86, 64Bit, %code% : %pre%
  4. x86, 32bit, %code% : %pre%

Tatsächlich stimmt es, dass die meisten Fälle %code% verwenden. Dennoch ist der Code für beide Fälle gleich nicht . Dafür gibt es zwei Gründe:

    Der Zusatz
  1. kann überlaufen und umlaufen, während Bitoperationen wie %code% oder %code% nicht möglich sind
  2. %code% ist nur wahr, wenn %code% else die Addition auf das nächste Bit überträgt. Im Allgemeinen führt das Hinzufügen von nur dazu, dass in der Hälfte der Fälle das niedrigste Bit gesetzt wird.

Während wir (und der Compiler, wahrscheinlich) wissen, dass der zweite notwendigerweise anwendbar ist, ist der erste immer noch eine Möglichkeit. Der Compiler erzeugt daher einen anderen Code, da die "oder-Version" erfordert, dass Bit 0 auf 1 gesetzt wird.

    
___ qstntxt ___

Ich verstehe, dass die Antwort wahrscheinlich Hardware-spezifisch ist, aber ich bin neugierig, ob es eine allgemeinere Intuition gibt, die ich vermisse?

Ich habe diese Frage & amp; Angesichts der Antwort frage ich mich nun, ob ich meinen Ansatz im Allgemeinen ändern sollte, um "(i & lt; & lt; 1 | 1)" anstelle von "(2 * i + 1)" zu verwenden?

    
___ qstnhdr ___ In C ++, was ist schneller? (2 * i + 1) oder (i 1 | 1)? ___ antwort4373527 ___

Ausgabe von gcc mit der Option -S (keine Compilerflags angegeben):

%Vor%

Ich bin nicht sicher, welches ist was, aber ich glaube nicht, dass es wichtig ist.

Wenn der Compiler überhaupt keine Optimierungen vornimmt, würde die zweite wahrscheinlich zu schnelleren Assemblerbefehlen führen. Wie lange jede Anweisung dauert, hängt vollständig von der Architektur ab. Die meisten Compiler optimieren sie so, dass sie die gleichen Anweisungen auf Baugruppenebene haben.

    
___ answer12025811 ___

Ich habe das gerade mit gcc-4.7.1 unter Verwendung der Quelle von FrankH getestet, der generierte Code ist

%Vor%

egal, ob die Shift- oder die Multiplikationsversion verwendet wird.

    
___ antwort4376029 ___

%code% ist möglicherweise schneller als die anderen beiden, weil Addition schneller ist als Multiplikation und schneller sein kann als shift.

    
___ answer4373837 ___

Niemand interessiert sich. Noch sollten sie.
Hör auf, dir darüber Sorgen zu machen und deinen Code korrekt, einfach und fertig zu machen.

    
___ tag123c ___ C ++ ist eine universelle Programmiersprache. Es wurde ursprünglich als Erweiterung von C entworfen und behält eine ähnliche Syntax, ist aber jetzt eine völlig andere Sprache. Verwenden Sie dieses Tag für Fragen zu Code, der mit einem C ++ - Compiler kompiliert werden soll. ___ tag123performance ___ Für Fragen zur Messung oder Verbesserung der Code- und Anwendungseffizienz. ___ tag123bitshift ___ Eine Bit-Shift-Operation verschiebt die in einer Binärzahl oder einem Bitmuster enthaltenen Bits nach links oder rechts. ___ answer4377505 ___

Je schneller die erste Form ist (diejenige mit der Rechtsverschiebung), tatsächlich dauert die Ausführung der shr-Anweisung im schlimmsten Fall 4 Taktzyklen, im besten Fall die Mul 10. Die beste Form sollte jedoch vom Compiler entschieden werden, da sie eine vollständige Sicht auf die anderen (Assembly-) Anweisungen hat.

    
___ answer4373506 ___

Da der ISO-Standard keine Leistungsanforderungen vorschreibt, hängt dies von der Implementierung, den ausgewählten Compiler-Flags, der Ziel-CPU und möglicherweise der Phase des Mondes ab.

Diese Art von Optimierungen (die ein paar Zyklen sparen) verblassen fast immer in der Geringfügigkeit in Bezug auf den Return on Investment, gegen Makro-Level-Optimierungen wie die Algorithmusauswahl.

Achten Sie in erster Linie auf die Lesbarkeit des Codes. Wenn Sie beabsichtigen, Bits und %code% zu verschieben, verwenden Sie die Bit-Shift-Version. Wenn Sie beabsichtigen, zu multiplizieren, verwenden Sie die %code% Version. Sorgen Sie sich erst um die Leistung, wenn Sie festgestellt haben, dass ein Problem vorliegt.

Jeder ordentliche Compiler wird es weit besser optimieren als Sie es können: -)

    
___ answer4373500 ___

Jeder außer dem hirntoten Compiler sieht diese Ausdrücke als äquivalent und kompiliert sie zu demselben ausführbaren Code.

In der Regel lohnt es sich nicht, sich allzu viele Gedanken über die Optimierung einfacher arithmetischer Ausdrücke wie dieser zu machen, da Compiler am besten optimiert werden können. (Im Gegensatz zu vielen anderen Fällen, in denen ein "intelligenter Compiler" das Richtige tun könnte, fällt jedoch ein tatsächlicher Compiler aus.)

Dies wird übrigens zu dem gleichen Paar von Anweisungen auf PPC, Sparc und MIPS führen: eine Verschiebung, gefolgt von einem Add. Auf dem ARM wird es auf einen einzelnen fusionierten Shift-Add-Befehl herunterkochen, und auf x86 wird es wahrscheinlich ein einzelnes %code% op.

sein     
___ tag123assembly ___ Assemblersprache (asm) Programmierfragen. Achten Sie darauf, auch mit dem Prozessor und / oder Befehlssatz, die Sie verwenden, sowie den Assembler TAG. WARNUNG: Verwenden Sie für .NET-Assemblies stattdessen das Tag [.net-assembly]. Verwenden Sie für Java ASM stattdessen das Tag [java-bytecode-asm]. ___
Abyx 07.12.2010 11:18
quelle
-2

Je schneller die erste Form ist (diejenige mit der Rechtsverschiebung), tatsächlich dauert die Ausführung der shr-Anweisung im schlimmsten Fall 4 Taktzyklen, im besten Fall die Mul 10. Die beste Form sollte jedoch vom Compiler entschieden werden, da sie eine vollständige Sicht auf die anderen (Assembly-) Anweisungen hat.

    
BlackBear 07.12.2010 14:11
quelle