2014-11-15 8 views
71

Angenommen, ich habe folgende C-Code:Verzögern temporäre Variablen mein Programm?

int i = 5; 
int j = 10; 
int result = i + j; 

Wenn ich über dies oft bin Looping, wäre es schneller sein int result = 5 + 10 zu benutzen? Ich erstelle oft temporäre Variablen, um meinen Code lesbarer zu machen, zum Beispiel, wenn die zwei Variablen von einem Array mit einem langen Ausdruck zur Berechnung der Indizes erhalten wurden. Ist das in C leistungstechnisch schlecht? Was ist mit anderen Sprachen?

+44

Durch die Optimierung des Compilers wird dieser Code wirksam geändert: 'int result = 15;' – 2501

+13

Compiler wird Ihren Code optimieren. Es ist produktiver, sich auf Dinge wie (teilweise) eine Berechnung zu konzentrieren, die innerhalb einer Schleife wiederholt wird, die besser gemacht werden würde, bevor die Schleife beginnt. –

+5

Ich denke, dass er irgendwelche temporäre Variablen bedeutet, d. H. Mit a = b + c; d = a + e; langsamer als mit a = b + c + d + e; Es kann möglicherweise mehr Speicher verwenden, wenn es auf eine Weise ausgeführt wird, die der Compiler nicht optimieren kann, aber es sollte nicht langsamer sein. Beste Fokussierung oder Arbeitsproduktivität, es sei denn, es handelt sich um einen kommerziellen und kritischen Leistungscode. –

Antwort

83

Eine moderne optimierenden Compiler sollte diese Variablen entfernt, zum Beispiel optimiert, wenn wir das folgende Beispiel in godbolt mit gcc verwenden mit dem -std=c99 -O3 Flags (see it live):

#include <stdio.h> 

void func() 
{ 
    int i = 5; 
    int j = 10; 
    int result = i + j; 

    printf("%d\n", result) ; 
} 

es in der folgenden Montage führt :

movl $15, %esi 

für die Berechnung der i + j, ist diese Form der constant propagation.

Hinweis, habe ich die printf so dass wir eine Nebenwirkung haben, sonst hätte func optimiert wurden weg:

func: 
    rep ret 

Diese Optimierungen sind unter der erlaubt als-ob Regel, die nur verlangt der Compiler, um das beobachtbare Verhalten eines Programms zu emulieren. Dies wird im Entwurf der C99-Standard Abschnitt behandelt 5.1.2.3Programmausführung die sagt:

In der abstrakten Maschine, alle Ausdrücke werden von der Semantik angegeben ausgewertet. Eine tatsächliche Implementierung braucht keinen Teil eines Ausdrucks auszuwerten, wenn sie daraus ableiten kann, dass ihr Wert nicht verwendet wird und dass keine erforderlichen Nebenwirkungen erzeugt werden (einschließlich aller durch Aufruf einer -Funktion oder Zugriff auf ein flüchtiges Objekt). siehe

auch: Optimizing C++ Code : Constant-Folding

+2

Wie kann ich sehen, welche Assembly mein C-Code generiert? – syntagma

+0

@REACHUS Ich habe einen Link zum godbolt-Beispiel hinzugefügt, wo Sie die Assembly sehen können –

+0

Danke, aber welches Befehlszeilentool kann ich verwenden, stellt GCC irgendein Flag dafür zur Verfügung? – syntagma

10

Während alle Arten von trivialen Unterschiede zu dem Code kann der Compiler das Verhalten in einer Weise stören, die leicht verbessern oder die Leistung verschlechtern, im Prinzip sollte es keine Performance-Unterschied, ob Sie verwenden Temp-Variablen wie diese, solange die Bedeutung des Programms nicht geändert wird. Ein guter Compiler sollte den gleichen oder vergleichbaren Code in beide Richtungen generieren, es sei denn, Sie bauen absichtlich die Optimierung ab, um einen Maschinencode zu erhalten, der so nah wie möglich an der Quelle liegt (z. B. für Debugging-Zwecke).

+3

Der Punkt über "solange die Bedeutung des Programms nicht geändert wird" ist der Schlüssel. Es gibt viele Fälle, in denen zwei Arten, ein Programm zu schreiben, geringfügige semantische Unterschiede haben, die für einen Programmierer nicht von Bedeutung sind, aber einen Compiler benötigen würden, um viel weniger effizienten Code für einen zu erzeugen, als für den anderen benötigt würde. – supercat

+1

Genau was supercat sagte: Der Compiler kann nicht immer beweisen, dass zwei Zeiger/Arrays nicht überlappen, oder dass ein Funktionsaufruf den Inhalt des Speichers nicht ändern kann. Daher wird es manchmal gezwungen, mehrere Ladungen desselben Ortes zu erzeugen, aber die Verwendung von int a = arr [i] würde den Compiler veranlassen, den Wert in einem Register über Funktionsaufrufe hinweg zu halten und durch andere Zeiger zu schreiben. –

+0

Ich stimme den Aussagen von Supercat und Peter Cordes vollkommen zu. –

