Warum ist die Stapelspeichergröße so begrenzt?

81

Wenn Sie Speicher auf dem Heap zuweisen, ist die einzige Grenze der freie RAM (oder der virtuelle Speicher). Es macht Gb des Gedächtnisses.

Warum ist die Stapelgröße so begrenzt (ca. 1 MB)? Welcher technische Grund hindert Sie daran, wirklich große Objekte auf dem Stapel zu erstellen?

Update : Meine Absicht ist möglicherweise nicht klar. Ich möchte keine großen Objekte auf dem Stapel zuweisen und benötige keinen größeren Stapel. Diese Frage ist nur reine Neugier.

undu
quelle
Warum sollte es praktisch sein, große Objekte auf dem Haufen zu erstellen? (Anrufketten gehen normalerweise auf den Stapel.)
Makoto
4
Ich denke, die eigentliche Antwort ist einfacher als die meisten Antworten: "Weil wir es immer so gemacht haben und es bisher in Ordnung war. Warum also ändern?"
Jerry Coffin
@ JerryCoffin Hast du eine der bisher veröffentlichten Antworten gelesen? Es gibt mehr Einblick in diese Frage.
user1202136
3
@ user1202136: Ich habe alle gelesen - aber die Leute raten, und ich vermute, dass viele der Faktoren, die sie zitieren, wahrscheinlich nicht einmal bei den ursprünglichen Entscheidungen zu diesem Thema berücksichtigt wurden. Um einen Satz zu prägen: "Manchmal ist eine Zigarre nur eine Zigarre."
Jerry Coffin
3
"Wie groß sollten wir den Standardstapel machen?" "Oh, ich weiß nicht, wie viele Threads können wir laufen lassen?" "Es explodiert irgendwo über einem K" "OK, dann nennen wir es 2K, wir haben 2 Gig virtuell, also wie wäre es mit 1 Meg?" "Ja, OK, was ist die nächste Ausgabe?"
Martin James

Antworten:

45

Meine Intuition ist die folgende. Der Stapel ist nicht so einfach zu verwalten wie der Heap. Der Stapel muss an fortlaufenden Speicherorten gespeichert werden. Dies bedeutet, dass Sie den Stapel nicht nach Bedarf zufällig zuordnen können, sondern mindestens virtuelle Adressen für diesen Zweck reservieren müssen. Je größer der reservierte virtuelle Adressraum ist, desto weniger Threads können Sie erstellen.

Beispielsweise hat eine 32-Bit-Anwendung im Allgemeinen einen virtuellen Adressraum von 2 GB. Dies bedeutet, dass Sie bei einer Stapelgröße von 2 MB (standardmäßig in pthreads) maximal 1024 Threads erstellen können. Dies kann für Anwendungen wie Webserver klein sein. Durch Erhöhen der Stapelgröße auf beispielsweise 100 MB (dh Sie reservieren 100 MB, weisen dem Stapel jedoch nicht unbedingt sofort 100 MB zu) würde die Anzahl der Threads auf etwa 20 begrenzen, was selbst für einfache GUI-Anwendungen einschränkend sein kann.

Eine interessante Frage ist, warum wir dieses Limit auf 64-Bit-Plattformen immer noch haben. Ich kenne die Antwort nicht, gehe aber davon aus, dass die Benutzer bereits an einige "Best Practices für Stapel" gewöhnt sind: Achten Sie darauf, große Objekte auf dem Heap zuzuweisen und die Stapelgröße bei Bedarf manuell zu erhöhen. Daher fand es niemand nützlich, auf 64-Bit-Plattformen "riesige" Stack-Unterstützung hinzuzufügen.

user1202136
quelle
Viele 64-Bit-Computer haben nur 48-Bit-Adressen (eine große Verstärkung über 32-Bit, aber immer noch begrenzt). Selbst bei zusätzlichem Speicherplatz müssen Sie sich Gedanken darüber machen, wie die Reservierung in Bezug auf Seitentabellen erfolgt - das heißt, es ist immer mit mehr Speicherplatz verbunden. Es ist wahrscheinlich genauso billig, wenn nicht sogar billiger, ein neues Segment (mmap) zuzuweisen, anstatt für jeden Thread große Stapelplätze zu reservieren.
edA-qa mort-ora-y
4
@ edA-qamort-ora-y: In dieser Antwort geht es nicht um Zuweisung , sondern um die Speicherung von virtuellem Speicher , die fast kostenlos und mit Sicherheit viel schneller als mmap ist.
Mooing Duck
32

