参加这个Ruby(MRI)计划:
@n = 0
loop do
@n += 1
break if @n == 11_900
end
ps = `ps auxm | grep ruby`
puts "Memory usage: #{ps.split[5].to_i/1024.0} Mb"
使用内置loop
函数,它无限循环直到@n
等于11,900,然后打印Ruby在进程中使用的内存(我使用系统调用缺少一个好的,工作的内存分析器)
执行时,输出:Memory usage: 9.16796875 Mb
,或大约8.99 Mb和9.49 Mb之间的任何位置。
与此功能比较:
@n = 0
def lp
@n += 1
if @n == 11_900
return
end
lp
end
lp
使用自调用函数lp
,它会循环,直到@n
等于11,900(堆栈限制)。
执行时,输出:Memory usage: 10.20703125 Mb
,或大约9.96 Mb和10.41 Mb之间的任何位置。
为什么第一个程序占用的内存比第二个程序少几兆字节?内置循环与人工循环有何不同?
我能用有限的知识思考的唯一原因是loop
函数直接编译为C,而第二个程序在函数定义等方面有很多开销。
答案 0 :(得分:2)
你的第一个循环是迭代,你的第二个循环是递归。
换句话说,你一遍又一遍地调用相同的方法。这构建了一个巨大的堆栈。你可以通过在给定点引发异常来看到这一点:
@n = 0
def lp
@n += 1
raise if @n >= 10
lp
end
lp
给出:
loop.rb:4:in `lp': unhandled exception
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:5:in `lp'
from loop.rb:7:in `<main>'
您在这里使用一种称为tail recursion的特殊递归,它可以进行优化。
虽然默认情况下Ruby不会优化尾调用,但您可以手动enable it:
# tailcall.rb
tailcall = ARGV.include?('-t')
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: tailcall,
trace_instruction: false
}
RubyVM::InstructionSequence.new(<<-RUBY).eval
@n = 0
def lp
@n += 1
if @n == 11_900
return
end
lp
end
lp
RUBY
puts "Memory usage: #{`ps -o rss= -p #{$$}`.to_i} kB"
从没有优化的shell:
$ ruby --disable-all tailcall.rb
Memory usage: 4952 kB
并优化:
$ ruby --disable-all tailcall.rb -t
Memory usage: 3860 kB
使用尾调用优化使得递归算法与其迭代对应物一样具有内存效率。它还可以防止堆栈溢出。
答案 1 :(得分:0)
在递归方法中,必须为要调用的新函数分配额外的内存段。在迭代方法中,计算逻辑所需的所有值都已从设置中获得,因此使用的内存更少。