如何测量每个gem在初始化时需要多少内存?

时间:2022-01-11 00:19:31

I have a Rails 2.3.10 app with bundler. At startup the memory footprint is quite big (300MB in development mode).

我有一个与bundler合作的Rails 2.3.10应用。在启动时,内存占用相当大(在开发模式中为300MB)。

I would like to see how much memory each gem takes on startup.

我想看看每个gem在启动时需要多少内存。

3 个解决方案

#1


19  

We had a problem in which our basic Rails app, with no traffic or requests, had a footprint of ~140MB on startup.

我们遇到了一个问题,我们的基本Rails应用程序(没有流量或请求)在启动时占用了~140MB的内存。

We used the following approach to trace the memory requirement of each gem specified in the Gemfile of our app, without having to try to patch bundler.

我们使用以下方法跟踪应用程序Gemfile中指定的每个gem的内存需求,而无需尝试对bundler进行补丁。

  1. using rails new myappname generate a new empty rails app
  2. 使用rails new appmyname生成一个新的空rails应用程序
  3. Copy the Gemfile from the main project to this new rails project
  4. 将Gemfile从主项目复制到这个新rails项目
  5. run bundle install and then rails server to ensure it is possible to boot up the rails server and that any basic configurations required are loaded
  6. 运行bundle install,然后运行rails服务器,以确保能够启动rails服务器,并加载所需的任何基本配置
  7. Open up the Gemfile and with the exception of the specification for the rails gem, append require: false at the end of each line. Ensure that any other gems that are specified with one name but required using :require => 'othergemname' are using the older ruby Hash notation so that the pattern match below will catch it.
  8. 打开Gemfile,除了rails gem的规范之外,在每一行末尾附加require: false。确保用一个名称指定但使用:require => 'othergemname'的任何其他gem都使用旧的ruby哈希表示法,以便下面的模式匹配将捕获它。
  9. Run bundle install again to regenerate the Gemfile.lock
  10. 再次运行bundle install来重新生成Gemfile.lock
  11. Create the following script which will use manually require each gem specified in the Gemfile and log the system memory consumed by the rails process before and after.

    创建以下脚本,该脚本将使用手工要求Gemfile中指定的每个gem,并记录rails进程在前后消耗的系统内存。

    # require_and_profile.rb
    def require_and_profile(gemname = nil)
      unless gemname
        puts "%-20s: %10s | %10s" % ['gem','increment','total']
        return
      end
      # This is how to get memory of calling process in OS X, check host OS for variants
      memory_usage = `ps -o rss= -p #{Process.pid}`.to_i / 1024.0
      require gemname
      puts "%-20s: %10.2f | %10.2f" % [ gemname, (`ps -o rss= -p #{Process.pid}`.to_i / 1024.0 - memory_usage), (`ps -o rss= -p #{Process.pid}`.to_i / 1024.0)]
    end
    
    pattern = /^[^#]*gem[ ]*['"]([^,'"]*)['"][ ,~>0-9\.'"]*(:require[ => ]*['"]([^'"]*)['"][, ])?/
    
    require_and_profile
    File.open('Gemfile').each do |line|
      if line.match(pattern)
      if line.match(pattern)[3]
        require_and_profile line.match(pattern)[3]
      else
          require_and_profile line.match(pattern)[1]
        end
      end
    end
    
  12. Run rails c

    运行rails c

  13. load 'require_and_profile.rb'
  14. 负载的require_and_profile.rb
  15. The output shows how much (in MB) each gem adds to the base app footprint (increment) and what the total footprint is after inclusion of the gem (total).
  16. 输出显示了每个gem为基本应用程序增加了多少(以MB为单位)内存占用(增量),以及在包含了gem之后的总内存占用(总量)。

This helped us identify for example, that we'd been requiring asset-sync in our boot when we only needed it in the :asset group. We do find that on different boot-ups the memory footprint of each gem is not exactly the same, but running it a few times does show you the patterns of which ones are the memory-hungry gems.

例如,这帮助我们确定,当我们只需要资产组中的:asset group时,我们在启动时需要资产同步。我们确实发现,在不同的启动时,每个gem的内存足迹并不是完全相同的,但是运行它几次确实可以向您展示哪些模式是需要内存的宝石。

#2


9  

There is an easier way to do this now with the derailed gem:

现在有一种更简单的方法来实现这一点:

add to your gemfile:

添加到您的gemfile:

gem 'derailed', group: :development

then on the command line from your apps root:

然后在应用程序根目录的命令行:

bundle exec derailed bundle:mem

This will print out how much memory each gem takes as it's included.

这将打印出每个宝石包含多少内存。

#3


0  

I would do it as follows:

我将这样做:

  • Find the place within bundler where all the gems are required.
  • 找到邦德勒里面需要所有宝石的地方。
  • after each gem is required, get the current object count (c = 0; ObjectSpace.each_object { c += 1 })
  • 在需要每个gem之后,获取当前对象计数(c = 0;ObjectSpace。每个对象{c += 1})

This way you'll see which gem causes the most objects being instantiated and therefore indirectly causes most memory usage.

通过这种方式,您将看到哪些gem导致了大多数对象被实例化,从而间接导致了大多数内存使用。

Two caveats though:

两个问题:

  • memory usage is not really linear to number of objects, but you should get a good enough estimate about which gems are the worst offenders
  • 内存使用与对象的数量并不是线性的,但是您应该对哪个gem是最坏的攻击者有一个足够好的估计
  • as already pointed out: gems might not load all their code when being required, so a gem might causes more memory usage later on when more code is loaded...
  • 如前所述:gems可能不会在需要时加载所有代码,因此当加载更多代码时,gem可能会导致更多的内存使用……

#1


19  

We had a problem in which our basic Rails app, with no traffic or requests, had a footprint of ~140MB on startup.

我们遇到了一个问题,我们的基本Rails应用程序(没有流量或请求)在启动时占用了~140MB的内存。

We used the following approach to trace the memory requirement of each gem specified in the Gemfile of our app, without having to try to patch bundler.

我们使用以下方法跟踪应用程序Gemfile中指定的每个gem的内存需求,而无需尝试对bundler进行补丁。

  1. using rails new myappname generate a new empty rails app
  2. 使用rails new appmyname生成一个新的空rails应用程序
  3. Copy the Gemfile from the main project to this new rails project
  4. 将Gemfile从主项目复制到这个新rails项目
  5. run bundle install and then rails server to ensure it is possible to boot up the rails server and that any basic configurations required are loaded
  6. 运行bundle install,然后运行rails服务器,以确保能够启动rails服务器,并加载所需的任何基本配置
  7. Open up the Gemfile and with the exception of the specification for the rails gem, append require: false at the end of each line. Ensure that any other gems that are specified with one name but required using :require => 'othergemname' are using the older ruby Hash notation so that the pattern match below will catch it.
  8. 打开Gemfile,除了rails gem的规范之外,在每一行末尾附加require: false。确保用一个名称指定但使用:require => 'othergemname'的任何其他gem都使用旧的ruby哈希表示法,以便下面的模式匹配将捕获它。
  9. Run bundle install again to regenerate the Gemfile.lock
  10. 再次运行bundle install来重新生成Gemfile.lock
  11. Create the following script which will use manually require each gem specified in the Gemfile and log the system memory consumed by the rails process before and after.

    创建以下脚本,该脚本将使用手工要求Gemfile中指定的每个gem,并记录rails进程在前后消耗的系统内存。

    # require_and_profile.rb
    def require_and_profile(gemname = nil)
      unless gemname
        puts "%-20s: %10s | %10s" % ['gem','increment','total']
        return
      end
      # This is how to get memory of calling process in OS X, check host OS for variants
      memory_usage = `ps -o rss= -p #{Process.pid}`.to_i / 1024.0
      require gemname
      puts "%-20s: %10.2f | %10.2f" % [ gemname, (`ps -o rss= -p #{Process.pid}`.to_i / 1024.0 - memory_usage), (`ps -o rss= -p #{Process.pid}`.to_i / 1024.0)]
    end
    
    pattern = /^[^#]*gem[ ]*['"]([^,'"]*)['"][ ,~>0-9\.'"]*(:require[ => ]*['"]([^'"]*)['"][, ])?/
    
    require_and_profile
    File.open('Gemfile').each do |line|
      if line.match(pattern)
      if line.match(pattern)[3]
        require_and_profile line.match(pattern)[3]
      else
          require_and_profile line.match(pattern)[1]
        end
      end
    end
    
  12. Run rails c

    运行rails c

  13. load 'require_and_profile.rb'
  14. 负载的require_and_profile.rb
  15. The output shows how much (in MB) each gem adds to the base app footprint (increment) and what the total footprint is after inclusion of the gem (total).
  16. 输出显示了每个gem为基本应用程序增加了多少(以MB为单位)内存占用(增量),以及在包含了gem之后的总内存占用(总量)。

This helped us identify for example, that we'd been requiring asset-sync in our boot when we only needed it in the :asset group. We do find that on different boot-ups the memory footprint of each gem is not exactly the same, but running it a few times does show you the patterns of which ones are the memory-hungry gems.

例如,这帮助我们确定,当我们只需要资产组中的:asset group时,我们在启动时需要资产同步。我们确实发现,在不同的启动时,每个gem的内存足迹并不是完全相同的,但是运行它几次确实可以向您展示哪些模式是需要内存的宝石。

#2


9  

There is an easier way to do this now with the derailed gem:

现在有一种更简单的方法来实现这一点:

add to your gemfile:

添加到您的gemfile:

gem 'derailed', group: :development

then on the command line from your apps root:

然后在应用程序根目录的命令行:

bundle exec derailed bundle:mem

This will print out how much memory each gem takes as it's included.

这将打印出每个宝石包含多少内存。

#3


0  

I would do it as follows:

我将这样做:

  • Find the place within bundler where all the gems are required.
  • 找到邦德勒里面需要所有宝石的地方。
  • after each gem is required, get the current object count (c = 0; ObjectSpace.each_object { c += 1 })
  • 在需要每个gem之后,获取当前对象计数(c = 0;ObjectSpace。每个对象{c += 1})

This way you'll see which gem causes the most objects being instantiated and therefore indirectly causes most memory usage.

通过这种方式,您将看到哪些gem导致了大多数对象被实例化,从而间接导致了大多数内存使用。

Two caveats though:

两个问题:

  • memory usage is not really linear to number of objects, but you should get a good enough estimate about which gems are the worst offenders
  • 内存使用与对象的数量并不是线性的,但是您应该对哪个gem是最坏的攻击者有一个足够好的估计
  • as already pointed out: gems might not load all their code when being required, so a gem might causes more memory usage later on when more code is loaded...
  • 如前所述:gems可能不会在需要时加载所有代码,因此当加载更多代码时,gem可能会导致更多的内存使用……