Ein Aspekt, den noch niemand erwähnt hat:

Eine begrenzte Stapelgröße ist ein Fehlererkennungs- und Eindämmungsmechanismus.

Im Allgemeinen besteht die Hauptaufgabe des Stapels in C und C ++ darin, den Aufrufstapel und die lokalen Variablen zu verfolgen. Wenn der Stapel außerhalb der Grenzen wächst, ist dies fast immer ein Fehler im Design und / oder im Verhalten der Anwendung .

Wenn der Stapel beliebig groß werden könnte, würden diese Fehler (wie die unendliche Rekursion) erst sehr spät abgefangen, wenn die Ressourcen des Betriebssystems erschöpft sind. Dies wird verhindert, indem eine beliebige Grenze für die Stapelgröße festgelegt wird. Die tatsächliche Größe ist nicht so wichtig, abgesehen davon, dass sie klein genug ist, um eine Systemverschlechterung zu verhindern.

Andreas Grapentin
quelle
Möglicherweise haben Sie ein ähnliches Problem mit zugewiesenen Objekten (eine Möglichkeit, die Rekursion zu ersetzen, besteht darin, einen Stapel manuell zu behandeln). Diese Einschränkung zwingt dazu, andere Methoden zu verwenden (die nicht unbedingt sicherer / einfacher / .. sind) (Beachten Sie die Anzahl der Bemerkungen zur Implementierung von (Spielzeug-) Listen std::unique_ptr, um einen Destruktor zu schreiben (und sich nicht auf den intelligenten Zeiger zu verlassen)).
Jarod42
15

Es ist nur eine Standardgröße. Wenn Sie mehr benötigen, können Sie mehr erhalten - meistens, indem Sie den Linker anweisen, zusätzlichen Stapelspeicher zuzuweisen.

Der Nachteil großer Stapel besteht darin, dass beim Erstellen vieler Threads jeweils ein Stapel benötigt wird. Wenn alle Stapel Multi-MBs zuweisen, diese jedoch nicht verwenden, wird der Speicherplatz verschwendet.

Sie müssen die richtige Balance für Ihr Programm finden.


Einige Leute, wie @BJovke, glauben, dass der virtuelle Speicher im Wesentlichen frei ist. Es ist wahr, dass Sie keinen physischen Speicher benötigen, der den gesamten virtuellen Speicher sichert. Sie müssen in der Lage sein, mindestens Adressen an den virtuellen Speicher zu vergeben.

Auf einem typischen 32-Bit-PC entspricht die Größe des virtuellen Speichers jedoch der Größe des physischen Speichers, da für jede virtuelle oder nicht virtuelle Adresse nur 32 Bit zur Verfügung stehen.

Da alle Threads in einem Prozess denselben Adressraum verwenden, müssen sie ihn zwischen ihnen aufteilen. Und nachdem das Betriebssystem seinen Teil dazu beigetragen hat, bleiben "nur" 2-3 GB für eine Anwendung übrig. Und diese Größe ist die Grenze sowohl für den physischen als auch für den virtuellen Speicher, da es einfach keine Adressen mehr gibt.

Bo Persson
quelle
Das größte Threading-Problem besteht darin, dass Sie Stapelobjekte nicht einfach anderen Threads signalisieren können. Entweder muss der Produzententhread synchron warten, bis der Konsumententhread das Objekt freigibt, oder es müssen teure und konkurrenzerzeugende Tiefenkopien erstellt werden.
Martin James
2
@MartinJames: Niemand sagt, dass sich alle Objekte auf dem Stapel befinden sollten. Wir diskutieren, warum die Standardstapelgröße klein ist.
Mooing Duck
Speicherplatz wird nicht verschwendet, die Stapelgröße ist nur eine Reservierung des kontinuierlichen virtuellen Adressraums. Wenn Sie also eine Stapelgröße von 100 MB festlegen , hängt die tatsächlich verwendete RAM-Größe vom Stapelverbrauch in Threads ab.
BJovke
1
@BJovke - Der virtuelle Adressraum wird jedoch weiterhin belegt. In einem 32-Bit-Prozess ist dies auf einige GB beschränkt. Wenn Sie also nur 20 * 100 MB reservieren, treten Probleme auf.
Bo Persson
7

