【问题标题】:ruby memoization, efficiency红宝石记忆,效率
【发布时间】:2014-12-21 02:10:03
【问题描述】:

这个问题主要是关于 Ruby 内部的,速度可以用一个简单的基准来衡量。

在 ruby​​ 中记忆返回值的最有效方法是什么?

我总是记住以下值:

def method
  @value ||= calculate_value
end

但由于它在技术上扩展为:

@value = @value || calculate_value

我想知道每次重新执行相同分配的效率。

这样会更好吗?

def method
  @value ? @value : (@value = calculate_value)
end

另外,它在不同的解释器中会发生变化吗?核磁共振、Rubinius 等。

【问题讨论】:

  • 如果 calculate_value 很昂贵并且它可以返回 false 或 nil,您必须考虑类似defined? @value ? @value : (@value = calculate_value)
  • 一个有趣的问题,我很高兴知道他的答案。如果有帮助,请不要忘记选择答案。

标签: ruby memoization


【解决方案1】:

你的例子

@value ||= calculate_value

等价于

@value || @value = calculate_value

并且等价于

@value = @value || calculate_value

因此答案是:非常高效。不是每次都重新分配。

【讨论】:

  • 你能以某种方式证明它吗?例如。 ruby 源代码引号还是什么?简单的基准测试表明 (value ||= calculate_value) 的工作速度比 (value || value = calculate_value) 慢 30%。
  • 当然,您可能想阅读 Peter Cooper (rubyinside.com/…) 的这篇文章或 Rubyforum (ruby-forum.com/topic/151660) 中的一些帖子
  • 恐怕你错了。 a += 1 等价于 a = a + 1。相同的规则适用于其他运算符:a ||= 1 扩展为 a = a || 1
  • @tompave: 不要将 [op]= 与任何与 ||= 或 &&= 相关的内容混淆。它们是完全不同的想法,实现方式也完全不同[ly]。(另请参阅:rubyinside.com/…
  • @tompave,请注意第 1 页“||= 成语”框中的最后一段。 97:“缩写赋值运算符 ||= 的行为与此处显示的扩展略有不同。如果 ||= 的左值不是 nil 或 false,则实际上不执行任何赋值。如果左值是属性或数组元素,则 setter不会调用执行赋值的方法。”
【解决方案2】:

@spickermann,@tompave,很抱歉将其发布为答案,但我需要代码格式来发布不同用例的基准测试结果。看起来显式方式是最快的@a || @a = calculated_value

> n = 10000000;
> a = 1;
> puts Benchmark.measure { n.times { a ||= 1 } }
  0.570000   0.000000   0.570000 (  0.569977)
> puts Benchmark.measure { n.times { a ? a : a = 1 } }
  0.560000   0.000000   0.560000 (  0.562855)
> puts Benchmark.measure { n.times { a || a = 1 } }
  0.530000   0.000000   0.530000 (  0.532715)
> @a = 1;
> puts Benchmark.measure { n.times { @a ||= 1 } }
  0.870000   0.000000   0.870000 (  0.869489)
> puts Benchmark.measure { n.times { @a ? @a : @a = 1 } }
  0.670000   0.000000   0.670000 (  0.668910)
> puts Benchmark.measure { n.times { @a || @a = 1 } }
  0.610000   0.000000   0.610000 (  0.613978)

ruby 2.1.2p95(2014-05-08 修订版 45877)[x86_64-darwin13.0]

从结果的角度来看,所有 3 个符号的行为都相同,但是从性能的角度来看,最后一个是禁食的。不幸的是,这都是经验性的,我无法在这里链接任何 ruby​​ 源代码。

【讨论】:

  • 对不起,我的评论不准确。您只考虑一种替代方案。
【解决方案3】:

我认为||=... = ... || ... 之间的任何区别都应该是细微的。

也许,更有效的方法是在设置值时切换方法。这种切换只发生一次,从那时起,方法调用就变成了对变量的引用,所以应该很快,而且随着方法调用次数的增加,优势也会增加。

def method
  alias method memoized_value
  @value ||= calculate_value
end

def memoized_value; @value end

这假定calculate_value 总是返回一个真值,并且代码中没有其他部分修改该值,因此一旦调用method,变量的值仍然是真值。

【讨论】:

  • 很有趣,但例如方法 alias 会为每个实例至少调用一次,而 IIRC 它会清除 Ruby 的方法缓存。
  • @tompave 我打算将这两种方法配对。你需要这些对尽可能多的变量来做记忆。
  • 是的,但是每次创建 alias 时它都会清除方法缓存。
猜你喜欢
  • 1970-01-01
  • 2012-11-09
  • 2019-05-16
  • 2016-02-15
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多