何时在 Ruby on Rails 中使用 memoization

Posted

技术标签:

【中文标题】何时在 Ruby on Rails 中使用 memoization【英文标题】:When to use memoization in Ruby on Rails 【发布时间】:2010-10-16 07:55:32 【问题描述】:

2008 年 7 月中旬,Memoization 被添加到 Rails 核心中。用法的演示是here。

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

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

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


在我自己进行了一些研究之后,我发现在 Rails 核心中使用记忆的次数非常多。

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

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

【问题讨论】:

【参考方案1】:

我认为许多 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 没有或不依赖任何副作用)

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

【讨论】:

当您说“即保证为相同的参数返回完全相同的值”时,这将如何应用于 Rails ActiveRecord 查询?举例来说,我记住了一个方法,该方法可以获取 City 模型中标记为活动的所有城市,例如:def self.active @active_cities =|| where("cities.active = (?)", true) end 当应用程序运行时管理员偶尔将新城市添加到数据库中时,我是否需要重新启动服务器以覆盖这个记忆的实例变量?还是在每次请求后都会销毁并重新创建已记忆的实例变量? 针对上面的回复:我现在意识到memoization只在单个请求的生命周期中持久化一个实例变量。这就是使我困惑。对于初学者,我建议您将这个关键点添加到您的定义中。【参考方案2】:

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

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

【讨论】:

构建 ActiveRecord 对象是否算作计算?据我了解,查询缓存只缓存mysql结果集而不是创建的对象(创建过程通常比查询本身花费更长的时间)。 据我所知,查询缓存存储的是实际的 ActiveRecord 对象。【参考方案3】:

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

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

【讨论】:

以上是关于何时在 Ruby on Rails 中使用 memoization的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ruby​​ on rails 的亚马逊 aws 服务器中使用 gem 执行 rake 任务?

Ruby on Rails- :symbols、@iVars 和“字符串”——哦,天哪!

Ruby on Rails - 如何在 Rails 方法中使用 HTML 坐标?

思考Ruby On Rails的底层代码(Ruby on Rails 開發秘籍 | Ruby on Rails 快速入門)

如何注销 Facebook - 在 Rails 应用程序中使用代码(Ruby on Rails Omniauth)

ruby 在Ruby on Rails中阻止别名