Die ursprüngliche Frage
Warum ist eine Schleife so viel langsamer als zwei Schleifen?
Fazit:
Fall 1 ist ein klassisches Interpolationsproblem, das zufällig ineffizient ist. Ich denke auch, dass dies einer der Hauptgründe war, warum viele Maschinenarchitekturen und Entwickler Multi-Core-Systeme mit der Fähigkeit zum Erstellen und Entwerfen von Multithread-Anwendungen sowie zur parallelen Programmierung erstellt und entworfen haben.
Betrachten Sie es von einem solchen Ansatz aus, ohne zu berücksichtigen, wie Hardware, Betriebssystem und Compiler zusammenarbeiten, um Heap-Zuweisungen vorzunehmen, die das Arbeiten mit RAM, Cache, Auslagerungsdateien usw. umfassen. Die Mathematik, die diesen Algorithmen zugrunde liegt, zeigt uns, welche dieser beiden die bessere Lösung ist.
Wir können eine Analogie eines Boss
Wesens verwenden Summation
, das ein Wesen darstellt For Loop
, das zwischen Arbeitern A
und Menschen reisen muss B
.
Wir können leicht erkennen, dass Fall 2 mindestens halb so schnell ist, wenn nicht etwas mehr als Fall 1, da sich die für die Reise erforderliche Entfernung und die zwischen den Arbeitern benötigte Zeit unterscheiden. Diese Mathematik stimmt fast virtuell und perfekt sowohl mit der BenchMark Times als auch mit der Anzahl der Unterschiede in der Montageanleitung überein.
Ich werde jetzt unten erklären, wie das alles funktioniert.
Bewertung des Problems
Der OP-Code:
const int n=100000;
for(int j=0;j<n;j++){
a1[j] += b1[j];
c1[j] += d1[j];
}
Und
for(int j=0;j<n;j++){
a1[j] += b1[j];
}
for(int j=0;j<n;j++){
c1[j] += d1[j];
}
Die Überlegung
In Anbetracht der ursprünglichen Frage des OP zu den beiden Varianten der for-Schleifen und seiner geänderten Frage zum Verhalten von Caches zusammen mit vielen anderen hervorragenden Antworten und nützlichen Kommentaren; Ich möchte versuchen, hier etwas anderes zu tun, indem ich diese Situation und dieses Problem anders betrachte.
Die Vorgehensweise
In Anbetracht der beiden Schleifen und der gesamten Diskussion über Cache- und Seitenablage möchte ich einen anderen Ansatz wählen, um dies aus einer anderen Perspektive zu betrachten. Bei einem Ansatz, bei dem weder der Cache und die Auslagerungsdateien noch die Ausführungen zum Zuweisen von Speicher erforderlich sind, betrifft dieser Ansatz überhaupt nicht die eigentliche Hardware oder Software.
Die Perspektive
Nachdem man sich den Code eine Weile angesehen hatte, wurde ziemlich deutlich, was das Problem ist und was es erzeugt. Lassen Sie uns dies in ein algorithmisches Problem zerlegen und es aus der Perspektive der Verwendung mathematischer Notationen betrachten und dann eine Analogie auf die mathematischen Probleme sowie auf die Algorithmen anwenden.
Was wir wissen
Wir wissen, dass diese Schleife 100.000 Mal ausgeführt wird. Wir wissen auch , dass a1
, b1
, c1
& d1
sind Zeiger auf einer 64-Bit - Architektur. In C ++ auf einem 32-Bit-Computer sind alle Zeiger 4 Byte und auf einem 64-Bit-Computer 8 Byte groß, da Zeiger eine feste Länge haben.
Wir wissen, dass wir in beiden Fällen 32 Bytes zuweisen müssen. Der einzige Unterschied besteht darin, dass wir jeder Iteration 32 Bytes oder 2 Sätze von 2-8 Bytes zuweisen, wobei wir im zweiten Fall 16 Bytes für jede Iteration für beide unabhängigen Schleifen zuweisen.
Beide Schleifen entsprechen immer noch 32 Bytes in der Gesamtzuweisung. Mit diesen Informationen wollen wir nun die allgemeine Mathematik, die Algorithmen und die Analogie dieser Konzepte zeigen.
Wir wissen, wie oft dieselbe Gruppe oder Gruppe von Operationen in beiden Fällen ausgeführt werden muss. Wir kennen die Speichermenge, die in beiden Fällen zugewiesen werden muss. Wir können davon ausgehen, dass die Gesamtarbeitsbelastung der Zuweisungen zwischen beiden Fällen ungefähr gleich sein wird.
Was wir nicht wissen
Wir wissen nicht, wie lange es für jeden Fall dauern wird, es sei denn, wir setzen einen Zähler und führen einen Benchmark-Test durch. Die Benchmarks wurden jedoch bereits aus der ursprünglichen Frage sowie aus einigen Antworten und Kommentaren aufgenommen. und wir können einen signifikanten Unterschied zwischen den beiden sehen und dies ist die ganze Begründung für diesen Vorschlag zu diesem Problem.
Lassen Sie uns untersuchen
Es ist bereits offensichtlich, dass viele dies bereits getan haben, indem sie sich die Heap-Zuordnungen, Benchmark-Tests, RAM, Cache und Auslagerungsdateien angesehen haben. Das Betrachten spezifischer Datenpunkte und spezifischer Iterationsindizes wurde ebenfalls aufgenommen, und die verschiedenen Gespräche über dieses spezifische Problem haben viele Leute dazu gebracht, andere verwandte Dinge darüber in Frage zu stellen. Wie fangen wir an, dieses Problem zu betrachten, indem wir mathematische Algorithmen verwenden und eine Analogie darauf anwenden? Wir beginnen mit ein paar Aussagen! Dann bauen wir von dort aus unseren Algorithmus aus.
Unsere Behauptungen:
- Wir lassen unsere Schleife und ihre Iterationen eine Summation sein, die bei 1 beginnt und bei 100000 endet, anstatt wie in den Schleifen mit 0 zu beginnen, da wir uns nicht um das 0-Indizierungsschema der Speicheradressierung kümmern müssen, da wir nur daran interessiert sind der Algorithmus selbst.
- In beiden Fällen müssen 4 Funktionen bearbeitet und 2 Funktionsaufrufe ausgeführt werden, wobei für jeden Funktionsaufruf 2 Operationen ausgeführt werden. Wir werden diese nach oben als Funktionen und Aufrufe von Funktionen wie die folgenden Satz:
F1()
, F2()
, f(a)
, f(b)
, f(c)
und f(d)
.
Die Algorithmen:
1. Fall: - Nur eine Summation, aber zwei unabhängige Funktionsaufrufe.
Sum n=1 : [1,100000] = F1(), F2();
F1() = { f(a) = f(a) + f(b); }
F2() = { f(c) = f(c) + f(d); }
2. Fall: - Zwei Summierungen, aber jede hat ihren eigenen Funktionsaufruf.
Sum1 n=1 : [1,100000] = F1();
F1() = { f(a) = f(a) + f(b); }
Sum2 n=1 : [1,100000] = F1();
F1() = { f(c) = f(c) + f(d); }
Wenn Sie bemerkt haben F2()
, existiert nur in Sum
von Case1
wo F1()
ist in Sum
von Case1
und in beiden Sum1
und Sum2
von enthalten Case2
. Dies wird später deutlich, wenn wir zu dem Schluss kommen, dass innerhalb des zweiten Algorithmus eine Optimierung stattfindet.
Die Iterationen durch die ersten Sum
Fallaufrufe f(a)
, die sich selbst hinzufügen, rufen f(b)
dann auff(c)
, die dasselbe tun f(d)
, sich jedoch für jede 100000
Iteration selbst hinzufügen . Im zweiten Fall haben wir Sum1
und Sum2
dass beide dasselbe tun , als ob sie dieselbe Funktion wären, die zweimal hintereinander aufgerufen wird.
In diesem Fall können wir behandeln Sum1
und Sum2
als einfach alt Sum
woSum
in diesem Fall so aussieht: Sum n=1 : [1,100000] { f(a) = f(a) + f(b); }
und jetzt sieht dies wie eine Optimierung aus, bei der wir es einfach als dieselbe Funktion betrachten können.
Zusammenfassung mit Analogie
Mit dem, was wir im zweiten Fall gesehen haben, scheint es fast so, als ob es eine Optimierung gibt, da beide for-Schleifen dieselbe exakte Signatur haben, aber dies ist nicht das eigentliche Problem. Das Problem ist nicht die Arbeit, die von erledigt wird f(a)
,f(b)
, f(c)
, und f(d)
. In beiden Fällen und beim Vergleich zwischen beiden ist es der Unterschied in der Entfernung, die die Summation jeweils zurücklegen muss, der den Unterschied in der Ausführungszeit ergibt.
Denken Sie an den For Loops
als das Wesen Summations
, das die Iterationen tut als ein Wesen , Boss
die Aufträge zu zwei Personen geben A
und B
und dass ihre Arbeitsplätze sind Fleisch C
und D
jeweils und abholen einige Pakete von ihnen und gibt es zurück. In dieser Analogie repräsentieren die for-Schleifen oder Summationsiterationen und Bedingungsprüfungen selbst nicht die Boss
. Was das tatsächlich darstellt, Boss
ist nicht direkt aus den tatsächlichen mathematischen Algorithmen, sondern aus dem tatsächlichen Konzept Scope
und Code Block
innerhalb einer Routine oder Unterroutine, Methode, Funktion, Übersetzungseinheit usw. Der erste Algorithmus hat 1 Bereich, wobei der zweite Algorithmus 2 aufeinanderfolgende Bereiche hat.
Im ersten Fall auf jedem Anrufzettel Boss
geht der zu A
und gibt den Befehl und A
geht los, um das B's
Paket abzurufen , dann Boss
geht der zu C
und gibt den Befehl, dasselbe zu tun und das Paket D
bei jeder Iteration zu erhalten.
Im zweiten Fall arbeitet das Boss
direkt mit A
dem Abrufen des B's
Pakets, bis alle Pakete empfangen wurden. Dann Boss
funktioniert das mit C
, um das Gleiche zu tun, um alle D's
Pakete zu erhalten.
Da wir mit einem 8-Byte-Zeiger arbeiten und uns mit der Heap-Zuweisung befassen, betrachten wir das folgende Problem. Nehmen wir an, das Boss
ist 100 Fuß von A
und das A
ist 500 Fuß von C
. Wir brauchen uns wegen der Reihenfolge der Hinrichtungen keine Gedanken darüber zu machen, wie weit das Boss
anfänglich entfernt ist C
. In beiden Fällen Boss
fährt der zunächst von A
zuerst nach B
. Diese Analogie soll nicht heißen, dass diese Entfernung genau ist; Es ist nur ein nützliches Testfallszenario, um die Funktionsweise der Algorithmen zu zeigen.
In vielen Fällen variieren diese Abstände zwischen Adresspositionen bei der Heap-Zuweisung und bei der Arbeit mit den Cache- und Auslagerungsdateien möglicherweise nicht so stark oder können je nach Art der Datentypen und Array-Größen erheblich variieren.
Die Testfälle:
Erster Fall: Bei der ersten IterationBoss
muss der zunächst 100 Fuß gehen, um den Bestellschein zu geben,A
undA
geht los und macht sein Ding, aber dannBoss
muss er 500 Fuß zurücklegenC
, um ihm seinen Bestellschein zu geben. Dann bei der nächsten Iteration und jeder zweiten Iteration nach demBoss
muss 500 Fuß zwischen den beiden hin und her gehen.
Zweiter Fall: DerBoss
muss bei der ersten Iteration 100 Fuß zurücklegenA
, aber danach ist er bereits da und wartet nur daraufA
, dass er zurückkommt, bis alle Belege gefüllt sind. DannBoss
muss der 500 Fuß bei der ersten Iteration zurücklegen,C
weil erC
500 Fuß entfernt istA
. Da diesBoss( Summation, For Loop )
direkt nach der Arbeit aufgerufen wird,A
wartet er dort einfach so, wie er es getan hat,A
bis alleC's
Bestellscheine fertig sind.
Der Unterschied in den zurückgelegten Entfernungen
const n = 100000
distTraveledOfFirst = (100 + 500) + ((n-1)*(500 + 500);
// Simplify
distTraveledOfFirst = 600 + (99999*100);
distTraveledOfFirst = 600 + 9999900;
distTraveledOfFirst = 10000500;
// Distance Traveled On First Algorithm = 10,000,500ft
distTraveledOfSecond = 100 + 500 = 600;
// Distance Traveled On Second Algorithm = 600ft;
Der Vergleich beliebiger Werte
Wir können leicht erkennen, dass 600 weit weniger als 10 Millionen sind. Dies ist nicht genau, da wir den tatsächlichen Unterschied in der Entfernung zwischen der Adresse des RAM oder dem Cache oder der Auslagerungsdatei nicht kennen. Jeder Aufruf bei jeder Iteration wird auf viele andere unsichtbare Variablen zurückzuführen sein. Dies ist nur eine Einschätzung der Situation, die Sie im schlimmsten Fall kennen und betrachten müssen.
Aus diesen Zahlen scheint es fast so, als ob Algorithmus Eins 99%
langsamer sein sollte als Algorithmus Zwei; Dies ist jedoch nur der Boss's
Teil oder die Verantwortung der Algorithmen und berücksichtigt nicht den tatsächlichen Arbeiter A
, B
, C
, und D
und , was sie haben auf jedem und jede Iteration der Schleife zu tun. Die Arbeit des Chefs macht also nur etwa 15 - 40% der gesamten geleisteten Arbeit aus. Der Großteil der Arbeit, die von den Arbeitern erledigt wird, hat einen etwas größeren Einfluss darauf, das Verhältnis der Geschwindigkeitsratenunterschiede zu etwa 50-70% zu halten
Die Beobachtung: - Die Unterschiede zwischen den beiden Algorithmen
In dieser Situation ist es die Struktur des Arbeitsprozesses. Es zeigt sich, dass Fall 2 sowohl bei der teilweisen Optimierung einer ähnlichen Funktionsdeklaration als auch bei der Definition effizienter ist, wenn sich nur die Variablen nach Name und zurückgelegter Entfernung unterscheiden.
Wir sehen auch, dass die in Fall 1 zurückgelegte Gesamtstrecke viel weiter ist als in Fall 2, und wir können diese zurückgelegte Strecke als unseren Zeitfaktor zwischen den beiden Algorithmen betrachten. Fall 1 hat erheblich mehr Arbeit zu erledigen als Fall 2 .
Dies ist aus dem Nachweis der ASM
Anweisungen ersichtlich, die in beiden Fällen gezeigt wurden. Zusammen mit dem, was bereits über diese Fälle erwähnt, ist dies zu berücksichtigen nicht die Tatsache , dass in Fall 1 der Chef wird für beide warten müssen A
und C
zurück zu bekommen , bevor er zurück zu gehen , kann A
wieder für jede Iteration. Es berücksichtigt auch nicht die Tatsache, dass, wenn A
oder wenn B
es extrem lange dauert, sowohl der Boss
als auch die anderen Arbeiter untätig sind und darauf warten, ausgeführt zu werden.
In Fall 2 ist der einzige, der untätig ist, der, Boss
bis der Arbeiter zurückkommt. Auch dies hat Auswirkungen auf den Algorithmus.
Die geänderten Fragen der OP
BEARBEITEN: Die Frage stellte sich als nicht relevant heraus, da das Verhalten stark von der Größe der Arrays (n) und des CPU-Cache abhängt. Wenn also weiteres Interesse besteht, formuliere ich die Frage neu:
Können Sie einen soliden Einblick in die Details geben, die zu den unterschiedlichen Cache-Verhaltensweisen führen, wie in den fünf Regionen in der folgenden Grafik dargestellt?
Es könnte auch interessant sein, auf die Unterschiede zwischen CPU / Cache-Architekturen hinzuweisen, indem ein ähnliches Diagramm für diese CPUs bereitgestellt wird.
Zu diesen Fragen
Wie ich ohne Zweifel gezeigt habe, gibt es ein zugrunde liegendes Problem, noch bevor die Hardware und Software beteiligt werden.
Nun zur Verwaltung des Speichers und zum Zwischenspeichern zusammen mit Auslagerungsdateien usw., die alle in einem integrierten Satz von Systemen zusammenarbeiten, zwischen den folgenden:
The Architecture
{Hardware, Firmware, einige eingebettete Treiber, Kernel und ASM-Befehlssätze}.
The OS
{Datei- und Speicherverwaltungssysteme, Treiber und die Registrierung}.
The Compiler
{Übersetzungseinheiten und Optimierungen des Quellcodes}.
- Und sogar das sich
Source Code
selbst mit seinen (n) markanten Algorithmen.
Wir können bereits sehen , dass es ein Engpass ist , die innerhalb des ersten Algorithmus geschehen , bevor wir es auch mit jeder beliebigen an jede Maschine gelten Architecture
, OS
und im Programmable Language
Vergleich zum zweiten Algorithmus. Es gab bereits ein Problem, bevor die Eigenschaften eines modernen Computers berücksichtigt wurden.
Die Endergebnisse
Jedoch; Es ist nicht zu sagen, dass diese neuen Fragen nicht von Bedeutung sind, weil sie selbst sind und schließlich eine Rolle spielen. Sie wirken sich auf die Verfahren und die Gesamtleistung aus. Dies wird anhand der verschiedenen Grafiken und Bewertungen von vielen deutlich, die ihre Antworten und / oder Kommentare abgegeben haben.
Wenn Sie die Aufmerksamkeit auf die Analogie des eingezahlten Boss
und die beiden Arbeiter A
und B
die mussten Pakete aus gehen und Abrufen C
& D
jeweils und unter Berücksichtigung der mathematischen Bezeichnungen der beiden Algorithmen in Frage; Sie können sehen, ohne die Beteiligung der Computerhardware und -software Case 2
ist etwa 60%
schneller als Case 1
.
Wenn Sie sich die Grafiken und Diagramme ansehen, nachdem diese Algorithmen auf einen Quellcode angewendet, kompiliert, optimiert und über das Betriebssystem ausgeführt wurden, um ihre Operationen auf einer bestimmten Hardware auszuführen, können Sie sogar eine geringfügig stärkere Verschlechterung zwischen den Unterschieden feststellen in diesen Algorithmen.
Wenn das Data
Set ziemlich klein ist, scheint es zunächst nicht so schlimm zu sein. Da Case 1
es jedoch ungefähr 60 - 70%
langsamer ist, als Case 2
wir das Wachstum dieser Funktion im Hinblick auf die Unterschiede in der Zeitausführung betrachten können:
DeltaTimeDifference approximately = Loop1(time) - Loop2(time)
//where
Loop1(time) = Loop2(time) + (Loop2(time)*[0.6,0.7]) // approximately
// So when we substitute this back into the difference equation we end up with
DeltaTimeDifference approximately = (Loop2(time) + (Loop2(time)*[0.6,0.7])) - Loop2(time)
// And finally we can simplify this to
DeltaTimeDifference approximately = [0.6,0.7]*Loop2(time)
Diese Annäherung ist die durchschnittliche Differenz zwischen diesen beiden Schleifen sowohl algorithmisch als auch Maschinenoperationen, die Softwareoptimierungen und Maschinenanweisungen umfassen.
Wenn der Datensatz linear wächst, wächst auch der Zeitunterschied zwischen den beiden. Algorithmus 1 hat mehr Fetches als Algorithmus 2 , die evident ist , wenn der Boss
zurück zu reisen hat und her dem maximalen Abstand zwischen A
& C
für jede Iteration nach der ersten Iteration , während Algorithmus 2 die Boss
muß Fahren auf A
einmal und dann , nachdem sich mit getan A
er muss Reise ein maximaler Abstand nur einmal , als ging von A
zu C
.
Der Versuch, sich darauf zu Boss
konzentrieren, zwei ähnliche Dinge gleichzeitig zu tun und sie hin und her zu jonglieren, anstatt sich auf ähnliche aufeinanderfolgende Aufgaben zu konzentrieren, wird ihn am Ende des Tages ziemlich wütend machen, da er doppelt so viel reisen und arbeiten musste. Verlieren Sie daher nicht den Umfang der Situation, indem Sie Ihren Chef in einen interpolierten Engpass geraten lassen, da der Ehepartner und die Kinder des Chefs dies nicht schätzen würden.
Änderung: Software Engineering Design Principles
- Der Unterschied zwischen Local Stack
und Heap Allocated
Berechnungen innerhalb von iterativen for-Schleifen und der Unterschied zwischen ihrer Verwendung, ihrer Effizienz und Effektivität -
Der oben vorgeschlagene mathematische Algorithmus gilt hauptsächlich für Schleifen, die Operationen an Daten ausführen, die auf dem Heap zugeordnet sind.
- Aufeinanderfolgende Stapeloperationen:
- Wenn die Schleifen Operationen an Daten lokal innerhalb eines einzelnen Codeblocks oder Bereichs ausführen, der sich innerhalb des Stapelrahmens befindet, wird dies immer noch angewendet, aber die Speicherorte sind viel näher, wo sie typischerweise sequentiell sind, und der Unterschied in der zurückgelegten Entfernung oder der Ausführungszeit ist fast vernachlässigbar. Da im Heap keine Zuweisungen vorgenommen werden, wird der Speicher nicht verstreut und der Speicher wird nicht über den RAM abgerufen. Der Speicher ist typischerweise sequentiell und relativ zum Stapelrahmen und Stapelzeiger.
- Wenn aufeinanderfolgende Operationen auf dem Stapel ausgeführt werden, speichert ein moderner Prozessor sich wiederholende Werte und Adressen zwischen, wobei diese Werte in lokalen Cache-Registern gehalten werden. Die Zeit der Operationen oder Anweisungen liegt hier in der Größenordnung von Nanosekunden.
- Aufeinanderfolgende Heap-zugewiesene Operationen:
- Wenn Sie anfangen, Heap-Zuweisungen anzuwenden, und der Prozessor die Speicheradressen bei aufeinanderfolgenden Aufrufen abrufen muss, kann die Zeit der Operationen oder der Ausführung in Abhängigkeit von der Architektur der CPU, des Bus-Controllers und der Ram-Module in der Größenordnung von Mikro zu liegen Millisekunden. Im Vergleich zu zwischengespeicherten Stapeloperationen sind diese recht langsam.
- Die CPU muss die Speicheradresse von Ram abrufen, und normalerweise ist alles über den Systembus hinweg langsam im Vergleich zu den internen Datenpfaden oder Datenbussen innerhalb der CPU selbst.
Wenn Sie also mit Daten arbeiten, die sich auf dem Heap befinden müssen, und diese in Schleifen durchlaufen, ist es effizienter, jeden Datensatz und die entsprechenden Algorithmen in einer eigenen Schleife zu halten. Sie erhalten bessere Optimierungen im Vergleich zum Versuch, aufeinanderfolgende Schleifen herauszufiltern, indem Sie mehrere Operationen verschiedener Datensätze, die sich auf dem Heap befinden, in einer einzigen Schleife zusammenfassen.
Es ist in Ordnung, dies mit Daten zu tun, die sich auf dem Stapel befinden, da diese häufig zwischengespeichert werden, jedoch nicht mit Daten, deren Speicheradresse bei jeder Iteration abgefragt werden muss.
Hier kommen Software Engineering und Software Architecture Design ins Spiel. Es ist die Fähigkeit zu wissen, wie Sie Ihre Daten organisieren, wann Sie Ihre Daten zwischenspeichern müssen, wann Sie Ihre Daten auf dem Heap zuordnen müssen, wie Sie Ihre Algorithmen entwerfen und implementieren und wann und wo Sie sie aufrufen müssen.
Möglicherweise haben Sie denselben Algorithmus, der sich auf denselben Datensatz bezieht, aber Sie möchten möglicherweise ein Implementierungsdesign für die Stapelvariante und ein anderes für die Heap-zugewiesene Variante, nur aufgrund des oben genannten Problems, das sich aus der O(n)
Komplexität des Algorithmus bei der Arbeit ergibt mit dem Haufen.
Nach dem, was ich im Laufe der Jahre bemerkt habe, berücksichtigen viele Menschen diese Tatsache nicht. Sie tendieren dazu, einen Algorithmus zu entwerfen, der für einen bestimmten Datensatz funktioniert, und sie verwenden ihn unabhängig davon, ob der Datensatz lokal auf dem Stapel zwischengespeichert ist oder ob er auf dem Heap zugewiesen wurde.
Wenn Sie eine echte Optimierung wünschen, scheint dies zwar eine Codeduplizierung zu sein, aber um es zu verallgemeinern, wäre es effizienter, zwei Varianten desselben Algorithmus zu haben. Eine für Stapeloperationen und die andere für Heap-Operationen, die in iterativen Schleifen ausgeführt werden!
Hier ist ein Pseudobeispiel: Zwei einfache Strukturen, ein Algorithmus.
struct A {
int data;
A() : data{0}{}
A(int a) : data{a}{}
};
struct B {
int data;
B() : data{0}{}
A(int b) : data{b}{}
}
template<typename T>
void Foo( T& t ) {
// do something with t
}
// some looping operation: first stack then heap.
// stack data:
A dataSetA[10] = {};
B dataSetB[10] = {};
// For stack operations this is okay and efficient
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
Foo(dataSetB[i]);
}
// If the above two were on the heap then performing
// the same algorithm to both within the same loop
// will create that bottleneck
A* dataSetA = new [] A();
B* dataSetB = new [] B();
for ( int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]); // dataSetA is on the heap here
Foo(dataSetB[i]); // dataSetB is on the heap here
} // this will be inefficient.
// To improve the efficiency above, put them into separate loops...
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
}
for (int i = 0; i < 10; i++ ) {
Foo(dataSetB[i]);
}
// This will be much more efficient than above.
// The code isn't perfect syntax, it's only psuedo code
// to illustrate a point.
Dies ist, worauf ich mich bezog, indem ich separate Implementierungen für Stapelvarianten gegenüber Heap-Varianten hatte. Die Algorithmen selbst spielen keine große Rolle, es sind die Schleifenstrukturen, die Sie dabei verwenden werden.