问题是,你有一个例程n+get_sum(n-1)
,当 n 有公因数 3 或 5 时,Ruby 会继续执行这个例程。这不能通过尾递归进行优化。Ruby 必须保留当前帧以记住当前帧,n
并且只有在get_sum(n-1)
计算时 Ruby 才能返回并丢弃该帧。
通常,堆栈空间很昂贵。操作系统为每个进程保留大约 2M(可能更多)的堆栈空间。这可以很快消耗掉。对于您的代码,(9999/3 + 9999/5)
递归调用消耗了所有堆栈空间。
如果要保持递归编程模式,则必须将方法调用的行为从递归(当前状态)更改为迭代。
def get_sum(n, sum = 0)
return sum if n < 1
if n % 3 == 0 || n % 5 == 0
get_sum(n - 1, sum + n)
else
get_sum(n - 1, sum)
end
end
然而,Ruby 的尾递归优化似乎并没有那么强大。对于上面的代码示例,有两个不同的get_sum()
尾调用。Ruby 不会对此进行优化。您必须将这两个调用重写为一个。
此外,为了使这项工作与RubyVM
您提供的调整一起工作,您必须在运行时加载方法定义。那是因为RubyVM
调整发生在运行时。不能将方法定义直接放在同一个文件中,否则编译时会解析方法,优化不生效。
您可以在调整后将其放入get_sum
单独的文件和require
该库中RubyVM
:
# file: test_lib.rb
def get_sum(n, sum = 0)
if n < 1
sum
else
get_sum(n - 1, sum + (n % 3 == 0 || n % 5 == 0 ? n : 0))
end
end
# eof
# file: test.rb
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
require_relative 'test_lib'
puts get_sum(999999)
或者,用于eval
在运行时评估 String 中的方法定义:
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
eval <<END
def get_sum(n, sum = 0)
if n < 1
sum
else
get_sum(n - 1, sum + (n % 3 == 0 || n % 5 == 0 ? n : 0))
end
end
END
puts get_sum(990000)