Diese Frage wurde bei SO ziemlich eiskalt aufgenommen, daher habe ich beschlossen, sie dort zu löschen und stattdessen hier zu versuchen. Wenn Sie der Meinung sind, dass es auch hier nicht passt, hinterlassen Sie bitte zumindest einen Kommentar zum Vorschlag, wie Sie ein Beispiel finden können, nach dem ich suche ...
Können Sie ein Beispiel geben , bei dem die Verwendung von C99-VLAs einen echten Vorteil gegenüber aktuellen C ++ RAII-Mechanismen mit Standardhaufen bietet?
Das Beispiel, nach dem ich suche, sollte:
- Erzielen Sie einen leicht messbaren (vielleicht 10%) Leistungsvorteil gegenüber der Verwendung von Heap.
- Keine gute Problemumgehung, die nicht das gesamte Array benötigen würde.
- Profitieren Sie tatsächlich von der Verwendung einer dynamischen Größe anstelle einer festen Maximalgröße.
- Es ist unwahrscheinlich, dass im normalen Verwendungsszenario ein Stapelüberlauf verursacht wird.
- Seien Sie stark genug, um einen Entwickler zu verführen, der die Leistung benötigt, um eine C99-Quelldatei in ein C ++ - Projekt aufzunehmen.
Hinzufügen einer Klarstellung zum Kontext: Ich meine VLA im Sinne von C99 und nicht in Standard-C ++ enthalten: int array[n]
Dabei n
handelt es sich um eine Variable. Und ich bin nach einem Anwendungsfall, bei dem die Alternativen anderer Standards (C90, C ++ 11) übertroffen werden:
int array[MAXSIZE]; // C stack array with compile time constant size
int *array = calloc(n, sizeof int); // C heap array with manual free
int *array = new int[n]; // C++ heap array with manual delete
std::unique_ptr<int[]> array(new int[n]); // C++ heap array with RAII
std::vector<int> array(n); // STL container with preallocated size
Einige Ideen:
- Funktionen, die varargs verwenden, wodurch die Anzahl der Elemente natürlich auf einen vernünftigen Wert begrenzt wird, jedoch keine nützliche Obergrenze auf API-Ebene vorliegt.
- Rekursive Funktionen, bei denen verschwendeter Stapel unerwünscht ist
- Viele kleine Zuweisungen und Releases, bei denen der Heap-Overhead schlecht wäre.
- Umgang mit mehrdimensionalen Arrays (wie Matrizen beliebiger Größe), bei denen die Leistung von entscheidender Bedeutung ist und von kleinen Funktionen erwartet wird, dass sie stark eingebunden werden.
- Aus dem Kommentar: Gleichzeitiger Algorithmus, bei dem die Heap-Zuweisung einen Synchronisierungsaufwand hat .
Wikipedia hat ein Beispiel, das meine Kriterien nicht erfüllt , da der praktische Unterschied zur Verwendung von Heap zumindest ohne Kontext irrelevant erscheint. Es ist auch nicht ideal, da ohne mehr Kontext die Anzahl der Elemente sehr wohl zu einem Stapelüberlauf führen kann.
Hinweis: Ich bin speziell auf der Suche nach einem Beispielcode oder einem Vorschlag für einen Algorithmus, der davon profitieren würde, damit ich das Beispiel selbst implementieren kann.
alloca()
würde ermalloc()
in einer Multithread-Umgebung aufgrund des Lock-Konflikts in letzterer wirklich überstrahlen . Dies ist jedoch eine echte Strecke, da kleine Arrays nur eine feste Größe verwenden sollten und große Arrays den Heap wahrscheinlich sowieso benötigen.alloca
, von dem ich denke, dass er im Grunde dasselbe ist). Aber diese Multithread-Sache ist gut, die Frage zu bearbeiten, um sie aufzunehmen!malloc
Verhalten von Linux dem C-Standard entspricht.Antworten:
Ich habe gerade ein kleines Programm gehackt, das eine Reihe von Zufallszahlen generiert, die jedes Mal mit demselben Startwert neu gestartet werden, um sicherzustellen, dass es "fair" und "vergleichbar" ist. Im Laufe der Zeit werden die Min- und Max-Werte dieser Werte ermittelt. Und wenn es den Satz von Zahlen generiert hat, zählt es, wie viele über dem Durchschnitt von
min
und liegenmax
.Für SEHR kleine Arrays zeigt es einen deutlichen Vorteil, wenn VLAs vorbei sind
std::vector<>
.Es ist kein wirkliches Problem, aber wir können uns leicht etwas vorstellen, bei dem wir die Werte aus einer kleinen Datei lesen würden, anstatt Zufallszahlen zu verwenden, und andere, aussagekräftigere Zähl- / Min / Max-Berechnungen mit derselben Art von Code durchführen würden .
Für SEHR kleine Werte der "Anzahl der Zufallszahlen" (x) in den relevanten Funktionen
vla
gewinnt die Lösung mit großem Abstand. Wenn die Größe größer wird, wird der "Gewinn" kleiner, und bei ausreichender Größe scheint die Vektorlösung effizienter zu sein - hat diese Variante nicht zu sehr untersucht, als wenn wir anfangen, Tausende von Elementen in einer VLA zu haben, ist dies nicht der Fall wirklich, was sie tun sollten ...Und ich bin sicher, jemand wird mir sagen, dass es eine Möglichkeit gibt, diesen ganzen Code mit einer Reihe von Vorlagen zu schreiben und ihn dazu zu bringen, ohne mehr als das RDTSC und die
cout
Bits zur Laufzeit auszuführen ... Aber ich denke nicht, dass das wirklich so ist Der Punkt.Wenn ich diese spezielle Variante ausführe, erhalte ich ungefähr 10% Unterschied zwischen
func1
(VLA) undfunc2
(std :: vector).Dies wird zusammengestellt mit:
g++ -O3 -Wall -Wextra -std=gnu++0x -o vla vla.cpp
Hier ist der Code:
quelle
std::vector
.func3
diev.push_back(rand())
anstelle von verwendetv[i] = rand();
und die Notwendigkeit für entferntresize()
. Es dauert etwa 10% länger als bei der Verwendungresize()
. [Natürlich habe ich dabei festgestellt, dass die Verwendung vonv[i]
einen großen Beitrag zur Zeit leistet, die die Funktion benötigt - darüber bin ich ein wenig überrascht].std::vector
Implementierung, die VLA / verwenden würdealloca
, oder ist das nur Spekulation?vector
Implementierung erfolgen.In Bezug auf VLAs im Vergleich zu einem Vektor
Haben Sie gedacht, dass ein Vektor die VLAs selbst nutzen kann? Ohne VLAs muss der Vektor bestimmte "Skalen" von Arrays angeben, z. B. 10, 100, 10000 für die Speicherung, damit Sie am Ende ein 10000-Element-Array für 101 Elemente zuweisen können. Wenn Sie bei VLAs die Größe auf 200 ändern, geht der Algorithmus möglicherweise davon aus, dass Sie nur 200 benötigen und ein Array mit 200 Elementen zuweisen können. Oder es kann ein Puffer von beispielsweise n * 1,5 zugewiesen werden.
Wie auch immer, ich würde argumentieren, dass eine VLA leistungsfähiger ist, wenn Sie wissen, wie viele Elemente Sie zur Laufzeit benötigen (wie der Mats-Benchmark gezeigt hat). Was er demonstrierte, war eine einfache Iteration mit zwei Durchgängen. Denken Sie an Monte-Carlo-Simulationen, bei denen wiederholt Zufallsstichproben entnommen werden, oder an Bildmanipulationen (wie Photoshop-Filter), bei denen Berechnungen für jedes Element mehrmals durchgeführt werden und möglicherweise bei jeder Berechnung für jedes Element Nachbarn betrachtet werden.
Dieser zusätzliche Zeigersprung vom Vektor zu seinem internen Array summiert sich.
Beantwortung der Hauptfrage
Wenn Sie jedoch über die Verwendung einer dynamisch zugewiesenen Struktur wie einer LinkedList sprechen, gibt es keinen Vergleich. Ein Array bietet direkten Zugriff mithilfe von Zeigerarithmetik auf seine Elemente. Mithilfe einer verknüpften Liste müssen Sie die Knoten durchlaufen, um zu einem bestimmten Element zu gelangen. Die VLA gewinnt also in diesem Szenario zweifellos.Nach dieser Antwort ist es architektonisch abhängig, aber in einigen Fällen ist der Speicherzugriff auf den Stapel schneller, da der Stapel im Cache verfügbar ist. Bei einer großen Anzahl von Elementen kann dies negiert werden (möglicherweise die Ursache für die sinkenden Renditen, die Mats in seinen Benchmarks gesehen hat). Es ist jedoch erwähnenswert, dass die Cache-Größen erheblich zunehmen und Sie möglicherweise mehr davon sehen werden.
quelle
std::vector
Arrays skalieren? Warum sollte es Platz für 10K-Elemente benötigen, wenn es nur 101 benötigt? Außerdem werden in der Frage niemals verknüpfte Listen erwähnt, daher bin ich mir nicht sicher, woher Sie das haben. Schließlich werden VLAs in C99 stapelweise zugewiesen. Sie sind eine Standardform vonalloca()
. Alles, was Heap-Speicher benötigt (es lebt herum, nachdem die Funktion zurückgegeben wurde) oder arealloc()
(das Array ändert die Größe selbst), würde VLAs sowieso verbieten.Der Grund für die Verwendung eines VLA ist in erster Linie die Leistung. Es ist ein Fehler, das Wiki-Beispiel als nur "irrelevant" zu betrachten. Ich kann leicht Fälle erkennen, in denen genau dieser Code einen großen Unterschied haben könnte, beispielsweise wenn diese Funktion in einer engen Schleife aufgerufen wurde, in der
read_val
sich eine E / A-Funktion befand, die auf einem System mit kritischer Geschwindigkeit sehr schnell zurückgegeben wurde.In den meisten Orten, in denen VLAs auf diese Weise verwendet werden, ersetzen sie keine Heap-Aufrufe, sondern ersetzen Folgendes:
Die Sache mit jeder lokalen Erklärung ist, dass sie extrem schnell ist. Die Zeile
float vals[n]
erfordert im Allgemeinen nur ein paar Prozessoranweisungen (möglicherweise nur eine). Sie addiert einfach den Wertn
zum Stapelzeiger.Andererseits erfordert eine Heap-Zuweisung das Durchlaufen einer Datenstruktur, um einen freien Bereich zu finden. Die Zeit ist wahrscheinlich sogar im glücklichsten Fall um eine Größenordnung länger. (Das heißt, nur das Platzieren
n
auf dem Stapel und das Aufrufenmalloc
sind wahrscheinlich 5-10 Anweisungen.) Wahrscheinlich viel schlimmer, wenn sich eine angemessene Datenmenge auf dem Heap befindet. Es würde mich überhaupt nicht überraschen, einen Fall zu sehen,malloc
in dem ein reales Programm 100x bis 1000x langsamer war.Natürlich haben Sie dann auch einige Leistungseinbußen beim Matching
free
, die wahrscheinlich in der Größe demmalloc
Anruf ähneln .Darüber hinaus gibt es das Problem der Speicherfragmentierung. Viele kleine Zuordnungen neigen dazu, den Haufen zu fragmentieren. Fragmentierte Haufen verschwenden sowohl Speicher als auch erhöhen die Zeit, die zum Zuweisen von Speicher erforderlich ist.
quelle
int vla[n]; if(test()) { struct LargeStruct s; int i; }
:: Der Stapelversatz vons
ist zum Zeitpunkt der Kompilierung nicht bekannt, und es ist auch zweifelhaft, ob der Compiler den Speicher voni
aus dem inneren Bereich auf einen festen Stapelversatz verschiebt. Daher wird zusätzlicher Maschinencode benötigt, da die Indirektion und dies auch Register verschlingen kann, die für PC-Hardware wichtig sind. Wenn Sie Beispielcode mit Compiler-Assembly-Ausgabe wünschen, stellen Sie bitte eine separate Frage;)s
undi
bei Eingabe der Funktion zuweisen , bevor sietest
aufgerufen odervla
zugewiesen wird, da die Zuweisungen fürs
undi
keine Nebenwirkungen haben. (Und tatsächlichi
könnte es sogar in ein Register gestellt werden, was bedeutet, dass es überhaupt keine "Zuordnung" gibt.) Es gibt keine Compiler-Garantien für die Reihenfolge der Zuordnungen auf dem Stapel oder sogar dafür, dass der Stapel verwendet wird.