Forum: PC-Programmierung Stacksize von Programm hochsetzen oder mit Heap arbeiten?


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Hallo Forum,

ich habe in einem Programm (Rust, Linux) mehrere grosse Arrays, zusammen 
10MB. Damit das aber funktioniert, muesste ich beim compilieren die 
Stacksize aendern.
1
thread 'main' has overflowed its stack
2
fatal runtime error: stack overflow
Ich kann statt den Arrays auch Vectoren nehmen, die bieten mir aber 
ansonsten (fuer meinen Anwendungsfall) keine Vor-, aber auch keine 
Nachteile.

Ist das aender der Stacksize beim compilieren "gute" praxis?
Oder ist das eher ein "Kann man machen, aber..."?

Gruesse

von rst (Gast)


Lesenswert?

Hallo,

zwei kurze Gegenfragen:
1. Ist das der Compiler, der sich mit einem Stackoverflow verabschiedet? 
Dann wäre ein Issue auf Github hilfreich. Falls es ein Stack Overflow 
zur Laufzeit deines Programms ist:
2. Wie sind die Arrays angelegt worden?

Der folgende Weg via Box::new() "geht nicht" für große Arrays:
1
let _ = Box::new([0_u8; 4*1024*1024]); // 4 MB
Er funktioniert deswegen nicht, weil das Array erst auf dem Stack 
angelegt wird (im Beispiel `[0_u8; 4*1024*1024]`) und dann auf den Heap 
verschoben wird. Korrekt wäre es tatsächlich einen Vektor zu nehmen, da 
dieser direkt auf dem Heap alloziert. Wenn du aber eine "owned 
slice"/"boxed slice" haben möchtest, dann geht das so:
1
let _ = vec![0_u8; 4*1024*1024].into_boxed_slice();
Siehe dazu auch https://github.com/rust-lang/rust-clippy/issues/4520.

Das als mögliche Verhinderung des Stack-Overflows.

Deine Eigentliche Frage bezog sich aber auf das Ändern der Stack-Größe. 
Das kann man meiner Meinung nach zwar machen, weißt aber im Allgemeinen 
auf einen Fehler hin. Per default hast du glaube ich 2 MB Stack pro 
Thread; das sollte reichen, sofern man große Dinge, wie deine Arrays, 
auf den Heap legt.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

rst schrieb:
> 1. Ist das der Compiler, der sich mit einem Stackoverflow verabschiedet?
Nein, das ist das Programm.

rst schrieb:
> 2. Wie sind die Arrays angelegt worden?
1
const SIZE_A: usize = 1024 * 1024;
2
const SIZE_B: usize = 1024 * 1024 * 4;
3
4
struct T_Crash {
5
    x: [u8; SIZE_A],
6
    y: [u16; SIZE_B],
7
    z: [u8; SIZE_A],
8
}
9
10
impl T_Crash {
11
    fn new() -> Self {
12
        T_Crash {
13
            x: [0; SIZE_A],
14
            y: [0; SIZE_B],
15
            z: [0; SIZE_A],
16
        }
17
    }
18
}
19
20
fn main() {
21
    let _ = T_Crash::new();
22
}

von intus (Gast)


Lesenswert?

Wenn diese Variablen für die ganze Programmlaufzeit gebraucht werden, 
können sie auf den Stack. Werden sie nur kurtzzeitig gebraucht ist es 
unschön dafür grundsätzlich Speicher auf dem Stack zu reservieren.

von rst (Gast)


Lesenswert?

Hallo,

prinzipiell hat intus recht, auf dem Stack sind kurzlebige kleine 
Datenstrukturen schöner als auf dem Heap. Aber der Stack ist nicht für 
riesige Datenstrukturen da, daher ja auch der Stack Overflow.

