何时在Ruby on Rails中使用memoization

2008年7月中旬,Memoization被添加到Rails核心。 这里使用的演示。

我还没有找到任何关于何时应该记忆方法的好例子,以及每个方法的性能影响。 例如, 这篇博客文章表明,通常不应该使用备忘录。

对于可能具有巨大性能影响的东西,似乎没有什么资源可以提供简单的教程。

有没有人看到他们自己的项目中使用的memoization? 哪些因素会让您考虑记忆方法?


在我自己做了一些研究之后,我发现在Rails核心内部使用了很多次memoization。

这是一个例子: http : //github.com/rails/rails/blob/1182658e767d2db4a46faed35f0b1075c5dd9a88/actionpack/lib/action_view/template.rb 。

这种用法似乎违背了上述博客文章的结果,发现备忘录可能会损害性能。

我认为许多Rails开发人员并不完全了解memoization的作用及其工作原理。 我已经看到它应用于返回延迟加载集合的方法(如Sequel数据集),或应用于不带参数但基于实例变量计算内容的方法。 在第一种情况下,memoization只是开销,而在第二种情况下,它是令人讨厌的,难以追踪错误的来源。

如果,我不会申请记忆

  • 返回的值只是计算起来有点贵。 它必须非常昂贵,而且不能进一步优化,因为值得记忆。
  • 返回的值是或者可能是延迟加载的
  • 该方法不是纯函数,即保证为相同的参数返回完全相同的值 – 并且仅使用参数来完成它的工作或其他纯函数。 使用实例变量或调用方法反过来使用实例变量意味着该方法可以为相同的参数返回不同的结果。

在其他情况下,备忘录也不合适,例如问题中的那个和上面的答案,但这些是我认为不那么明显的三个。

最后一项可能是最重要的:memoization根据方法的参数缓存结果,如果方法看起来像这样,则无法记忆:

def unmemoizable1(name) "%s was here %s" % name, Time.now.strftime('%Y-%m-%d') end def unmemoizable2 find_by_shoe_size(@size) end 

但是,两者都可以重写以利用记忆(尽管在这两种情况下显然不应该出于其他原因):

 def unmemoizable1(name) memoizable1(name, Time.now.strftime('%Y-%m-%d')) end def memoizable1(name, time) "#{name} was here #{time}" end memoize :memoizable1 def unmemoizable2 memoizable2(@size) end def memoizable2(size) find_by_shoe_size(size) end memoize :memoizable2 

(假设find_by_shoe_size没有或依赖于任何副作用)

诀窍是从方法中提取纯函数并将memoization应用于该方法。

当一个方法从多个表中获取数据,并在返回结果对象之前执行某些计算,并且此方法在请求中多次执行时,memoization可能有意义。

请记住,查询缓存也是活动的,因此只有memoize方法执行in-Ruby计算,而不是纯数据库提取。

也许我的经验是不使用memoize时的一个很好的例子。 在我的订单模型中,我记住了两个简单的计算结果,即Order#subtotal,Order#tax; 以及模型对象,即Order#most_recent_credit_card_used。 在后者中,当记住返回CreditCard对象的方法时,在尝试更新memoized对象上的属性时,我会得到“冻结哈希”错误。 订单号most_recent_credit_card_used.frozen? 当方法被记忆时返回true,这当然不是我想要的。

我的收获很简单:使用memoize进行返回简单数据类型(整数,浮点数等)的昂贵操作,但在返回 ActiveRecord模型等复杂对象时请勿使用memoize 。 如果您打算在内存中更新这些对象。