LoginSignup
5
0

Rubyのメモリを調査したい

Last updated at Posted at 2023-12-06

Ruby のメモリの話

メモリサイズを計測したりしたい

ps を使う

rss = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
vsz = `ps -o vsz= -p #{Process.pid}`.to_i * 0.001
puts "Process: #{Process.pid}: RSS = #{rss} MB, VSZ = #{vsz} MB"

オブジェクト数を使う

puts "#{ObjectSpace.memsize_of_all * 0.001 * 0.001 } MB"

試してみる

ps.rb
#!/usr/bin/env ruby

require 'objspace'
require 'benchmark'

def mem_by_ps
  rss = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
  vsz = `ps -o vsz= -p #{Process.pid}`.to_i * 0.001
  puts "Process: #{Process.pid}: RSS = #{sprintf('%0.3f', rss)} MB, VSZ = #{sprintf('%0.3f', vsz)} MB"
  [rss, vsz]
end

def mem_by_obj
  obj = ObjectSpace.memsize_of_all * 0.001 * 0.001
  puts "objspace: #{sprintf('%0.6f', obj)} MB"
  obj
end

def mem_wrap
  puts "Ruby version: #{RUBY_VERSION}"

  before = {
    rss: 0,
    vsz: 0,
    obj: 0,
  }
  after = {
    rss: 0,
    vsz: 0,
    obj: 0,
  }

  before[:rss], before[:vsz] = mem_by_ps
  before[:obj] = mem_by_obj


  bm = Benchmark.measure { yield }

  after[:rss], after[:vsz] = mem_by_ps
  after[:obj] = mem_by_obj

  puts "RSS: #{sprintf('%.3f', after[:rss] - before[:rss])} MB"
  puts "VSZ: #{sprintf('%.3f', after[:vsz] - before[:vsz])} MB"
  puts "OBJ: #{sprintf('%.3f', after[:obj] - before[:obj])} MB"

  puts Benchmark::CAPTION
  puts bm
end

if File.basename($0) == 'ps.rb'
  mem_wrap do
    a = Array.new(100_000_000, 0)
  end
end
$ rbenv exec ruby ps.rb
Ruby version: 2.7.4
Process: 8353: RSS = 12.984 MB, VSZ = 34651.104 MB
objspace: 2.238455 MB
Process: 8353: RSS = 794.288 MB, VSZ = 35432.356 MB
objspace: 802.231552 MB
RSS: 781.304 MB
VSZ: 781.252 MB
OBJ: 799.993 MB
      user     system      total        real
  0.188704   0.225386   0.414090 (  0.416024)

例えば,gsub!を見てみる

gsub.rb
#!/usr/bin/env ruby

require './ps'

str = "a" * ARGV[0].to_i

mem_wrap do
  str.gsub!(/a/, 'b')
end
$ make gsub1
for v in 2.7.4 2.7.6 3.0.2 3.0.3 3.0.4; do echo $v; RBENV_VERSION=$v time rbenv exec ruby gsub.rb 10000000; done
2.7.4
Ruby version: 2.7.4
Process: 71057: RSS = 23.092 MB, VSZ = 34414.088 MB
objspace: 12.349771 MB
Process: 71057: RSS = 32.900 MB, VSZ = 34423.856 MB
objspace: 22.215229 MB
RSS: 9.808 MB
VSZ: 9.768 MB
OBJ: 9.865 MB
        1.85 real         1.61 user         0.10 sys
2.7.6
Ruby version: 2.7.6
Process: 71163: RSS = 22.772 MB, VSZ = 34684.376 MB
objspace: 12.350140 MB
Process: 71163: RSS = 32.580 MB, VSZ = 34694.144 MB
objspace: 22.215430 MB
RSS: 9.808 MB
VSZ: 9.768 MB
OBJ: 9.865 MB
        1.85 real         1.58 user         0.11 sys
3.0.2
Ruby version: 3.0.2
Process: 71183: RSS = 22.916 MB, VSZ = 34414.440 MB
objspace: 11.647444 MB
Process: 71183: RSS = 32.724 MB, VSZ = 34424.208 MB
objspace: 21.319548 MB
RSS: 9.808 MB
VSZ: 9.768 MB
OBJ: 9.672 MB
        1.81 real         1.56 user         0.10 sys
3.0.3
Ruby version: 3.0.3
Process: 71198: RSS = 22.972 MB, VSZ = 34413.416 MB
objspace: 11.524093 MB
Process: 71198: RSS = 43.188 MB, VSZ = 34428.304 MB
objspace: 21.925663 MB
RSS: 20.216 MB
VSZ: 14.888 MB
OBJ: 10.402 MB
        9.35 real         8.86 user         0.26 sys
3.0.4
Ruby version: 3.0.4
Process: 71277: RSS = 22.724 MB, VSZ = 34545.528 MB
objspace: 11.532189 MB
Process: 71277: RSS = 41.352 MB, VSZ = 34558.368 MB
objspace: 22.665408 MB
RSS: 18.628 MB
VSZ: 12.840 MB
OBJ: 11.133 MB
        9.46 real         8.95 user         0.27 sys
他ファイル
Makefile
VERSIONS = $(shell rbenv versions | perl -lne '/(\d\.\d\.\d)/&&print$$1')

