Countdown in for-Schleifen
Ich glaube, (aus einige der Forschung zu Lesen), das zählen in for-Schleifen ist tatsächlich effizienter und schneller in der Laufzeit. Mein vollständiger software-code ist C++
Im Moment habe ich dieses:
for (i=0; i<domain; ++i) {
mein 'ich' ist unsigned resgister int,
auch 'Domäne' ist unsigned int
in der for-Schleife, die ich verwendet, für den Gang durch ein array, z.B.
array[i] = do stuff
die Umrechnung der count-down vermasselt die erwartete/richtige Ausgabe meiner routine.
Kann ich mir vorstellen, die Antwort wird ganz trivial, aber ich kann nicht meinen Kopf um ihn herum.
UPDATE: 'Dinge zu tun', hängt nicht von vorherigen oder späteren iteration. Die Berechnungen in der for-Schleife sind unabhängig, die für die iteration von i ist. (Ich hoffe das macht Sinn).
UPDATE: Um runtime-speedup mit meiner for-Schleife, muss ich runter zählen, und wenn ja, entfernen Sie die nicht signierten Teil, wenn delcaring meine int, oder welche andere Methode?
Bitte helfen.
- Wo ist die Frage?
- Es ist nicht klar, was die Frage ist. Sie sind eher eine hilfreiche Antwort bekommt, wenn Sie Bearbeiten Ihre Frage klar zu stellen was genau Sie zu Fragen.
- Vielleicht sollten Sie post von der for-Schleife, die Sie verwenden, die nach unten zählt auch. Und ohne Kenntnis der 'Sachen tun," wir sind gezwungen anzunehmen, dass die 'Dinge zu tun' für 'ich' nicht abhängig 'was' für 'i' -1', in welchem Fall zählen eindeutig nicht arbeiten kann.
- Wie funktioniert eine Aussage wie deine machen keinen Sinn immer-ohne Berücksichtigung, welchen compiler du redest? Es könnte auch noch abhängig von der Architektur, weil, wie die hardware implementiert die zugrunde liegenden Vorgänge. Mein Punkt ist, ich könnte das design einer Architektur oder compiler, der dies bricht... Auch was du selbst tun, das erfordert, dass so ein lächerlich-Optimierung?
- ja, ich bin nicht in der Lage zu finden keine klare Frage, die ich könnte Sinn machen. Es scheinen zwei "versteckte" Fragen unsigned wrap-around-und ist-looping-down-schneller, aber beide sind nicht ganz klar gefragt wie es scheint. Bitte geben Sie Ihren eigentlichen Frage
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich vermute, Ihre rückwärts for-Schleife sieht wie folgt aus:
In diesem Fall, weil
i
ist unsigned, es wird immer größer als oder gleich null ist. Wenn Sie Dekrement einer unsigned variable ist gleich null, es wird wrap-around zu einer sehr großen Zahl. Die Lösung ist, entweder zui
unterzeichnet, oder ändern Sie die Bedingung in der for-Schleife wie diese:Oder Zählung von
domain
zu1
anstatt vondomain - 1
zu0
:i >= 1
und nichti>0
?Gibt es nur eine richtige Methode der looping rückwärts über eine vorzeichenlose Zähler:
Gibt es einen trick hier, für den letzten loop iteration haben Sie i = 1 an den Anfang der Schleife, ich-- > 0 geht, denn 1 > 0, dann i = 0 in der Schleife. Für die nächste iteration i-- > 0 schlägt fehl, da i == 0, so spielt es keine Rolle, dass die postfix-Dekrement rollte über den Ladentisch.
Nicht sehr offensichtlich, ich weiß.
dec esi
, die sehe ich nicht oben in Michael s Kommentar.i
istn-1
.Dies ist keine Antwort auf Ihr problem, weil Sie nicht scheinen ein problem zu haben.
Diese Art der Optimierung ist völlig irrelevant und sollte gelassen werden, um den compiler (wenn machbar).
Haben Sie profiliert Ihr Programm, um zu überprüfen, dass deine for-Schleife ist ein Engpass? Wenn nicht, dann brauchen Sie nicht, Zeit zu verbringen, sich Gedanken über diese. Noch mehr so, dass "ich" als ein "register" int", wie Sie schreiben, macht keinen wirklichen Sinn vom performance-Standpunkt aus.
Sich auch ohne Kenntnis der problemdomäne, ich kann Ihnen garantieren, dass sowohl die rückwärts-looping-Verfahren und das "register" int counter haben vernachlässigbar Auswirkungen auf Ihre Programm-performance. Denken Sie daran, "Vorzeitige Optimierung ist die Wurzel allen übels".
Sagte, besser für die Optimierung wäre die Zeit auf das denken über die Allgemeine Programmstruktur, die Datenstrukturen und algorithmen verwendet, die Auslastung von Ressourcen, etc.
Überprüfen, um zu sehen, ob eine Zahl null ist, kann schneller und effizienter als ein Vergleich. Aber dies ist die Art von Mikro-Optimierung, die Sie wirklich sollten sich keine sorgen machen - ein paar Taktzyklen wird stark in den Schatten gestellt, die von fast jedem anderen perf Problem.
Auf x86:
Statt:
Wenn Sie haben eine anständige compiler optimieren "zählen auf", nur so effektiv wie "Countdown". Versuchen Sie einfach ein paar benchmarks und du wirst sehen.
Damit Sie "gelesen", dass couting unten ist effizienter? Ich finde das sehr schwer zu glauben, es sei denn, Sie zeigen mir einige profiler-Ergebnisse und den code. Ich es kaufen kann, unter gewissen Umständen, aber im Allgemeinen Fall nicht. Scheint mir wie ein klassischer Fall von vorzeitiger Optimierung.
Ihren Kommentar zu "register int i" ist auch sehr aufschlussreich. Heute, der compiler weiß immer besser als Sie, wie zu reservieren registriert. Kümmern Sie sich nicht mit mit der register-Schlüsselwort, es sei denn, Sie haben profiliert Ihr code.
Wenn Sie die Schleife durch die Daten-Strukturen jeglicher Art, cache-misses haben eine weit größere Auswirkung als die Richtung, die du gehst. Beschäftigen Sie sich mit dem größeren Bild, das Speicher-layout und Algorithmus-Struktur statt Platter micro-Optimierungen.
Es hat nichts zu tun mit dem zählen bis oder unten. Was kann man schneller zählen gegen null. Michael ' s Antwort zeigt, warum — x86 bietet Ihnen einen Vergleich mit null als eine implizite Nebeneffekt viele Anweisungen, so, nachdem Sie passen Sie Ihre Konter, die Sie gerade Zweig basierend auf dem Ergebnis zu tun, anstatt einen expliziten Vergleich. (Vielleicht sind andere Architekturen auch machen; ich weiß es nicht.)
Borland-Pascal-Compiler sind berüchtigt dafür, dass die Durchführung der Optimierung. Der compiler wandelt diesen code:
in eine interne Repräsentation eher an diese:
(Sage ich, berüchtigt nicht, weil die Optimierung wirkt sich auf das Ergebnis der Schleife, aber da der debugger zeigt die counter-variable falsch. Wenn der Programmierer prüft
i
, kann der debugger den Wert anzuzeigen, dertmp
statt, verursacht kein Ende der Verwirrung und Panik für die Programmierer, die denken, dass Ihre loops laufen rückwärts.)Die Idee ist, dass selbst mit dem extra -
Inc
oderDec
Unterricht, es ist immer noch ein Netto-Gewinn, in Bezug auf die Ausführung der Zeit, über das tun ein expliziter Vergleich. , Ob Sie tatsächlich bemerken, die Differenz steht zur Debatte.Aber beachten Sie, dass die Umwandlung ist etwas, was der compiler tun würde automatisch, basierend auf, ob Sie es als die transformation lohnt. Ist der compiler in der Regel besser auf das optimieren von code, als Sie sind, so don ' T verbringen zu viel Aufwand konkurrieren mit ihm.
Du sowieso gebeten, über C++, Pascal. C++ "for" - Schleifen sind nicht ganz so leicht anzuwenden, dass die Optimierung als Pascal "for" - Schleifen sind da die Grenzen von Pascal-Schleifen werden immer vollständig berechnet, bevor die Schleife ausgeführt wird, in der Erwägung, dass C++ - Schleifen manchmal hängt die Stopp-Bedingung und die Schleife Inhalt. C++ - Compiler tun müssen, eine gewisse Menge der statischen Analyse, um zu bestimmen, ob ein bestimmtes Schleife passen könnte, die die Anforderungen für die Art der transformation Pascal-Schleifen qualifizieren sich für bedingungslos. Wenn der C++ compiler ist die Analyse, dann könnte es nicht eine ähnliche transformation.
Gibt es nichts hindert Sie schreiben Ihre loops, die Weg auf Ihre eigenen:
Tun, dass könnte machen Sie Ihren code schneller laufen. Wie ich schon sagte, bevor, obwohl, werden Sie wahrscheinlich nicht bemerken. Die größeren Kosten, die Sie bezahlen, indem Sie manuell anordnen Schleifen wie, dass ist, dass Ihr code nicht mehr folgt etablierten Idiome. Deine Schleife ist eine stinknormale "for" - Schleife, aber es nicht mehr sieht wie — es hat zwei Variablen, Sie laufen in entgegengesetzte Richtungen, und einer von Ihnen ist nicht einmal in der Schleife Körper — also wer Lesen Ihren code (auch Sie, eine Woche, einen Monat oder ein Jahr ab jetzt, wenn Sie vergessen haben, die "Optimierung", die Sie hofften, zu erreichen) verbringen müssen zusätzlichen Aufwand erweist sich, dass die Schleife ist in der Tat eine ordentliche Schleife im Unglück.
(Haben Sie bemerkt, dass mein code oben verwendet unsigned Variablen ohne Gefahr der Umwicklung bei null? Mit zwei getrennten Variablen erlaubt.)
Drei Dinge zum mitnehmen aus all dem:
Können Sie versuchen, die folgende, der compiler optimieren wird, sehr effizient:
Nun können Sie es verwenden:
Können Sie Durchlaufen in jede Richtung:
Loop
ergibt folgenden code:
Schwer zu sagen, mit Angaben, aber... reverse-array, und der count down?
Jeremy Ruten zu Recht darauf hingewiesen, dass die Verwendung eines unsigned Schleifenzähler ist gefährlich. Es ist auch nicht notwendig, so weit ich erzählen kann.
Andere haben auch darauf hingewiesen, auf die Gefahren von vorzeitiger Optimierung. Sie haben absolut Recht.
Mit dieser sagte, hier ist ein Stil, den ich verwendet, wenn die Programmierung von embedded-Systemen vor vielen Jahren, als jedes byte und jeden Zyklus hast, zählen für etwas. Diese Formen wurden für mich nützlich, auf die insbesondere CPUs und Compiler, dass ich war mit, aber Ihre Laufleistung variieren.
Diese form nutzt die Vorteile des Zustand-flag, das gesetzt ist auf einige Prozessoren nach arithmetischen Operationen -- auf manchen Architekturen, die Dekrement-und Prüfung für den Zweig der Bedingung kombiniert werden können in einer einzigen Instruktion. Beachten Sie, dass mit predecrement (
--i
) ist hier der Schlüssel -- mit post-Dekrement (i--
) würde nicht gearbeitet haben.Alternativ
Diese zweite form nutzt die Vorteile der Zeiger (Adresse) Arithmetik. Ich habe selten sehen die form
(pointer - int)
diesen Tagen (aus gutem Grund), aber die Sprache, die gewährleistet, dass beim subtrahieren ein int aus einem Zeiger, der Zeiger wird um eins verringert, durch(int * sizeof (*pointer))
.Werde ich auch noch einmal betonen, dass, ob diese Formen sind ein Gewinn für Sie hängt von der CPU und compiler, dass Sie verwenden. Sie dienten mir gut auf Motorola 6809 und 68000-Architekturen.
In einigen später arm-cores, Dekrementieren und vergleichen dauert nur einer einzigen Instruktion. Dies macht Dekrementieren Schleifen effizienter als die Inkrementierung lieben.
Ich weiß nicht, warum es nicht eine Inkrement-compare-Anweisung auch.
Ich bin überrascht, dass dieser post gewählt wurde, -1, wenn es eine echte Frage.
Jeder hier konzentriert sich auf die performance aus. Gibt es eigentlich einen logischen Grund für die Iteration gegen null, die entstehen können, in sauberer code.
Iteration über das Letzte element der ersten ist praktisch, wenn Sie löschen, die ungültige Elemente durch vertauschen mit dem Ende des Arrays. Für die schlechte Elemente, die nicht benachbart zu dem Ende können wir tauschen in der end-position, verringern Sie die end-Grenze des Arrays, und halten Durchlaufen. Wenn Sie waren zu Durchlaufen und zum Ende hin dann tauschen mit dem Ende führen könnte swapping schlechte für schlecht. Durch Durchlaufen Ende 0 wir wissen, dass das element am Ende des Arrays schon bewiesen gilt für diese iteration.
Weiteren Erklärung...
Wenn:
Dann offensichtlich:
So bedeutet dies:
Also endlich:
Was zählt viel mehr als die Frage, ob Sie erhöhen oder verringern Sie Ihren counter ist, ob oder nicht Sie gehen, bis Speicher oder down memory. Die meisten caches sind optimiert für Speicher, nicht Arbeitsspeicher. Da der Speicher-Zugriffszeit ist der Engpass, dass die meisten Programme heute Gesicht, dies bedeutet, dass das ändern Sie Ihr Programm so, dass man mit dem Speicher kann zu einer Leistungssteigerung, auch wenn dies erfordert Vergleich der Zähler auf einen nicht-null-Wert. In einigen meiner Programme, ich sah eine signifikante Verbesserung in der Leistung, indem Sie meinen code zu gehen, bis Speicher statt es nach unten.
Skeptisch? Hier ist die Ausgabe, die ich bekam:
aus ausführen dieses Programms:
Beide
sum_abs_up
undsum_abs_down
tun die gleiche Sache und sind zeitgesteuert, Sie gleichen Weg, nur mit dem Unterschied, dasssum_abs_up
geht mit dem Speicher, währendsum_abs_down
geht down memory. Ich selbst passvec
durch Verweis so, dass beide Funktionen greifen auf die gleichen Speicherplätze. Dennochsum_abs_up
ist durchweg schneller alssum_abs_down
. Geben Sie einen Lauf selbst (ich kompiliert es mit g++ -O3).FYI
vec_original
ist es für Experimente, um es einfach für mich zu ändernsum_abs_up
undsum_abs_down
in einer Weise, die macht Sie zu verändernvec
zwar nicht, dass diese änderungen wirken sich auf zukünftige Zeiten.Es ist wichtig zu beachten, wie eng die Schleife, dass ich das timing ist. Wenn Sie einen loop, der Körper ist groß, dann ist es vermutlich egal, ob seine iterator geht nach oben oder unten Speicher, da die Zeit, die zum ausführen der schleifenrumpf wird wahrscheinlich komplett zu Dominieren. Es ist auch wichtig zu erwähnen, dass mit einigen seltenen Schleifen, werde sich das Gedächtnis ist manchmal schneller, als sich es. Aber auch mit solchen loops ist es nur selten der Fall, dass going up war immer langsamer als der Abstieg (im Gegensatz zu Schleifen, die gehen bis Speicher, welche sehr Häufig immer schneller als die entsprechende down-memory-loops; eine kleine Handvoll Zeiten waren Sie noch 40+% schneller).
Der Punkt ist, als eine Regel von Daumen, wenn Sie die Möglichkeit haben, wenn der schleifenrumpf ist klein, und wenn es gibt wenig Unterschied zwischen den loop zu gehen mit dem Speicher auf, anstatt nach unten, dann sollten Sie gehen Speicher.