Ich frage mich nur, ob es zu einem Geschwindigkeits- oder Effizienzverlust kommen würde, wenn Sie so etwas tun würden:
int i = 0;
while(i < 100)
{
int var = 4;
i++;
}
das erklärt int var
hundertmal. Es scheint mir, als würde es das geben, aber ich bin mir nicht sicher. Wäre es praktischer / schneller, dies stattdessen zu tun:
int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}
oder sind sie gleich, schnell und effizient?
Antworten:
Der Stapelspeicher für lokale Variablen wird normalerweise im Funktionsumfang zugewiesen. Innerhalb der Schleife findet also keine Stapelzeigeranpassung statt, sondern nur 4 zugewiesen
var
. Daher haben diese beiden Snippets den gleichen Overhead.quelle
var
Variable wird initialisiert, aber nie verwendet, sodass ein vernünftiger Optimierer sie einfach vollständig entfernen kann (mit Ausnahme des zweiten Snippets, wenn die Variable irgendwo nach der Schleife verwendet wurde).Bei primitiven Typen und POD-Typen spielt dies keine Rolle. Der Compiler weist den Stapelspeicherplatz für die Variable zu Beginn der Funktion zu und gibt die Zuordnung frei, wenn die Funktion in beiden Fällen zurückkehrt.
Bei Nicht-POD-Klassentypen mit nicht trivialen Konstruktoren wird dies einen Unterschied machen. In diesem Fall ruft das Platzieren der Variablen außerhalb der Schleife den Konstruktor und den Destruktor nur einmal und den Zuweisungsoperator bei jeder Iteration auf, während sie innerhalb der Iteration platziert wird loop ruft den Konstruktor und den Destruktor für jede Iteration der Schleife auf. Abhängig davon, was der Konstruktor, der Destruktor und der Zuweisungsoperator der Klasse tun, kann dies wünschenswert sein oder auch nicht.
quelle
Sie sind beide gleich, und wie Sie dies herausfinden können, indem Sie sich ansehen, was der Compiler tut (auch ohne auf hoch eingestellte Optimierung):
Schauen Sie sich an, was der Compiler (gcc 4.0) mit Ihren einfachen Beispielen macht:
1.c:
main(){ int var; while(int i < 100) { var = 4; } }
gcc -S 1.c
1.s:
_main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $0, -16(%ebp) jmp L2 L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3 leave ret
2.c
main() { while(int i < 100) { int var = 4; } }
gcc -S 2.c
2.s:
_main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $0, -16(%ebp) jmp L2 L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3 leave ret
Daraus können Sie zwei Dinge erkennen: Erstens ist der Code in beiden Fällen gleich.
Zweitens wird der Speicher für var außerhalb der Schleife zugewiesen:
subl $24, %esp
Und schließlich ist das einzige, was in der Schleife steht, die Zuordnung und Bedingungsprüfung:
L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3
Das ist ungefähr so effizient wie möglich, ohne die Schleife vollständig zu entfernen.
quelle
Heutzutage ist es besser, es innerhalb der Schleife zu deklarieren, es sei denn, es ist eine Konstante, da der Compiler den Code besser optimieren kann (Reduzierung des Variablenbereichs).
EDIT: Diese Antwort ist derzeit größtenteils veraltet. Mit dem Aufkommen postklassischer Compiler werden die Fälle, in denen der Compiler es nicht herausfinden kann, seltener. Ich kann sie immer noch konstruieren, aber die meisten Leute würden die Konstruktion als schlechten Code klassifizieren.
quelle
Die meisten modernen Compiler optimieren dies für Sie. Davon abgesehen würde ich Ihr erstes Beispiel verwenden, da ich es besser lesbar finde.
quelle
Bei einem integrierten Typ gibt es wahrscheinlich keinen Unterschied zwischen den beiden Stilen (wahrscheinlich bis zum generierten Code).
Wenn die Variable jedoch eine Klasse mit einem nicht trivialen Konstruktor / Destruktor ist, kann es durchaus zu einem großen Unterschied bei den Laufzeitkosten kommen. Ich würde die Variable im Allgemeinen auf den Bereich innerhalb der Schleife beschränken (um den Bereich so klein wie möglich zu halten), aber wenn sich herausstellt, dass dies einen perfekten Einfluss hat, würde ich versuchen, die Klassenvariable außerhalb des Bereichs der Schleife zu verschieben. Dies erfordert jedoch einige zusätzliche Analysen, da sich die Semantik des Odenpfads ändern kann. Dies kann daher nur erfolgen, wenn die Sematik dies zulässt.
Eine RAII-Klasse benötigt möglicherweise dieses Verhalten. Beispielsweise muss möglicherweise bei jeder Schleifeniteration eine Klasse erstellt und zerstört werden, die die Lebensdauer des Dateizugriffs verwaltet, um den Dateizugriff ordnungsgemäß zu verwalten.
Angenommen, Sie haben eine
LockMgr
Klasse, die beim Erstellen einen kritischen Abschnitt erfasst und bei Zerstörung freigibt:while (i< 100) { LockMgr lock( myCriticalSection); // acquires a critical section at start of // each loop iteration // do stuff... } // critical section is released at end of each loop iteration
ist ganz anders als:
LockMgr lock( myCriticalSection); while (i< 100) { // do stuff... }
quelle
Beide Schleifen haben den gleichen Wirkungsgrad. Sie werden beide unendlich viel Zeit in Anspruch nehmen :) Es kann eine gute Idee sein, i innerhalb der Schleifen zu erhöhen.
quelle
Ich habe einmal einige Leistungstests durchgeführt und zu meiner Überraschung festgestellt, dass Fall 1 tatsächlich schneller war! Ich nehme an, dies kann daran liegen, dass das Deklarieren der Variablen innerhalb der Schleife ihren Umfang verringert, sodass sie früher freigegeben wird. Das ist jedoch lange her, auf einem sehr alten Compiler. Ich bin mir sicher, dass moderne Compiler die Unterschiede besser optimieren können, aber es tut immer noch nicht weh, Ihren variablen Bereich so kurz wie möglich zu halten.
quelle
&i
).#include <stdio.h> int main() { for(int i = 0; i < 10; i++) { int test; if(i == 0) test = 100; printf("%d\n", test); } }
Der obige Code gibt immer 100 10-mal aus, was bedeutet, dass die lokale Variable innerhalb der Schleife nur einmal pro Funktionsaufruf zugewiesen wird.
quelle
Der einzige Weg, um sicher zu sein, besteht darin, sie zeitlich zu bestimmen. Aber der Unterschied, wenn es einen gibt, wird mikroskopisch sein, so dass Sie eine mächtige große Zeitschleife benötigen.
Genauer gesagt ist der erste Stil besser, da er die Variable var initialisiert, während der andere sie nicht initialisiert. Dies und die Richtlinie, dass Variablen so nahe wie möglich an ihrem Verwendungsort definiert werden sollten, bedeuten, dass normalerweise die erste Form bevorzugt werden sollte.
quelle
Mit nur zwei Variablen wird dem Compiler wahrscheinlich ein Register für beide zugewiesen. Diese Register sind sowieso vorhanden, daher dauert dies keine Zeit. In beiden Fällen gibt es 2 Registerschreib- und einen Registerlesebefehl.
quelle
Ich denke, dass den meisten Antworten ein wichtiger Punkt fehlt, der zu berücksichtigen ist: "Ist es klar" und offensichtlich ist bei allen Diskussionen die Tatsache; Nein ist es nicht. Ich würde vorschlagen, dass in den meisten Loop-Codes die Effizienz so gut wie kein Problem ist (es sei denn, Sie rechnen für einen Marslander). Die einzige Frage ist also, was vernünftiger und lesbarer und wartbarer aussieht - in diesem Fall würde ich die Deklaration empfehlen Die Variable vorne und außerhalb der Schleife - das macht es einfach klarer. Dann würden sich Leute wie Sie und ich nicht einmal die Mühe machen, online zu prüfen, ob es gültig ist oder nicht.
quelle
Das ist nicht wahr, es gibt Overhead, aber seinen vernachlässigbaren Overhead.
Auch wenn sie wahrscheinlich an derselben Stelle auf dem Stapel landen werden, wird sie dennoch zugewiesen. Es wird Speicherplatz auf dem Stapel für dieses int zuweisen und es dann am Ende von} freigeben. Nicht im haufenfreien Sinne bewegt es sp (Stapelzeiger) um 1. Und in Ihrem Fall, wenn es nur eine lokale Variable hat, setzt es einfach fp (Rahmenzeiger) und sp gleich
Eine kurze Antwort wäre: Kümmere dich nicht darum, wie es fast genauso funktioniert.
Lesen Sie jedoch mehr darüber, wie der Stapel organisiert ist. Meine Grundschule hatte ziemlich gute Vorlesungen dazu. Wenn Sie mehr lesen möchten, lesen Sie hier http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html
quelle