Eine saubere Lösung wäre es, die großen Strukturen auf den Heap 
auszulagern:
1
const SIZE_A: usize = 1024 * 1024;
2
const SIZE_B: usize = 1024 * 1024 * 4;
3
4
struct T_Crash {
5
    x: Box<[u8]>,
6
    y: Box<[u16]>,
7
    z: Box<[u8]>,
8
}
9
10
impl T_Crash {
11
    fn new() -> Self {
12
        T_Crash {
13
            x: vec![0; SIZE_A].into_boxed_slice(),
14
            y: vec![0; SIZE_B].into_boxed_slice(),
15
            z: vec![0; SIZE_A].into_boxed_slice(),
16
        }
17
    }
18
}
19
20
fn main() {
21
    let _ = T_Crash::new();
22
}
Funktioniert ohne etwas an der Stackgröße herumschrauben zu müssen: 
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=368e0494cd8875958b6403e18dda92a8

von Weg mit dem Troll ! Aber subito (Gast)


Lesenswert?

Weshalb sowas dynamisch auf Stack/Heap allozieren, und nicht global ?

von udok (Gast)


Lesenswert?

Keine Ahnung, was Rust so kann, in C würde ich einfach static 
davorschreiben...

von cppbert3 (Gast)


Lesenswert?

Weg mit dem Troll ! Aber subito schrieb:
> Weshalb sowas dynamisch auf Stack/Heap allozieren, und nicht
> global ?

Global ist stack - weil das array zugross ist fuer den default stack, 
auch unter C

von cppbert3 (Gast)


Lesenswert?

Oder global wie "teil des executable image"

von Vlad T. (vlad_tepesch)


Lesenswert?

cppbert3 schrieb:
> Weg mit dem Troll ! Aber subito schrieb:
> Weshalb sowas dynamisch auf Stack/Heap allozieren, und nicht global ?
>
> Global ist stack - weil das array zugross ist fuer den default stack,
> auch unter C

Global ist nicht Stack! Sondern Global. Wenn deine Routinen, die das 
sonst auf dem Stack gemacht haben, nicht reentrent sein müssen, wäre 
das, oder ein davorschreiben von 'static', wahrscheinlich die beste 
Variante.


Globale Variablen werden beim Laden des executable  alloziert Und sind 
so lange gültig, wie das Programm läuft.
Werden die globalen Variablen mit null oder gar nicht initialisiert, 
dann nehmen sie im Binary Noch nicht Mal Platz weg, sondern werden und 
bss angeht gepackt, anstelle des Data Segment

: Bearbeitet durch User
von DPA (Gast)


Lesenswert?

Wurde rust nicht extra so designt, um genau solche 
Speicherzugriffsverletzungen zu verhindern?

von rst (Gast)


Lesenswert?

DPA schrieb:
> Wurde rust nicht extra so designt, um genau solche
> Speicherzugriffsverletzungen zu verhindern?

Tut es doch: anstatt den Stack still zu überschreiben bricht das 
Programm kontrolliert ab. Und das nicht mit einem Segfault oder einem 
Abort, sondern mit einem Sprachkonstrukt (einer sog. panic). Was soll 
die Sprache denn tun, wenn der Programmierer mehr Speicher als verfügbar 
auf dem Stack alloziert? Die einzige mehr oder weniger triviale Lösung 
wäre es, alles auf dem Heap zu allozieren (vgl. Java), aber das geht zu 
lasten der Performance.

Das vorliegende Problem ist mehr oder weniger sprachunabhängig.

Kurz zum Thema global: globale Variablen sind in anderen Sprachen 
schlechter Stil, ebenso in Rust. Es gibt seltene Ausnahmen, aber 
Normalerweise ist es schöner, Ressourcen (nichts anderes ist 
Stack-/Heap-Speicher) erst wenn benötigt zu belegen und frühst möglich 
freizugeben. In Rust könnte man wenn man möchte genauso statics anlegen, 
jedoch sind diese aufwändiger zu handhaben (statics sind nicht 
threadsafe, also benötigt man einen Mutex oder ähnliches, was einen 
Performance-Nachteil mit sich bringt, oder man nutzt unsafe-Blöcke). Die 
Sprache "zwingt" den Programmierer also sogar vorsichtig in die 
"richtige" Richtung, ohne den anderen Weg aber zu versperren.
Imho ist es nicht ratsam, auf Teufel komm raus auf den Heap zu 
verzichten. Gerade für dynamische oder große statische Datenstrukturen 
ist er auf einem PC unerlässlich (auf Mikrocontrollern ggf. eine andere 
Sache).

