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