2008-09-10 9 views
6

Welche kompiliert zu schneller Code: "ans = n * 3" oder "ans = n + (n * 2)"?Welche kompiliert zu schneller Code: "n * 3" oder "n + (n * 2)"?

Angenommen, dass n entweder ein Int oder ein Lang ist, und es ist auf einer modernen Win32-Intel-Box läuft.

Wäre dies anders, wenn eine Dereferenzierung beteiligt wäre, dh welche wäre schneller?

 

long a; 
long *pn; 
long  ans; 

... 
*pn = some_number; 
ans = *pn * 3; 

Oder

 
ans = *pn+(*pn*2); 

Oder es ist etwas, das man braucht sich keine Sorgen darüber, wie optimierende Compiler sind wahrscheinlich für diese in jedem Fall zu berücksichtigen?

Antwort

55

IMO solche Mikro-Optimierung ist nicht notwendig, es sei denn, Sie arbeiten mit einigen exotischen Compiler. Ich würde Lesbarkeit auf den ersten Platz bringen.

1

Es hängt von dem Compiler ab, den Sie tatsächlich verwenden, aber sehr wahrscheinlich übersetzen sie in den gleichen Code.

Sie können es selbst überprüfen, indem Sie ein kleines Testprogramm erstellen und dessen Ausbau überprüfen.

1

Die meisten Compiler sind intelligent genug, um eine ganzzahlige Multiplikation in eine Reihe von Bitverschiebungen und -additionen zu zerlegen. Ich weiß nichts über Windows-Compiler, aber zumindest mit Gcc können Sie es den Assembler ausspucken, und wenn Sie sich das ansehen, können Sie wahrscheinlich identische Assembler für beide Schreibweisen sehen.

0

Compiler sind gut darin, Code wie Ihren zu optimieren. Jeder moderne Compiler würde für beide Fälle den gleichen Code erzeugen und zusätzlich * 2 durch eine Linksverschiebung ersetzen.

+0

Seien Sie nicht so sicher :) Ich sah einige wirklich komische Compiler für Embedded-Software-Entwicklung. – aku

+1

In eingebetteten Systemen endet fast alles konventionelle Wissen. ;-) –

4

Dies hängt vom Compiler, seiner Konfiguration und dem umgebenden Code ab.

Sie sollten nicht versuchen zu erraten, ob die Dinge "schneller" sind, ohne Messungen zu machen.

Im Allgemeinen Sie sollen nicht über diese Art von Sachen nanoskaligen Optimierung heutzutage Sorge - es ist fast immer eine vollständige Bedeutungslosigkeit, und wenn Sie wirklich in einer Domäne arbeiten, wo es darauf ankam, würden Sie bereits einen Profiler verwenden und auf der Suche in der Assemblersprachausgabe des Compilers.

10

Da es einfach ist, es selbst zu messen, warum tun Sie das nicht? (Mit gcc und time von Cygwin)

/* test1.c */ 
int main() 
{ 
    int result = 0; 
    int times = 1000000000; 
    while (--times) 
     result = result * 3; 
    return result; 
} 

machine:~$ gcc -O2 test1.c -o test1 
machine:~$ time ./test1.exe 

real 0m0.673s 
user 0m0.608s 
sys  0m0.000s 

für ein paar Mal den Test durchführen und für den anderen Fall wiederholen.

Wenn Sie an dem Assembler-Code spähen, gcc -S -O2 test1.c

+0

Leider ist das ein schlechtes Beispiel - mit i686-apple-darwin8-gcc-4.0.1 entfernt es das "result = result * 3" vollständig aus der Schleife, da es immer Null ist. Ändern der Anfangsbedingung zu "Ergebnis = 1" ergibt ein besseres Ergebnis. –

+0

oder besser, erstellen Sie ein Array von Zufallszahlen und verarbeiten Sie das, damit der Compiler keine Annahmen treffen kann. – DarenW

15

