Ich habe diese Idee aus dieser Frage auf stackoverflow.com
Das folgende Muster ist üblich:
final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
//...do something
}
Der Punkt, den ich versuche, ist, dass die bedingte Aussage etwas Kompliziertes ist und sich nicht ändert.
Ist es besser, es im Initialisierungsabschnitt der Schleife als solches zu deklarieren?
final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
//...do something
}
Ist das klarer?
Was ist, wenn der bedingte Ausdruck einfach ist wie
final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
//...do something
}
java
c++
coding-style
language-agnostic
Celeritas
quelle
quelle
x
in der Größe groß ist,Math.floor(Math.sqrt(x))+1
ist gleichMath.floor(Math.sqrt(x))
. :-){ x=whatever; for (...) {...} }
.Antworten:
Was ich tun würde, ist ungefähr so:
Ehrlich gesagt, der einzige gute Grund, die Initialisierung
j
(jetztlimit
) in den Loop-Header zu stopfen, besteht darin, den korrekten Gültigkeitsbereich beizubehalten. Alles was es braucht, um eine Nicht-Ausgabe zu machen, ist ein schöner, enger Umfang.Ich kann den Wunsch, schnell zu sein, würdigen, aber die Lesbarkeit nicht ohne guten Grund opfern.
Sicher, der Compiler kann optimieren, das Initialisieren mehrerer VARs ist zwar zulässig, aber Schleifen sind so schwer zu debuggen. Bitte sei freundlich zu den Menschen. Wenn sich herausstellt, dass dies uns wirklich verlangsamt, ist es schön, es zu verstehen und zu beheben.
quelle
for(int i = 0; i < n*n; i++){...}
keine zuweisenn*n
, oder?final
). Wen interessiert es, ob später in der Funktion auf eine Konstante zugegriffen werden kann, deren Compiler-Erzwingung Änderungen verhindert?Ein guter Compiler generiert in beiden Fällen den gleichen Code. Wenn Sie also Leistung anstreben, nehmen Sie eine Änderung nur dann vor, wenn sich diese in einer kritischen Schleife befindet und Sie tatsächlich ein Profil erstellt und festgestellt haben, dass sie einen Unterschied macht. Auch wenn der Compiler es nicht optimieren kann, wie in den Kommentaren zum Fall mit Funktionsaufrufen bereits erwähnt, ist der Leistungsunterschied in den allermeisten Situationen zu gering, um eine Überlegung durch einen Programmierer wert zu sein.
Jedoch...
Wir dürfen nicht vergessen, dass Code in erster Linie ein Kommunikationsmedium zwischen Menschen ist und beide Optionen nicht sehr gut mit anderen Menschen kommunizieren. Der erste erweckt den Eindruck, dass der Ausdruck bei jeder Iteration berechnet werden muss, und der zweite im Initialisierungsabschnitt impliziert, dass er irgendwo innerhalb der Schleife aktualisiert wird, wo er wirklich durchgehend konstant ist.
Eigentlich würde ich es vorziehen, wenn es über der Schleife herausgezogen und gemacht wird
final
, um das jedem, der den Code liest, sofort und in Hülle und Fülle klar zu machen. Das ist auch nicht ideal, weil es den Gültigkeitsbereich der Variablen vergrößert, aber Ihre einschließende Funktion sollte sowieso nicht viel mehr als diese Schleife enthalten.quelle
Wie @ Karl Bielefeldt in seiner Antwort sagte, ist dies normalerweise kein Problem.
Es war jedoch einmal ein häufiges Problem in C und C ++, und es entstand ein Trick, mit dem das Problem umgangen werden konnte,
0
ohne die Lesbarkeit des Codes zu beeinträchtigen .Jetzt ist die Bedingung in jeder Iteration,
>= 0
die jeder Compiler in 1 oder 2 Assembler-Anweisungen kompiliert. Jede CPU, die in den letzten Jahrzehnten hergestellt wurde, sollte grundlegende Überprüfungen wie diese haben. Bei einer kurzen Überprüfung auf meinem x64-Computer wird dies vorhersehbar zucmpl $0x0, -0x14(%rbp)
(Long-Int-Compare-Wert 0 vs. Register-RBP versetzt -14) undjl 0x100000f59
(springen Sie zu der Anweisung, die der Schleife folgt, wenn der vorherige Vergleich für "2nd-arg" zutrifft <1st-arg ”) .Beachten Sie, dass ich das
+ 1
von entfernt habeMath.floor(Math.sqrt(x)) + 1
; Damit die Mathematik funktioniert, sollte der Startwert seinint i = «iterationCount» - 1
. Bemerkenswert ist auch, dass Ihr Iterator signiert sein muss.unsigned int
funktioniert nicht und wird wahrscheinlich vom Compiler gewarnt.Nachdem ich ~ 20 Jahre lang in C-basierten Sprachen programmiert habe, schreibe ich jetzt nur noch Reverse-Index-Iterationsschleifen, es sei denn, es gibt einen bestimmten Grund, Index-Iterationen weiterzuleiten. Zusätzlich zu einfacheren Überprüfungen der Bedingungen führt die umgekehrte Iteration häufig auch zu Nebenschritten, was andernfalls störende Array-Mutationen beim Iterieren bedeuten würde.
quelle
unsigned
Zähler hier funktionieren, wenn Sie die Prüfung ändern (der einfachste Weg ist, auf beiden Seiten den gleichen Wert hinzuzufügen). Beispielsweise sollteDec
die Prüfung für jede Dekrementierung(i + Dec) >= Dec
immer dasselbe Ergebnis wie diesigned
Prüfungi >= 0
mit beidensigned
undunsigned
Zählern haben, solange die Sprache gut definierte Umlaufregeln fürunsigned
Variablen hat (insbesondere-n + n == 0
muss für beidesigned
und wahr seinunsigned
). Beachten Sie jedoch, dass dies möglicherweise weniger effizient ist als eine signierte>=0
Prüfung, wenn der Compiler keine Optimierung dafür hat.Dec
Konstante sowohl zum Anfangs- als auch zum Endwert funktioniert, macht ihn jedoch weniger intuitiv. Wenni
Sie ihn als Array-Index verwenden, müssen Sie auch einenunsigned int arrayI = i - Dec;
In-the-Loop-Body ausführen. Ich benutze nur die Vorwärtsiteration, wenn ich mit einem nicht signierten Iterator stecke. oft mit eineri <= count - 1
Bedingung, um die Logik parallel zu den Rückwärtsiterationsschleifen zu halten.Dec
die Start- und Endwerte ergänzen , sondern die ZustandsüberprüfungDec
auf beiden Seiten verschieben.for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/
Auf diese Weise können Siei
die Schleife normal verwenden und gleichzeitig sicherstellen, dass auf der linken Seite der Bedingung der niedrigstmögliche Wert0
angezeigt wird (um zu verhindern, dass der Umlauf beeinträchtigt wird). Es ist jedoch definitiv viel einfacher, die Vorwärtsiteration zu verwenden, wenn Sie mit vorzeichenlosen Zählern arbeiten.Interessant wird es, wenn Math.sqrt (x) durch Mymodule.SomeNonPureMethodWithSideEffects (x) ersetzt wird.
Grundsätzlich ist mein Modus operandi: Wenn erwartet wird, dass etwas immer den gleichen Wert ergibt, dann bewerte es nur einmal. Zum Beispiel List.Count, wenn sich die Liste während des Betriebs der Schleife nicht ändern soll, wird die Zählung außerhalb der Schleife in eine andere Variable übertragen.
Einige dieser "Zählungen" können überraschend teuer sein, insbesondere wenn Sie mit Datenbanken arbeiten. Selbst wenn Sie an einem Datensatz arbeiten, der sich während der Iteration der Liste nicht ändern soll.
quelle
for( auto it = begin(dataset); !at_end(it); ++it )
Meiner Meinung nach ist dies sehr sprachspezifisch. Wenn ich beispielsweise C ++ 11 verwende, würde ich vermuten, dass
constexpr
der Compiler , wenn die Bedingungsprüfung eine Funktion wäre, sehr wahrscheinlich die mehreren Ausführungen optimieren würde, da er weiß, dass sie jedes Mal den gleichen Wert ergeben.Wenn es sich bei dem Funktionsaufruf jedoch um eine Bibliotheksfunktion handelt, die nicht
constexpr
vom Compiler ausgeführt wird, wird sie mit ziemlicher Sicherheit bei jeder Iteration ausgeführt, da dies nicht abgeleitet werden kann (es sei denn, sie ist inline und kann daher als rein abgeleitet werden).Ich weiß weniger über Java, aber angesichts der Tatsache, dass JIT kompiliert ist, würde ich vermuten, dass der Compiler zur Laufzeit über genügend Informationen verfügt, um die Bedingung wahrscheinlich inline zu integrieren und zu optimieren. Dies würde jedoch von einem guten Compiler-Design abhängen, und der Compiler, der diese Schleife entschied, war eine Optimierungspriorität, die wir nur erraten können.
Persönlich finde ich es etwas eleganter, die Bedingung in die for-Schleife zu setzen, wenn Sie können, aber wenn sie komplex ist, schreibe ich sie in eine
constexpr
oderinline
-Funktion oder in Ihre entsprechenden Sprachen, um darauf hinzuweisen, dass die Funktion rein und optimierbar ist. Dies macht die Absicht offensichtlich und behält den idiomatischen Schleifenstil bei, ohne eine riesige unlesbare Linie zu erzeugen. Außerdem erhält die Bedingungsprüfung einen Namen, wenn es sich um eine eigene Funktion handelt, sodass die Leser sofort logisch sehen können, wozu die Prüfung dient, ohne sie zu lesen, wenn sie komplex ist.quelle