Как увеличить размер стека для приложения ruby. Получение рекурсивного приложения: уровень стека слишком глубокий (SystemStackError)
Публикация вопроса о переполнении стека на stack overflow.com, как забавно:-)
Я запускаю некоторый рекурсивный код Ruby и получаю: "Stack level too deep (SystemStackError)"
(Я совершенно уверен, что код работает, что я не нахожусь в бесконечной рекурсивной спирали смерти, но это не главное)
Есть ли способ изменить допустимую глубину / размер стека для моего приложения на Ruby?
Я не совсем понимаю, если это ограничение в Ruby, так как ошибка говорит "Уровень стека", что создает у меня впечатление, что Ruby каким-то образом считает "уровни" стека или просто означает, что стек заполнен.
Я попытался запустить эту программу под Vista и Ubuntu с тем же результатом. В Ubuntu я пытался изменить размер стека с помощью ulimit -s с 8192 до 16000, но это ничего не изменило.
Изменить: Спасибо за отзыв.
Я понимаю, что использование рекурсивной функции, возможно, не самый надежный способ. Но это не главное. Мне просто интересно, есть ли способ увеличить размер стека.. период. И, как я уже говорил, я попытался запустить ulimit -s 16000 перед запуском сценария ruby ... без улучшений... Я неправильно его использую?
Edit2: у меня фактически была бесконечная рекурсия в крайнем случае кода.
Усеченная трассировка стека рубинов, когда вы получаете "Stack level too deep"
ошибка немного вводит в заблуждение.
При рекурсивном поведении, включающем несколько функций, создается впечатление, что количество рекурсий намного меньше, чем на самом деле. В этом примере может показаться, что он падает после чуть более 190 вызовов, но на самом деле это около 15000 вызовов.
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
7 ответов
Ruby использует стек C, поэтому ваши варианты включают использование ulimit или компиляцию Ruby с некоторым флагом размера стека компилятора / компоновщика. Хвостовая рекурсия еще не реализована, и текущая поддержка Ruby для рекурсии не так уж велика. Какая бы классная и элегантная рекурсия ни была, вы можете подумать о том, чтобы справиться с ограничениями языка и написать свой код по-другому.
Этот вопрос и ответы на него относятся к Ruby 1.8.x, в котором использовался стек C. Ruby 1.9.x и более поздние версии используют виртуальную машину с собственным стеком. В Ruby 2.0.0 и более поздних версиях размер стека виртуальных машин можно контролировать с помощью RUBY_THREAD_VM_STACK_SIZE
переменная окружения.
Если вы уверены, что у вас нет ситуации бесконечной рекурсии, тогда ваш алгоритм, вероятно, не подходит для Ruby для его рекурсивного выполнения. Преобразовать алгоритм из рекурсии в другой вид стека довольно легко, и я предлагаю вам попробовать это. Вот как вы можете это сделать.
def recursive(params)
if some_conditions(params)
recursive(update_params(params))
end
end
recursive(starting_params)
превратится в
stack = [starting_params]
while !stack.empty?
current_params = stack.delete_at(0)
if some_conditions(current_params)
stack << update_params(current_params)
end
end
Юкихиро Мацумото пишет здесь
Ruby использует стек C, так что вам нужно использовать ulimit, чтобы указать ограничение глубины стека.
Просто была та же проблема, и это очень легко исправить в Linux или на Mac. Как сказано в других ответах, Ruby использует настройку системного стека. Вы можете легко изменить это на Mac и Linux, установив размер стека. Пример Фокса:
ulimit -s 20000
Подумайте о том, что происходит с кодом. Как упоминали другие авторы, можно взломать C-код интерпретатора. Тем не мение. В результате вы будете использовать больше оперативной памяти и не будете иметь никаких гарантий, что больше не будете загружать стек.
Действительно хорошим решением было бы придумать итеративный алгоритм для того, что вы пытаетесь сделать. Иногда запоминание может помочь, а иногда вы обнаружите, что не используете то, что помещаете в стек, и в этом случае вы можете заменить рекурсивные вызовы изменяемым состоянием.
Если вы новичок в этом, посмотрите на SICP здесь для некоторых идей...
Начиная с Ruby 1.9.2, вы можете включить оптимизацию хвостового вызова с помощью чего-то вроде:
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
Это позволит избежать SystemStackError
ошибка, если рекурсивный вызов находится в конце метода и только в методе. Конечно, этот пример приведет к бесконечному циклу. Вероятно, лучше всего отлаживать с использованием мелкой рекурсии (и без оптимизации), прежде чем идти после глубокой рекурсии.