24

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

我还没有找到任何关于何时应该记住方法以及每个方法的性能影响的好例子。例如,这篇博文建议,通常根本不应该使用记忆。

对于可能对性能产生巨大影响的东西,似乎很少有资源可以提供简单的教程。

有没有人在他们自己的项目中看到过记忆化?哪些因素会让你考虑记忆一个方法?


在我自己做了一些更多的研究之后,我发现在 Rails 核心中使用了很多次记忆。

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

这种用法似乎与上述博客文章的发现背道而驰,即发现记忆化会损害性能。

4

3 回答 3

34

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

如果我不会应用记忆

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

还有其他不适合记忆的情况,例如问题中的那个和上面的答案,但我认为这三个不是那么明显。

最后一项可能是最重要的:memoization 根据方法的参数缓存结果,如果方法看起来像这样,它就不能被 memoized:

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没有或依赖于任何副作用)

诀窍是从方法中提取一个纯函数,然后对其应用记忆化。

于 2009-12-04T14:15:04.707 回答
10

当一个方法从多个表中获取数据,并在返回结果对象之前执行一些计算,并且该方法在请求中多次出现时,记忆化可能是有意义的。

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

于 2009-03-30T08:22:56.847 回答
2

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

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

于 2011-04-04T21:46:14.743 回答