29

Dies ist eine einfache Aufgabe für einen optimierenden Compiler zu optimieren. Es löscht alle Variablen und ersetzt result durch 15.

Konstante Faltung in SSA form ist so ziemlich die grundlegende Optimierung, die es gibt.

+0

Ja, aber da "result" technisch nicht genutzt wird, wäre das Programm leer. – alexy13

+0

Wenn dies das ganze Programm ist, dann wäre es in der Tat leer. Wenn nicht, wird 15 in alle Verwendungen "eingefügt". – usr

11

Das Beispiel, das Sie angegeben haben, ist für einen Compiler einfach zu optimieren. Die Verwendung lokaler Variablen zum Zwischenspeichern von Werten, die aus globalen Strukturen und Arrays stammen, kann die Ausführung Ihres Codes beschleunigen.Wenn Sie zum Beispiel etwas von einer komplexen Struktur innerhalb einer for-Schleife holen, wo der Compiler nicht optimieren kann und Sie wissen, dass sich der Wert nicht ändert, können die lokalen Variablen ziemlich viel Zeit sparen.

Sie können GCC (auch andere Compiler) verwenden, um den Code der Zwischenbaugruppe zu generieren und zu sehen, was der Compiler tatsächlich macht.
Es gibt eine Diskussion darüber, wie man die Assembly-Listen hier anschaltet: Using GCC to produce readable assembly?

Es kann lehrreich sein, den generierten Code zu untersuchen und zu sehen, was ein Compiler tatsächlich macht.

+0

Dies ist die nützlichere Antwort. Die anderen gingen in den fortlaufenden Teil der Dinge und nicht in die lokale Kopie eines Speicherpunkts. Das Zuweisen von Globalen und Array-Elementen zu Locals ist oft hilfreich, wenn der Compiler nicht nachweisen kann, dass sich Eingabearrays nicht überlappen oder dass eine unbekannte Funktion keine Änderungen am Array vornehmen kann. Dies hält den Compiler oft davon ab, denselben Speicherort mehrmals neu zu laden. –

5

Sie leiden das gleiche Problem, das ich tue, wenn ich versuche herauszufinden, was ein Compiler tut - Sie machen ein triviales Programm, um das Problem zu demonstrieren, und untersuchen die Assembly-Ausgabe des Compilers, nur um zu erkennen, dass Der Compiler hat alles optimiert, was Sie versucht haben, um es zu beseitigen. Sie können zu wesentlich reduziert auch eine ziemlich komplexe Operation in main() finden:

push "%i" 
push 42 
call printf 
ret 

Ihre ursprüngliche Frage ist nicht „was mit int i = 5; int j = 10... passiert?“ aber "verursachen temporäre Variablen im Allgemeinen eine Laufzeitstrafe?"

Die Antwort ist wahrscheinlich nicht. Aber Sie müssen sich die Assembly-Ausgabe für Ihren speziellen, nicht-trivialen Code ansehen. Wenn Ihre CPU viele Register hat, wie zum Beispiel ein ARM, dann sind i und j sehr wahrscheinlich in Registern, genau so, als würden diese Register den Rückgabewert einer Funktion direkt speichern. Zum Beispiel:

int i = func1(); 
int j = func2(); 
int result = i + j; 

ist mit ziemlicher Sicherheit genau der gleiche Maschinencode zu sein, wie:

int result = func1() + func2(); 

Ich schlage vor, Sie temporäre Variablen verwenden, wenn sie den Code leichter zu verstehen und zu pflegen, und wenn Sie machen‘ Wenn Sie wirklich versuchen, eine Schleife zu straffen, werden Sie ohnehin in den Baugruppenausgang schauen, um herauszufinden, wie Sie so viel Leistung wie möglich finalisieren können. Aber opfern Sie die Lesbarkeit und Wartbarkeit für einige Nanosekunden nicht, wenn dies nicht notwendig ist.

+0

Die Lösung besteht darin, die Compilerausgabe nach Funktionen zu betrachten, die mit Parametern arbeiten, anstatt 'main()', das auf Kompilierzeitkonstanten operiert. z.B. Hier ist ein einfaches Beispiel für die Summierung eines Float-Arrays mit gcc asm-Ausgabe von godbolt: http://goo.gl/RxIFEF –

+0

Ich glaube, das ist, was ich gesagt habe: "Sie müssten sich die Assembly-Ausgabe für Ihre speziellen, nicht "trivial code". ;) – Scott

+0

Ich habe versucht zu sagen, dass Sie ziemlich trivial Code in eine Funktion setzen können, und sehen Sie die ASM dafür. (Ich denke, das wurde zu einem Streit über die Definition von "trivial", was ich nicht beabsichtigte ...). In Ihrem Beispiel wurde ein Funktionsaufruf verwendet, um 'i' und' j' zu generieren. Mein Beispiel wäre, 'i' und' j' Funktion * Parameter * zu machen, also würden sie nur in Registern am Anfang des Codes für die Funktion sitzen. –