Ruby内存使用:有条理的循环与内置循环

时间:2016-10-19 04:28:10

标签: ruby loops memory

参加这个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,而第二个程序在函数定义等方面有很多开销。

2 个答案:

答案 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)

在递归方法中,必须为要调用的新函数分配额外的内存段。在迭代方法中,计算逻辑所需的所有值都已从设置中获得,因此使用的内存更少。