Come aumentare le dimensioni dello stack per un’app ruby. App ricorsiva: livello di stack troppo alto (SystemStackError)

Pubblicazione di una domanda di overflow dello stack su stackoverflow.com, che divertimento 🙂

Sto eseguendo del codice Ruby ricorsivo e ottengo il: "Stack level too deep (SystemStackError)"

(Sono abbastanza sicuro che il codice funzioni, che non sia in una spirale di morte ricorsiva infinita, ma non è questo il punto in ogni caso)

Esiste comunque la possibilità di modificare la profondità / dimensione dello stack consentita per la mia app Ruby?

Non lo capisco se questa è una restrizione in Ruby, poiché l’errore dice “Stack level”, che mi dà l’impressione che Ruby conti in qualche modo “livelli” di stack, o se semplicemente significa che lo stack è pieno.

Ho provato a eseguire questo programma con Vista e Ubuntu con lo stesso risultato. Sotto Ubuntu ho provato a cambiare le dimensioni dello stack con ‘ulimit -s’ da 8192 a 16000, ma questo non ha cambiato nulla.

Modifica: Grazie per il feedback.
Mi rendo conto che l’uso di una funzione ricorsiva non è forse il modo più efficace. Ma non è questo il punto. Mi chiedo semplicemente se c’è un modo per aumentare le dimensioni dello stack .. periodo. E come ho detto ho provato a eseguire ulimit -s 16000 prima di eseguire lo script ruby ​​.. senza miglioramenti .. Sto usando male?

Edit2: In realtà avevo una ricorsione infinita in un caso limite del codice.
La traccia troncata dello stack di rubini quando ottieni l’errore "Stack level too deep" è un po ‘fuorviante.
Quando si ha un comportamento ricorsivo che coinvolge diverse funzioni, si ha l’impressione che il numero di ricorsioni sia molto più basso di quanto non sia in realtà. In questo esempio, potrebbe accadere che si blocchi dopo poco più di 190 chiamate, ma in realtà sono circa 15000 chiamate

 tst.rb:8:in `p': stack level too deep (SystemStackError) from tst.rb:8:in `bar' from tst.rb:12:in `bar' from tst.rb:19:in `foo' from tst.rb:10:in `bar' from tst.rb:19:in `foo' from tst.rb:10:in `bar' from tst.rb:19:in `foo' from tst.rb:10:in `bar' ... 190 levels... from tst.rb:19:in `foo' from tst.rb:10:in `bar' from tst.rb:19:in `foo' from tst.rb:22 

-Andreas

Ruby usa lo stack C in modo che le tue opzioni includano l’uso di ulimit o la compilazione di Ruby con un flag di dimensioni dello stack compilatore / linker. La ricorsione della coda deve ancora essere implementata e l’attuale supporto di Ruby per la ricorsione non è così grande. Poiché la ricorsione è fredda ed elegante, potresti voler prendere in considerazione la possibilità di affrontare i limiti della lingua e di scrivere il tuo codice in un modo diverso.

Se sei sicuro di non avere una situazione di ricorsione infinita, allora il tuo algorore non è adatto a Ruby per eseguirlo in modo recidivo. Convertire un algoritmo dalla ricorsione a diversi tipi di stack è piuttosto semplice e ti suggerisco di provarlo. Ecco come puoi farlo.

 def recursive(params) if some_conditions(params) recursive(update_params(params)) end end recursive(starting_params) 

si trasformsrà in

 stack = [starting_params] while !stack.empty? current_params = stack.delete_at(0) if some_conditions(current_params) stack << update_params(current_params) end end 

Questa domanda e le sue risposte sembrano risalire a Ruby 1.8.x, che utilizzava lo stack C. Ruby 1.9.xe versioni successive utilizzano una VM con il proprio stack. In Ruby 2.0.0 e versioni successive, la dimensione dello stack VM può essere controllata tramite la variabile di ambiente RUBY_THREAD_VM_STACK_SIZE .

Yukihiro Matsumoto scrive qui

Ruby usa lo stack C, quindi devi usare ulimit per specificare un limite sulla profondità dello stack.

Pensa a cosa sta succedendo con il codice. Come altri autori hanno menzionato, è ansible hackerare il codice C dell’interprete. Però. il risultato sarà che si sta utilizzando più RAM e non si ha alcuna garanzia che non si possa saltare di nuovo lo stack.

La soluzione veramente buona sarebbe quella di elaborare un algoritmo iterativo per ciò che stai cercando di fare. A volte la memoria può aiutare e a volte ti accorgi che non stai utilizzando le cose che stai spingendo in pila, nel qual caso puoi sostituire le chiamate ricorsive con lo stato mutabile.

Se sei nuovo a questo genere di cose dai un’occhiata a SICP qui per alcune idee …

Ho appena avuto lo stesso problema ed è molto facile da risolvere su Linux o su Mac. Come detto nelle altre risposte, Ruby usa l’impostazione dello stack di sistema. Puoi facilmente modificarlo su Mac e Linux impostando la dimensione dello stack. Esempio di Fox:

 ulimit -s 20000 

A partire da Ruby 1.9.2 è ansible triggersre l’ottimizzazione tail-call con qualcosa come:

 RubyVM::InstructionSequence.compile_option = { tailcall_optimization: true, trace_instruction: false } RubyVM::InstructionSequence.new(<<-EOF).eval def me_myself_and_i me_myself_and_i end EOF me_myself_and_i # Infinite loop, not stack overflow 

Ciò eviterà l'errore SystemStackError se la chiamata ricorsiva si trova alla fine del metodo e solo il metodo . Ovviamente, questo esempio genererà un ciclo infinito. Probabilmente è meglio eseguire il debug usando la ricorsione superficiale (e senza ottimizzazione) prima di procedere alla ricorsione profonda.