Slide 1

Slide 1 text

RBSのメモリ使用量改善 への道 岡山Ruby, Ruby on Rails勉強会 #23

Slide 2

Slide 2 text

誰 ● Pocke ● Ruby committer ● RBSのメンテナ ● 2020年から岡山に住んでいます

Slide 3

Slide 3 text

Agenda メインのトピックが2つ ● ファイル数が多い時に、Steepの実行速度を改善した話 ● Ruby向けのメモリプロファイラを作っている話

Slide 4

Slide 4 text

なぜメモリ使用量の改善が必要なのか

Slide 5

Slide 5 text

なぜメモリ使用量の改善が必要なのか ● Steepはメモリをたくさん使う ○ LSP Serverとしてプロセスが常駐する ○ 1プロセスあたり 1.5GB ■ * LSPが立ち上がっているプロジェクト数 ■ * CPUの数(並列化するため) ● Steepを広く使ってもらうにはメモリ使用量改善が必要

Slide 6

Slide 6 text

Steepの実行速度を改善した話

Slide 7

Slide 7 text

Steepの実行速度を改善した話 ● Steepのメモリ使用量の改善に取り組んでいたら、その 副産物として実行速度の改善ができた ○ メモリ使用量の改善はまだ…… ● これはこれで面白かったので、紹介します ● (来週この内容でブログ記事を公開するよ)

Slide 8

Slide 8 text

メモリのプロファイリング ● SamSaffron/memory_profilerを使う ● すべてのメモリの確保と、確保したまま残っているメモ リを表示してくれる

Slide 9

Slide 9 text

memory_profiler の結果

Slide 10

Slide 10 text

memory_profiler の結果 set.rbがめちゃくちゃメモリを使っている!

Slide 11

Slide 11 text

わかった問題 ● set.rb がメモリをたくさん確保している ○ 全体のメモリ確保の70%ぐらいがここで確保されている ● これを直せれば問題が解決できるかも!

Slide 12

Slide 12 text

わかっていないこと ● set.rb のコードがどこから呼ばれているのかが分から ない ● これがわからないとSteepのどこを直したらよいのかが 分からない

Slide 13

Slide 13 text

Rubyの雑パッチを書いた https://gist.github.com/pocke/7499e5799856393 b930684ebb905d41c

Slide 14

Slide 14 text

雑パッチ ● MemoryProfilerは ObjectSpace.allocation_sourcefile などを使っ ている ● このメソッドが位置情報を取得するところにパッチする ○ 取得した位置情報がset.rbならば、バックトレースを1つ遡る

Slide 15

Slide 15 text

雑パッチ

Slide 16

Slide 16 text

再度memory_profilerを実行

Slide 17

Slide 17 text

再度memory_profilerを実行 Steepのどこがメモリを確保しているのかわかった!

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

実行速度の問題が解決された💪 https://github.com/soutaro/steep/pull/1184 でもメモリ使用率はまだ未解決…

Slide 22

Slide 22 text

新しいメモリプロファイラ

Slide 23

Slide 23 text

今までの反省点 ● メモリ使用量を改善するつもりが、実行速度の改善に なってしまっていた ● memory_profilerで正しく問題を捉えられていなかっ た

Slide 24

Slide 24 text

今までの反省点(2) ● memory_profilerで見ていた指標は「すべてのメモリ 確保」 ● 一方でメモリ使用量の削減のために知りたいのは「メモ リ使用量がピークの時にメモリを使っているオブジェク ト」 ● ここでずれがあった ○ たくさん確保されてはすぐ消えるオブジェクトがノイズとなっていた

Slide 25

Slide 25 text

これに対するアプローチ ● 長生きしたオブジェクトに絞って、メモリのプロファイ リングをできるとよいのでは ● つまり、GCを生き残ったオブジェクトに注目する

Slide 26

Slide 26 text

メモリプロファイラを作った󰩂 https://github.com/pocke/majo ● GCを1回でも生き残ったオブジェクトだけを集めて集計す るプロファイラ ● 一応まともに使えるので、v1.0.0をリリース済み

Slide 27

Slide 27 text

使ってみた感想 ● 出る情報はmemory_profilerと近い ○ しかし「長生きしたオブジェクト」しか結果に含まれないので、ノイズ がないことがわかるのが大きい ○ 案外どれが長生きでどれが短命なのかは自明ではない ● CSV出力が意外とかなり便利 ○ もしくはスプレッドシートが便利とも言える ● memory_profilerに比べると若干高速 ○ 「全オブジェクト」を対象にするmemory_profilerはかなり遅い ○ 一方で短命オブジェクトを無視できるMajoは若干マシ

Slide 28

Slide 28 text

余談(1) memory_profilerのretainedを見るんじゃだめだった の? ● retainedを見るにはオブジェクトを生かしておく必要が あり、memory_profilerを差し込む場所を考えるのが むずかしい… ● 「メモリ使用量がピークの時」にmemory_profilerを 止めないといけない

Slide 29

Slide 29 text

余談(2) ● https://github.com/ruby/ruby/pull/10598 ○ RubyのMajor GCだけを止めて、Minor GCのみを動かす機能追加のPR ● 最初実現方法を考えている時に、このPRも試せないかな と眺めていた ● 結果としては使わなかったけれど、PRでバグ報告をした りしていい経験になった