Zum einen ist der Stapel kontinuierlich. Wenn Sie also 12 MB zuweisen, müssen Sie 12 MB entfernen, wenn Sie die von Ihnen erstellten Werte unterschreiten möchten. Auch das Bewegen von Objekten wird viel schwieriger. Hier ist ein Beispiel aus der Praxis, das das Verständnis erleichtern kann:

Angenommen, Sie stapeln Kisten um einen Raum. Welches ist einfacher zu verwalten:

  • Stapeln von Kisten mit beliebigem Gewicht übereinander, aber wenn Sie etwas auf den Boden bringen möchten, müssen Sie Ihren gesamten Stapel rückgängig machen. Wenn Sie einen Gegenstand aus dem Stapel nehmen und an eine andere Person weitergeben möchten, müssen Sie alle Kisten abnehmen und die Kiste auf den Stapel der anderen Person legen (nur Stapel).
  • Sie legen alle Ihre Kisten (mit Ausnahme von wirklich kleinen Kisten) in einen speziellen Bereich, in dem Sie keine Sachen über andere Sachen stapeln und notieren, wo Sie sie auf ein Stück Papier (einen Zeiger) legen und das Papier darauf legen der Stapel. Wenn Sie die Schachtel jemand anderem geben müssen, geben Sie ihm einfach den Zettel von Ihrem Stapel oder geben Sie ihm eine Fotokopie des Papiers und lassen Sie das Original dort, wo es sich auf Ihrem Stapel befand. (Stapel + Haufen)

Diese beiden Beispiele sind grobe Verallgemeinerungen, und es gibt einige Punkte, die in der Analogie offensichtlich falsch sind, aber sie sind nah genug, dass sie Ihnen hoffentlich helfen werden, die Vorteile in beiden Fällen zu erkennen.

Scott Chamberlain
quelle
@MooingDuck Ja, aber Sie arbeiten im virtuellen Speicher Ihres Programms. Wenn ich eine Unterroutine eingebe, etwas auf den Stapel lege und dann von der Unterroutine zurückkehre, muss ich das erstellte Objekt entweder aufheben oder verschieben, bevor ich mich abwickeln kann der Stapel, um dorthin zurückzukehren, wo ich herkam.
Scott Chamberlain
1
Obwohl mein Kommentar auf eine Fehlinterpretation zurückzuführen war (und ich ihn gelöscht habe), stimme ich dieser Antwort immer noch nicht zu. Das Entfernen von 12 MB von der Oberseite des Stapels ist buchstäblich ein Opcode. Es ist im Grunde kostenlos. Auch Compiler können und tun dies, um die "Stapel" -Regel zu betrügen. Nein, sie müssen das Objekt vor dem Abwickeln nicht kopieren / verschieben, um es zurückzugeben. Daher denke ich, dass Ihr Kommentar auch falsch ist.
Mooing Duck
Nun, es spielt normalerweise keine Rolle, dass die Freigabe von 12 MB einen Opcode auf einem Stapel über 100 auf dem Heap erfordert - er liegt wahrscheinlich unter dem Rauschpegel der tatsächlichen Verarbeitung des 12-MB-Puffers. Wenn Compiler betrügen möchten, wenn sie bemerken, dass ein lächerlich großes Objekt zurückgegeben wird (z. B. indem sie den SP vor dem Aufruf verschieben, um den Objektbereich in den Aufruferstapel aufzunehmen), ist dies jedoch in Ordnung, TBH, Entwickler, die ein solches Objekt zurückgeben Objekte (anstelle von Zeigern / Refs) sind etwas programmierproblematisch.
Martin James
@MartinJames: Die C ++ - Spezifikation besagt auch, dass die Funktion die Daten normalerweise direkt in den Zielpuffer stellen und nicht den temporären verwenden kann. Wenn Sie also vorsichtig sind, entsteht kein Aufwand für die Rückgabe eines 12-MB-Puffers nach Wert.
Mooing Duck
3

Denken Sie an den Stapel in der Reihenfolge von nah bis fern. Die Register befinden sich in der Nähe der CPU (schnell), der Stapel ist etwas weiter entfernt (aber immer noch relativ nah) und der Heap ist weit entfernt (langsamer Zugriff).

Der Stack lebt natürlich auf dem Heap, aber da er kontinuierlich verwendet wird, verlässt er wahrscheinlich nie die CPU-Cache (s) und ist damit schneller als nur der durchschnittliche Heap-Zugriff. Dies ist ein Grund, die Größe des Stapels angemessen zu halten. um es so weit wie möglich zwischengespeichert zu halten. Das Zuweisen großer Stapelobjekte (möglicherweise wird die Größe des Stapels automatisch geändert, wenn Überläufe auftreten) verstößt gegen dieses Prinzip.

