Ruby on Rails - 缓慢加载并在垃圾收集器中花费大量时间

时间:2013-01-20 23:17:58

标签: ruby-on-rails ruby garbage-collection profiling

我有一个很大的Rails应用程序,我正在寻求改善(令人沮丧)的表现。

使用ruby-prof运行对我没有多大帮助,我得到类似于此的输出(在生产模式下运行):

Thread ID: 9322800
Total: 1.607768
Sort by: self_time

 %self     total     self     wait    child    calls   name
 26.03      0.42     0.42     0.00     0.00     1657   Module#define_method 
  8.03      0.13     0.13     0.00     0.00      267   Set#initialize 
  4.41      0.07     0.07     0.00     0.00       44   PG::Result#values 
  4.28      0.07     0.07     0.00     0.00     1926   ActiveSupport::Callbacks::Callback#start 
  4.21      0.07     0.07     0.00     0.00    14835   Kernel#hash 
  4.13      0.08     0.07     0.00     0.01      469   Module#redefine_method 
  4.11      0.07     0.07     0.00     0.00       63  *<Class::ActiveRecord::Base>#with_scope 
  4.02      0.07     0.06     0.00     0.00      774   ActiveSupport::Callbacks::Callback#_compile_options 
  3.24      0.05     0.05     0.00     0.00       30   PG::Connection#async_exec 
  2.31      0.40     0.04     0.00     0.37     2130  *Module#class_eval 
  1.47      0.02     0.02     0.00     0.00        6   PG::Connection#unescape_bytea 
  1.03      0.05     0.02     0.00     0.03      390  *Array#select 

* indicates recursively called methods

我猜想也许它在垃圾收集器上花了很多时间,所以自从我运行REE后,我决定尝试使用GC.enable_stats来获取更多信息。我将以下内容添加到应用程序控制器中:

around_filter :enable_gc_stats

private

def enable_gc_stats
  GC.enable_stats

  begin
    yield
  ensure
    GC.disable_stats
    GC.clear_stats
  end
end

在我的机器上运行的一个相对较大的页面上,在生产模式下使用REE和瘦Web服务器(ruby-prof禁用,因为它使它慢一点)我得到:

Completed 200 OK in 1093ms (Views: 743.1ms | ActiveRecord: 139.2ms)

GC.collections: 11
GC.time: 666299 us 666.299 ms
GC.growth: 461 KB

GC.allocated_size: 152 MB
GC.num_allocations: 1,924,773
ObjectSpace.live_objects: 1,015,195
ObjectSpace.allocated_objects: 12,393,644

因此,对于一个耗时1093毫秒的页面,似乎差不多700毫秒花在了垃圾收集器上。以前有人有过这种问题吗?我意识到你无法帮助我的应用程序(它有很多宝石和东西) - 但是有没有技术或工具可以更好地了解为什么会产生如此多的垃圾?

非常感谢任何想法!

1 个答案:

答案 0 :(得分:4)

您的rails日志显示大部分时间(75%)花费在视图代码中。

您的个人资料报告显示了三个明显的热点:自助时间为Module#define_method,总时间为Module#class_evalSet#initialize

define_methodclass_eval表示可能有很多动态代码执行似乎对我来说太过分了 - 通常您希望尽早生成该代码并重复使用它而不是重复生成它。对于过多的对象分配问题,几乎肯定是问题的一部分。生成图形报告而不是平面报告应该可以帮助您找到落入这些昂贵路径的父方法,并且可以为您提供指向优化位置的指针。

Set#initialize可能是您的代码需要执行的真实工件,或者它可能表示某些重要的Set[...]Set::new集合创建调用内联可以完成一次并分配给常量或实例/类var以供重用。

ruby​​-prof是好的,但您可能还想尝试使用perftools.rb轻松连接到机架导轨的rack-perftools_profiler。 perftools具有一些增强的可视化工具,可以更容易理解热执行路径。

由于您正在运行REE并且广泛的对象分配(以及垃圾收集)是一个问题,您可以尝试memprof来了解所有这些分配的来源和位置。

如果找不到减少分配对象数量的路径,则可以通过tuning the GC以更大的进程内存大小为代价来减轻GC负担,从而预先分配足够大的堆以容纳典型的堆请求的分配要求。 Unicorn为out of band GC提供机架模块。您可能能够调整此模块的方法以使用精简版并将所有GC时间移至请求之间 - 您仍将支付cpu成本,但至少您不会延迟响应垃圾回收。