DOCKER_COMPOSE = docker compose
DOCKER_COMPOSE_RUN = $(DOCKER_COMPOSE) run --rm app

RUBY = rbenv exec ruby

define RUBY_RUNS
docker-$(1):
	$(DOCKER_COMPOSE_RUN) make $(1)
$(1):
	for v in $$(VERSIONS); do echo $$$$v; RBENV_VERSION=$$$$v $$(RUBY) $(2); done
endef

# require space for split
define EXEC_DEFINES
  version:-v
  ps:ps.rb
  gsub1:gsub.rb:10000000
  gsub5:gsub.rb:50000000
endef

default: docker-version

$(foreach kv,$(EXEC_DEFINES),$(eval $(call RUBY_RUNS,$(shell echo $(kv) | cut -d: -f1),$(shell echo $(kv) | cut -d: -f2- | sed -e 's/:/ /g'))))
compose.yml
version: '3'
services:
  app:
    build:
      context: .
    volumes:
      - .:/usr/src/app
    environment:
      - RBENV_VERSION
Dockerfile
FROM debian:bookworm-20231120

ARG RUBY_VERSIONS='2.7.6 3.0.4 3.1.2'
ARG RUBY_BUILD_VERSION=v20231114

WORKDIR /usr/src/app

RUN apt-get update && apt-get install -y \
rbenv \
git \
procps \
&& rm -rf /var/lib/apt/lists/*

RUN git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build \
&& git -C  "$(rbenv root)"/plugins/ruby-build switch -c ${RUBY_BUILD_VERSION} ${RUBY_BUILD_VERSION}

RUN for v in ${RUBY_VERSIONS}; do rbenv install $v; done

もう少し調べてみる

memprof2 を追加しました

追加した
ps.rb
#!/usr/bin/env ruby

require 'objspace'
require 'benchmark'
require 'memprof2'

def mem_by_ps
  rss = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
  vsz = `ps -o vsz= -p #{Process.pid}`.to_i * 0.001
  puts "Process: #{Process.pid}: RSS = #{sprintf('%0.3f', rss)} MB, VSZ = #{sprintf('%0.3f', vsz)} MB"
  [rss, vsz]
end

def mem_by_obj
  obj = ObjectSpace.memsize_of_all * 0.001 * 0.001
  puts "objspace: #{sprintf('%0.6f', obj)} MB"
  obj
end

def mem_wrap
  puts "Ruby version: #{RUBY_VERSION}"

  before = {
    rss: 0,
    vsz: 0,
    obj: 0,
  }
  after = {
    rss: 0,
    vsz: 0,
    obj: 0,
  }

  before[:rss], before[:vsz] = mem_by_ps
  before[:obj] = mem_by_obj


  bm = Benchmark.measure do
    Memprof2.start

    yield

    Memprof2.report
    Memprof2.stop
  end

  after[:rss], after[:vsz] = mem_by_ps
  after[:obj] = mem_by_obj

  puts "RSS: #{sprintf('%.3f', after[:rss] - before[:rss])} MB"
  puts "VSZ: #{sprintf('%.3f', after[:vsz] - before[:vsz])} MB"
  puts "OBJ: #{sprintf('%.3f', after[:obj] - before[:obj])} MB"

  puts Benchmark::CAPTION
  puts bm
end

if File.basename($0) == 'ps.rb'
  mem_wrap do
    a = Array.new(100_000_000, 0)
  end
end

結果

$ make docker-gsub1
docker compose run --rm app make gsub1
for v in 2.7.6 3.0.4 3.1.2; do echo $v; RBENV_VERSION=$v rbenv exec ruby gsub.rb 10000000; done
2.7.6
Ruby version: 2.7.6
Process: 86: RSS = 22.784 MB, VSZ = 83.672 MB
objspace: 12.304341 MB
10000121 gsub.rb:8:String
336 gsub.rb:8:MatchData
Process: 86: RSS = 32.640 MB, VSZ = 93.592 MB
objspace: 22.296744 MB
RSS: 9.856 MB
VSZ: 9.920 MB
OBJ: 9.992 MB
      user     system      total        real
  1.656802   0.004753   1.661555 (  1.661600)
3.0.4
Ruby version: 3.0.4
Process: 114: RSS = 23.168 MB, VSZ = 84.272 MB
objspace: 11.504425 MB
231336 gsub.rb:8:MatchData
10014925 gsub.rb:8:String
Process: 114: RSS = 36.748 MB, VSZ = 97.728 MB
objspace: 22.434818 MB
RSS: 13.580 MB
VSZ: 13.456 MB
OBJ: 10.930 MB
      user     system      total        real
 12.587906   0.038369  12.626275 ( 12.626842)
3.1.2
Ruby version: 3.1.2
Process: 142: RSS = 23.680 MB, VSZ = 84.664 MB
objspace: 11.903804 MB
582624 gsub.rb:8:MatchData
10090841 gsub.rb:8:String
Process: 142: RSS = 37.172 MB, VSZ = 98.248 MB
objspace: 22.871224 MB
RSS: 13.492 MB
VSZ: 13.584 MB
OBJ: 10.967 MB
      user     system      total        real
 12.153679   0.042324  12.196003 ( 12.196610)
  • MatchData が増えているのがわかる

Refs

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0