Es ist also ein gutes Paradigma für die Leistung, nicht nur ein Überbleibsel aus alten Zeiten.

Ruud van Gaal
quelle
4
Obwohl ich glaube, dass das Caching eine große Rolle spielt, um die Stapelgröße künstlich zu reduzieren, muss ich Sie in der Aussage "Der Stapel lebt auf dem Haufen" korrigieren. Sowohl der Stapel als auch der Heap befinden sich im Speicher (virtuell oder physisch).
Sebastian Hojas
Wie hängt "nah oder fern" mit der Zugriffsgeschwindigkeit zusammen?
Minh Nghĩa
@ MinhNghĩa Nun, Variablen im RAM werden im L2-Speicher zwischengespeichert, dann werden sie im L1-Speicher zwischengespeichert, und dann werden sogar diese in den Registern zwischengespeichert. Der Zugriff auf RAM ist langsam, auf L2 ist schneller, L1 ist noch schneller und das Register ist am schnellsten. Ich denke, OP bedeutete, dass auf im Stapel gespeicherte Variablen schnell zugegriffen werden soll, sodass die CPU ihr Bestes versucht, Stapelvariablen in der Nähe zu halten. Daher möchten Sie sie klein machen, damit die CPU schneller auf die Variablen zugreifen kann.
157 239n
1

Viele der Dinge, für die Sie einen großen Stapel benötigen, können auf andere Weise erledigt werden.

Sedgewicks "Algorithmen" enthält einige gute Beispiele für das "Entfernen" der Rekursion aus rekursiven Algorithmen wie QuickSort, indem die Rekursion durch Iteration ersetzt wird. In Wirklichkeit ist der Algorithmus immer noch rekursiv und es gibt immer noch einen Stapel, aber Sie weisen den Sortierstapel auf dem Heap zu, anstatt den Laufzeitstapel zu verwenden.

(Ich bevorzuge die zweite Ausgabe mit Algorithmen, die in Pascal angegeben sind. Sie kann für acht Dollar verwendet werden.)

Eine andere Sichtweise ist, wenn Sie glauben, einen großen Stapel zu benötigen, ist Ihr Code ineffizient. Es gibt einen besseren Weg, der weniger Stapel verwendet.

Mike Crawford
quelle
-8

Ich glaube nicht, dass es einen technischen Grund gibt, aber es wäre eine seltsame App, die nur ein riesiges Superobjekt auf dem Stapel erstellt hat. Stapelobjekte haben keine Flexibilität, die mit zunehmender Größe problematischer wird. Sie können nicht zurückkehren, ohne sie zu zerstören, und Sie können sie nicht in andere Threads einreihen.

Martin James
quelle
1
Niemand sagt, dass sich alle Objekte auf dem Stapel befinden sollten. Wir diskutieren, warum die Standardstapelgröße klein ist.
Mooing Duck
Es ist nicht klein! Wie viele Funktionsaufrufe müssten Sie durchführen, um 1 MB Stack zu verbrauchen? Die Standardeinstellungen können im Linker ohnehin leicht geändert werden, und so bleibt uns die Frage, warum Stack anstelle von Heap verwendet werden soll.
Martin James
3
ein Funktionsaufruf. int main() { char buffer[1048576]; } Es ist ein sehr häufiges Problem für Neulinge. Sicher gibt es eine einfache Problemumgehung, aber warum sollten wir die Stapelgröße umgehen müssen?
Mooing Duck
Zum einen möchte ich nicht, dass dem Stapel jedes Threads, der die betroffene Funktion aufruft, die Stapelanforderungen von 12 MB (oder sogar 1 MB) auferlegt werden. Trotzdem muss ich zustimmen, dass 1 MB ein wenig geizig ist. Ich würde mich über eine Standardeinstellung von 100 MB freuen, schließlich hindert mich nichts daran, sie auf 128 KB herunterzuschalten, genauso wie andere Entwickler sie nicht aufhalten können.
Martin James
1
Warum möchten Sie Ihrem Thread nicht 12 MB Stapel hinzufügen? Der einzige Grund dafür ist, dass die Stapel klein sind. Das ist ein rekursives Argument.
Mooing Duck