Grüße

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Danke fuer die Antworten. :)

Das Objekt existiert fuer die gesamte Laufzeit des Programms.

Hat die Box
1
x: Box<[u8]>
Vorteile gegenueber eines Vectors?
1
x: Vec<u8>

DPA schrieb:
> Wurde rust nicht extra so designt, um genau solche
> Speicherzugriffsverletzungen zu verhindern?
Das Programm stuertzt direkt nach dem Start ab, es wird "kein Code" 
ausgefuehrt (z.B. println!(...)), egal ob der Code vor oder hinter der 
Allokation steht.

Schoen waere es, wenn der Compiler eine Warnung bzgl. der Stackgroesse 
geben wuerde. Die Groesse der Arrays steht ja zur Compiletime fest. Aber 
gut, ich hatte gar nicht auf dem Schirm, das es da eine 
Default-Stacksize gibt. Wieder was gelernt. :)

rst schrieb:
> Imho ist es nicht ratsam, auf Teufel komm raus auf den Heap zu
> verzichten.
War auch keine "Absicht". Learning by doing. :)

Gruesse

-------------------
Edit:

Habe gerade gesehen, das im Bugtracker ueber eine Warnung bzgl. der 
Stackgroesse/grossen Arrays disskutiert wird.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Kaj G. schrieb:
> Ist das aender der Stacksize beim compilieren "gute" praxis

Kommt auf die Lebensdauer der Arrays an.

Wenn du den Speicherplatz des Arrays genau so lange brauchst wie die 
Funktion dauert, kommen Daten auf den Stack.

Dessen Speicherverwaltung ist schneller.

Bei nur ein paar Arrays wäre der Verwaltungsoverhead aber 
vernachlässigbar.

Da wäre erstens die Frage:

Möchte man auch auf anderen Plattformen kompilieren ohne dort erstmal 
rausfinden zu müssen wie man die Stacksize ändert ?

Möchte man den Code in eine DLL tun die den Stack vom Hauptprogramm 
benutzt, der nicht unter deinem Einfluss steht.

Müssen 10 MB überhaupt in ddn Speicher, oder nutzt man memory mapping 
von files.

Braucht dein Programm immer 10 MB oder auch mal nur 1 MB oder 100k ? 
Dann könnte es in den kleinen Fällen mit weniger Gesamtspeicher auf 
kleineren Rechnern laufen wenn du es per heap machst.

von S. R. (svenska)


Lesenswert?

Kaj G. schrieb:
> Aber gut, ich hatte gar nicht auf dem Schirm, das es da eine
> Default-Stacksize gibt. Wieder was gelernt. :)

Ich hab das bei einem etwas größeren rekursiven Algorithmus gelernt, der 
machte bei 8 MB oder so "puff". Eigentlich ist das auch logisch, da ja 
alle Threads (mit jeweils ihrem eigenen Stack) sich den gleichen 
Adressraum teilen müssen, kann der Stack nicht beliebig groß sein - bei 
32 Bit ist das relevant.

rst schrieb:
> Und das nicht mit einem Segfault oder einem
> Abort, sondern mit einem Sprachkonstrukt (einer sog. panic).

Ob mir die Sprache ein "panic", ein abort() oder eine 
OutOfMemoryException schenkt, ist aus meiner Sicht jetzt kein großer 
Unterschied. Das sind alles Suizidfragmente, auf die man als 
Programmierer nur sehr eingeschränkt reagieren kann.

Ein Segfault ist eine etwas andere Hausnummer, zugegeben. Aber auch den 
kann man im Prinzip ganz normal behandeln.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.