Ruby2.3.3での省メモリ化をやってみる

rubyはよく、メモリリークが発生するとか、メモリを爆食いするとか言われています。
明示的にnilを入れて、メモリを開放したり、GC.startしたりして対応する人が多いようです。 そこで、実際どうなのかruby2.3.3で確認してみます。

個人的にはrubyは書きやすくて好きですが、あまり使うべきではないかと思います。
使うならgoですね〜

https://www.techempower.com/benchmarks/#section=data-r13&hw=cl&test=query

環境

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]
$ rails -v
Rails 5.0.1

準備

$ rails new ruby-memory -T -B -d mysql
$ bundle install --path=vendor/bundle
$ bundle exec rails g scaffold user a:string b:string c:string d: string e: string f: string g: string h: string i: string j: string k: string l: string m: string n: string o: string p: string q: string r: string s: string t: string u: string v: string w: string x: string y: string z: string
$ mysql.server start
※database.ymlのuserとpasswordを調整
$ bundle exec rails db:create
$ bundle exec rails db:migrate
※seeds作成
$ bundle exec rails db:seed 

db/seeds.rb

User.create([
  {
    a: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    b: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
    c: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
    d: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
    e: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
    f: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
    g: 'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg',
    h: 'hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh',
    i: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
    j: 'jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj',
    k: 'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk',
    l: 'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll',
    m: 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
    n: 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn',
    o: 'oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo',
    p: 'pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
    q: 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
    r: 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr',
    s: 'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss',
    t: 'tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt',
    u: 'uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu',
    v: 'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv',
    w: 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
    x: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    y: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
    z: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz',
  },
  ....
])

ソース1

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

76413584
107100
  User Load (1.1ms)  SELECT `users`.* FROM `users`
77939342
108876
77942258
108876
...
78041722
108936
78044638
108936

結果(f5長押し)

133268
99497606
133268
99518745
133280
99587787
133300

大量アクセスするとrubyのメモリ解放が追いつかないのか。

ソース2

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      u = nil
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

69259461
110160
  User Load (0.8ms)  SELECT `users`.* FROM `users`
70789587
110332
70792503
110332
70795419
110332
...
70891687
110340
70894603
110340

結果(f5長押し)

89480104
136112
89483020
136112
89485936
136112
89488852
136112

何もしないよりかは、ましなのかもしれません。

ソース3

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      GC.start
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

74074939
110832
  User Load (0.7ms)  SELECT `users`.* FROM `users`
66737741
110880
66737141
110880
66737141
110880
66737141
110880
...
66737141
110880
66740057
110880

結果(f5長押し)

69000597
135268
69000597
135268
69000597
135268
69003833
135268

結果としては、GC.startすると一番メモリ使用量は少なくなりました。

rubyGCのことまで考えないといけないので、もはや欠陥スクリプト言語ですかね。

memory関連のgem

https://github.com/SamSaffron/memory_profiler

https://github.com/tagomoris/ruby-memory-usage-profiler

参考

http://blog.inouetakuya.info/entry/2015/07/26/204320

http://2012.rubyworld-conf.org/files/slides/rwc2012_A-5.pdf