Ruby真的很需要内存——但也值得每一点。
您如何保持低内存使用率?您是否避免使用大字符串并使用较小的数组/散列,或者您不必担心并让垃圾收集器完成这项工作?
编辑:我在这里找到了一篇关于这个主题的好文章- 旧但仍然很有趣。
我发现 Phusion 的 Ruby 企业版(主线 Ruby 的一个分支,具有很大改进的垃圾收集)在内存使用方面产生了巨大的差异......另外,它们使它非常容易安装(如果你想删除,找到需要)。
您可以在他们的网站上找到更多信息并下载。
我真的不认为这很重要。降低代码的可读性以减少内存消耗是您只有在需要时才应该做的事情。所谓需要,我的意思是有一个特定的性能配置文件和 特定指标,表明任何更改都将解决问题。
如果您的应用程序内存将成为限制因素,那么 Ruby 可能不是最佳选择。也就是说,我发现我的 Rails 应用程序通常每个 Mongrel 实例消耗大约 40-60mb 的 RAM。在事情的计划,这不是很多。
您也许可以使用 JRuby 在 JVM 上运行您的应用程序 - Ruby VM 目前在内存管理和垃圾收集方面不如 JVM 先进。1.9 版本增加了许多改进,并且还有其他 VM 正在开发中。
Ruby 开发人员非常幸运,因为他们不必自己管理内存。
请注意,ruby 分配对象,例如像这样简单的东西
100.times{ 'foo' }
分配 100 个字符串对象(字符串是可变的,每个版本都需要自己的内存分配)。
确保如果您正在使用分配大量对象的库,则其他替代方案不可用,并且您的选择值得支付垃圾收集器的成本。(您可能没有很多请求/秒,或者每个请求可能不关心几十毫秒)。
例如,创建一个哈希对象实际上分配的不仅仅是一个对象
{'joe' => 'male', 'jane' => 'female'}
不分配 1 个对象,而是 7 个。(一个哈希,4 个字符串 + 2 个键字符串)
如果您可以使用符号键,因为它们不会被垃圾收集。但是,由于它们不会被垃圾收集,因此您要确保不使用完全动态的键,例如将用户名转换为符号,否则您将“泄漏”内存。
示例:在您的应用程序的某处,您将 to_sym 应用于用户名,例如:
hash[current_user.name.to_sym] = something
当你有数百个用户时,这可能没问题,但如果你有 100 万个用户会发生什么?以下是数字:
ruby-1.9.2-head >
# Current memory usage : 6608K
# Now, add one million randomly generated short symbols
ruby-1.9.2-head > 1000000.times { (Time.now.to_f.to_s).to_sym }
# Current memory usage : 153M, even after a Garbage collector run.
# Now, imagine if symbols are just 20x longer than that ?
ruby-1.9.2-head > 1000000.times { (Time.now.to_f.to_s * 20).to_sym }
# Current memory usage : 501M
请注意,之前不要在符号中转换非受控参数或检查参数,这很容易导致拒绝服务。
还要记住避免嵌套循环超过三层,因为它使维护变得困难。将循环和函数的嵌套限制在三个或更少级别是保持代码高性能的一个很好的经验法则。
以下是一些相关的链接:
我不是 ruby 开发人员,但我认为某些技术和方法适用于任何语言:
使用适合作业的最小大小变量
不使用时销毁并关闭变量和连接
但是,如果您有一个需要多次使用的对象,请考虑将其保持在范围内 任何带有大字符串操作的循环 dp 上的工作较小的字符串,然后附加到较大的字符串
使用体面的(try catch finally)错误处理来确保对象和连接被关闭
处理数据集时只返回必要的最小值
除了在极端情况下,无需担心内存使用情况。您尝试减少内存使用所花费的时间将购买大量千兆字节。
看看Small Memory Software - Patterns for Systems with Limited Memory。你没有指定什么样的内存限制,但我假设是 RAM。虽然不是 Ruby 特有的,但我想你会在本书中找到一些有用的想法——模式涵盖 RAM、ROM 和二级存储,并分为小型数据结构、内存分配、压缩、二级存储和小型数据结构等主要技术。建筑学。
我们所拥有的唯一真正值得担心的是 RMagick。
解决方案是确保您使用的是 RMagick 版本 2,并在使用完Image#destroy!
图像后调用
避免这样的代码:
str = ''
veryLargeArray.each do |foo|
str += foo
# but str << foo is fine (read update below)
end
这会将每个中间字符串值创建为 String 对象,然后在下一次迭代中删除其唯一引用。这会用大量越来越长的字符串来垃圾内存,这些字符串必须被垃圾收集。
相反,使用Array#join
:
str = veryLargeArray.join('')
这在 C 中非常有效地实现,并且不会产生 String 创建开销。
更新:乔纳斯在下面的评论中是正确的。我的警告适用于+=
但不适用<<
。
我是 Ruby 的新手,但到目前为止,我还没有发现有必要在这方面做任何特别的事情(也就是说,超出了我作为程序员通常倾向于做的事情)。也许这是因为内存比认真优化它所花费的时间要便宜(我的 Ruby 代码在具有 4-12 GB RAM 的机器上运行)。这也可能是因为我使用它的工作不是长期运行的(即它将取决于您的应用程序)。
我正在使用 Python,但我想这些策略是相似的。
我尝试使用小函数/方法,以便当您返回调用者时,局部变量会自动被垃圾收集。
在较大的函数/方法中,当不再需要大型临时对象(如列表)时,我会显式删除它们。尽早关闭资源也可能有所帮助。
要记住的是对象的生命周期。如果你的对象没有被传递那么多,垃圾收集器最终会启动并释放它们。但是,如果您继续引用它们,垃圾收集器可能需要一些周期才能释放它们。在 Ruby 1.8 中尤其如此,其中垃圾收集器使用的标记和清除技术的实现很差。
当您尝试应用一些“设计模式”(如装饰器)将对象长时间保存在内存中时,您可能会遇到这种情况。孤立地尝试示例时可能并不明显,但在同时创建数千个对象的实际应用程序中,内存增长的成本将是巨大的。
如果可能,请使用数组而不是其他数据结构。当整数可以使用时,尽量不要使用浮点数。
使用 gem/library 方法时要小心。它们可能没有经过内存优化。例如,Ruby PG::Result 类有一个未优化的方法“values”。它将使用大量额外的内存。我还没有报告这个。
将 malloc(3) 实现替换为jemalloc将立即将内存消耗减少多达 30%。我创建了“jemalloc”gem 来立即实现这一目标。
我尽量保持数组、列表和数据集尽可能小。单个对象无关紧要,因为在大多数现代语言中创建和垃圾收集都非常快。
如果您必须从数据库中读取某种巨大的数据集,请确保以正向/仅方式读取并进行少量处理,而不是先将所有内容加载到内存中。
不要使用很多符号,它们会保留在内存中,直到进程被杀死。这是因为符号永远不会被垃圾收集。