2008-10-28 9 views
29

zamieszczaniu pytanie przepełnienia stosu na stackoverflow.com, jak zabawny :-)Jak zwiększyć rozmiar stosu dla aplikacji ruby. Rekurencyjne app uzyskanie: Poziom Stos zbyt głębokie (SystemStackError)

biegnę trochę rekurencyjną kodu Ruby i otrzymuję: "Stack level too deep (SystemStackError)"

(Jestem całkiem pewny, że kod działa, że ​​nie jestem w nieskończonej rekurencyjnej spirali śmierci, ale to nie jest w gruncie rzeczy)

Czy mimo to można zmienić dozwoloną grubość/rozmiar stosu dla mojej aplikacji Ruby?

Nie do końca rozumiem, jeśli jest to ograniczenie w Rubim, ponieważ błąd mówi "Poziom stosu", co daje mi wrażenie, że Ruby w jakiś sposób liczy "poziomy" stosu lub jeśli po prostu oznacza to, że stos jest pełny.

Próbowałem uruchomić ten program zarówno pod Vistą, jak i Ubuntu z tym samym wynikiem. Pod Ubuntu próbowałem zmienić rozmiar stosu za pomocą "ulimit -s" z 8192 na 16000, ale to niczego nie zmieniło.

Edytuj: Dzięki za opinię.
Zdaję sobie sprawę, że używanie funkcji rekursywnej może nie jest najbardziej niezawodną metodą. Ale nie o to chodzi. Po prostu zastanawiam się, czy istnieje sposób na zwiększenie rozmiaru stosu .. okresu. I jak wspomniałem, spróbowałem uruchomić ulimit -s 16000 przed uruchomieniem skryptu ruby ​​.. bez poprawy .. Czy używam go źle?

Edit2: W rzeczywistości miałem nieskończoną rekurencję w skrajnym przypadku kodu.
Ścinany ślad stosu ruby ​​po błędzie "Stack level too deep" jest nieco mylący.
Po zachowaniu rekursywnym obejmującym kilka funkcji, można odnieść wrażenie, że liczba rekursji jest znacznie niższa niż w rzeczywistości. W tym przykładzie jednej rzeczy, że może to wywala po nieco ponad 190 połączeń, ale w rzeczywistości jest to około 15.000 połączeń

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

Odpowiedz

6

Ruby używa stosu C, więc twoje opcje obejmują używanie ulimit lub kompilowanie Rubiego z pewną flagą rozmiaru stosu kompilatora/linkera. Rekurencja ogona nie została jeszcze wdrożona, a obecna obsługa Ruby dla rekursji nie jest tak wielka. Jak fajna i elegancka rekursja, możesz rozważyć poradzenie sobie z ograniczeniami języka i napisanie kodu w inny sposób.

+3

Ta odpowiedź jest poprawna dla wersji Ruby przed 1.9. Wersja 1.9 lub nowsza - patrz http://stackoverflow.com/a/27510458/238886 –

8

Yukihiro Matsumoto pisze here

Ruby używa C stosu, abyś potrzebował użyć ulimit, aby określić limit na głębokości stosu .

3

Pomyśl o tym, co dzieje się z kodem. Jak wspomniały inne plakaty, możliwe jest zhackowanie kodu C tłumacza. Jednak. wynikiem będzie to, że używasz więcej pamięci RAM i nie masz żadnej gwarancji, że nie wysadzisz ponownie stosu.

Naprawdę dobrym rozwiązaniem byłoby wymyślenie algorytmu iteracyjnego dla tego, co próbujesz zrobić. Czasami pomocne może okazać się zapamiętywanie, a czasami okazuje się, że nie używasz rzeczy, które naciskasz na stos, w którym to przypadku możesz zastąpić wywołania rekursywne stanem zmiennym.

Jeśli jesteś nowy w tego rodzaju rzeczy spojrzeć na SICP here dla niektórych pomysłów ...

13

Jeżeli jesteś pewien, że nie mają nieskończony sytuację rekurencji wówczas algorytm jest pobably nie nadaje się do Ruby, aby wykonać ją w sposób rekurencyjny. Konwertowanie algorytmu z rekurencji na inny rodzaj stosu jest dość łatwe i sugeruję, abyś spróbował tego. Oto, jak możesz to zrobić.

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

recursive(starting_params) 

przekształci

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

prostu miał ten sam problem i to jest bardzo łatwe do naprawienia na Linux lub Mac. Jak wspomniano w innych odpowiedziach, Ruby używa ustawienia stosu systemowego. Możesz to łatwo zmienić na Macu i Linuksie, ustawiając rozmiar stosu. Przykład z Fox:

ulimit -s 20000 
11

To pytanie i odpowiedzi zostały wydane z powrotem w wersji Ruby 1.8.x, która używała stosu C. Ruby 1.9.x i nowsze używają maszyny wirtualnej, która ma własny stos. W Ruby 2.0.0 i nowszych rozmiar stosu VM można kontrolować za pomocą zmiennej środowiskowej RUBY_THREAD_VM_STACK_SIZE.

+4

Pomocna! Na przykład 'export RUBY_THREAD_VM_STACK_SIZE = 5000000' ustawia go na 5 MB. –

1

Od Ruby 1.9.2 można włączyć optymalizacji ogon połączenia z czymś takim:

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 

To pozwoli uniknąć błędu SystemStackError jeśli wywołanie rekurencyjne jest na końcu metody i tylko metoda. Oczywiście ten przykład spowoduje nieskończoną pętlę. Prawdopodobnie najlepiej jest debugować przy użyciu płytkiej rekursji (i bez optymalizacji) przed przejściem do głębokiej rekursji.