我看到了三种不同的方式来编写斐波那契函数的递归形式:数学内联,数学内联结果缓存和一个使用尾递归。我知道使用memoization将O(N)算法转换为O(1),缓存后的答案。但我无法理解tail call优化如何能够提供如此大的帮助。我的印象是它可能会阻止一些副本或类似的东西。它几乎和O(1)一样快。 Ruby在做什么让这么快?是尾巴呼叫优化所有解释此性能差异
这里是数学内联慢天真的实现。这显然是最慢的O(N)时间,然后当在O(N^2)时间的显示中循环时。
puts Benchmark.measure {
# Calculate the nth Fibonacci number, f(n).
def fibo (n)
if n <= 1
return n
else
value = fibo(n-1) + fibo(n-2)
return value
end
end
# Display the Fibonacci sequence.
(1..40).each do |number|
puts "fibo(#{number}) = #{fibo(number)}"
end
}
时报红宝石1.9.3:55.989000 0.000000 55.989000(55.990000)
时报的JRuby 1.7.9:51.629000 0.000000 51.629000(51.629000)
源(http://rayhightower.com/blog/2014/04/12/recursion-and-memoization/?utm_source=rubyweekly)
这里是一个memoizes答案版本,这很明显为什么这对我来说很快。一旦它已经做任何以下请求运行在O数学(1)的时间,所以当包括在一个循环中它它仍然运行在O(N)时间在最坏的情况下:
puts Benchmark.measure {
# Fibonacci numbers WITH memoization.
# Initialize the memoization array.
@scratchpad = []
@max_fibo_size = 50
([email protected]_fibo_size).each do |i|
@scratchpad[i] = :notcalculated
end
# Calculate the nth Fibonacci number, f(n).
def fibo (n)
if n > @max_fibo_size
return "n must be #{@max_fibo_size} or less."
elsif n <= 1
return n
elsif @scratchpad[n] != :notcalculated
return @scratchpad[n]
else
@scratchpad[n] = fibo(n-1) + fibo(n-2)
return @scratchpad[n]
end
end
# Display the Fibonacci sequence.
(1..40).each { |number|
puts "fibo(#{number}) = #{fibo(number)}"
}
}
时报红宝石1.9.3 :0.000000 0.000000 0.000000(0.025000)
时报的JRuby 1.7.9:0.027000 0.000000 0.027000(0.028000)
源(http://rayhightower.com/blog/2014/04/12/recursion-and-memoization/?utm_source=rubyweekly)
的这个版本尾部调用递归版本的运行几乎瞬间:
puts Benchmark.measure {
# Calculate the nth Fibonacci number, f(n). Using invariants
def fibo_tr(n, acc1, acc2)
if n == 0
0
elsif n < 2
acc2
else
return fibo_tr(n - 1, acc2, acc2 + acc1)
end
end
def fibo (n)
fibo_tr(n, 0, 1)
end
# Display the Fibonacci sequence.
(1..50).each do |number|
puts "fibo(#{number}) = #{fibo(number)}"
end
}
时报红宝石1.9.3:0.000000 0.000000 0.000000(0.021000)
时报的JRuby 1.7.9:0.041000 0.000000 0.041000(0.041000)
源(https://gist.github.com/mvidaurre/11006570)
我不能相信我错过了!我读过它,它根本不会发生在我身上,那就是它在做什么。 – Sqeaky
“Ruby不会做任何事情来优化尾部呼叫” - 但是,它也不会对*优化尾部呼叫做任何事情。 (与Python不同,TCO被明确禁止)。对于Ruby实现来说,执行TCO是完全合法的,有些则是这样。 YARV可以执行TCO,并且在执行TCO的JVM(如IBM J9)上运行JRuby可能会或可能不会导致某些方法获得TCO'd。 –