Es spielt keine Rolle. Moderne Prozessoren können einen Ganzzahl-MUL-Befehl in einem Taktzyklus oder weniger ausführen, im Gegensatz zu älteren Prozessoren, die eine Reihe von Verschiebungen durchführen und intern hinzufügen mussten, um die MUL auszuführen, wodurch mehrere Zyklen verwendet werden.Ich würde wetten, dass

MUL EAX,3 

führt schneller als

MOV EBX,EAX 
SHL EAX,1 
ADD EAX,EBX 

Der letzte Prozessor, wo diese Art von Optimierung nützlich war gewesen wäre wahrscheinlich die 486. (ja, das ist voreingenommen zu Intel-Prozessoren, ist aber vermutlich auch Vertreter anderer Architekturen).

In jedem Fall sollte jeder vernünftige Compiler in der Lage sein, den kleinsten/schnellsten Code zu generieren. Also immer zuerst mit Lesbarkeit gehen.

+3

Ich bezweifle wirklich, dass die MUL schneller ausgeführt wird, sobald die Latenz und Inflexibilität über die Register, die Sie verwenden können, berücksichtigt werden. Außerdem würde auf LEX, nicht auf der 3-Befehlssequenz, die LEA, von jedem vernünftigen Compiler für 3 * n und n + 2 * n benutzt werden. –

+1

Wahr, aber LEA ist nur nützlich, wenn man mit einem kleinen Satz von Konstanten multipliziert (2, 3, 4, 5, 8 und 9, wenn ich mich richtig erinnere). Wie auch immer, ich wollte den Compiler den schnellsten Code herausfinden lassen. – Ferruccio

4

Es ist nicht schwer herauszufinden, was der Compiler mit Ihrem Code macht (ich benutze DevStudio 2005 hier). Schreiben Sie ein einfaches Programm mit dem folgenden Code:

int i = 45, j, k; 
j = i * 3; 
k = i + (i * 2); 

Platz einen Haltepunkt auf der Mittellinie und führen Sie den Code, um den Debugger. Wenn der Haltepunkt ausgelöst wird, klicken Sie mit der rechten Maustaste auf die Quelldatei und wählen Sie "Zur Demontage wechseln". Sie haben jetzt ein Fenster mit dem Code, den die CPU ausführt. Sie werden in diesem Fall bemerken, dass die letzten zwei Zeilen genau die gleichen Anweisungen erzeugen, nämlich "lea eax, [ebx + ebx * 2]" (nicht bitverschiebend und addierend in diesem speziellen Fall). Auf einer modernen IA32-CPU ist es wahrscheinlich effizienter, eine geradlinige MUL zu machen als eine Bitverschiebung aufgrund der Pipeline-Natur der CPU, die eine Strafe verursacht, wenn ein modifizierter Wert zu früh verwendet wird.

Dies zeigt, was aku spricht, nämlich, Compiler sind schlau genug, um die besten Anweisungen für Ihren Code auszuwählen.

+0

Ich bin nicht die Pipeline ein Problem. Die Recheneinheit kann wahrscheinlich ebx + ebx * 2 intern in einem Schritt übernehmen. – Artelius

0

Vertrauen Sie Ihrem Compiler, um kleine Teile des Codes so zu optimieren. Lesbarkeit ist auf der Codeebene viel wichtiger. Echte Optimierung sollte auf einer höheren Ebene erfolgen.

1

Es ist egal. Ich denke, dass es wichtigere Dinge zu optimieren gibt. Wie viel Zeit haben Sie investiert, um diese Frage zu denken und zu schreiben, anstatt selbst zu programmieren und zu testen?

:-)

1

Solange man einen anständigen optimierenden Compiler verwenden, nur Code schreiben, die einfach für den Compiler zu verstehen. Dies erleichtert es dem Compiler, clevere Optimierungen durchzuführen.

Wenn Sie diese Frage stellen, bedeutet dies, dass ein optimierender Compiler mehr über die Optimierung weiß als Sie. Also vertraue dem Compiler. Verwenden Sie n * 3.

Schauen Sie sich auch this answer an.