この記事は
YAMAP エンジニア Advent Calendar 2022 の第十日目の記事です。
前回
の記事で書いたコードのパフォーマンスを比較してみたくなったので、Benchmark モジュールを使って 2 つのコードのパフォーマンスを計測してみました。(ベンチマークを走らせてみました)
引き続き、以下の環境で検証を行っています。
root@a68e3293a424:/work# ruby -v ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
Benchmark モジュール
Benchmark モジュールは、その名の通り Ruby プログラムのベンチマークを取得するモジュールです。
ドキュメントに掲載されているサンプルコードを実行してみます。
require 'benchmark' n = 50000 Benchmark.benchmark(" "*7 + Benchmark::CAPTION, 7, Benchmark::FORMAT, ">total:", ">avg:") do |x| tf = x.report("for:") { for i in 1..n; a = "1"; end } tt = x.report("times:") { n.times do ; a = "1"; end } tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } [tf+tt+tu, (tf+tt+tu)/3] end
これは、以下の各コードの実行速度を計測し、実行速度の合計と平均も合わせて出力しています。
for i in 1..n; a = "1"; end
n.times do ; a = "1"; end
1.upto(n) do ; a = "1"; end
実行すると以下のように出力されます。
root@a68e3293a424:/work# ruby sample.rb user system total real for: 0.011802 0.001178 0.012980 ( 0.027640) times: 0.006102 0.000499 0.006601 ( 0.007872) upto: 0.007856 0.000000 0.007856 ( 0.011528) >total: 0.025760 0.001677 0.027437 ( 0.047040) >avg: 0.008587 0.000559 0.009146 ( 0.015680)
出力結果のヘッダ部分 (user
や system
等) は以下のような意味があります。
ヘッダ名 | 意味 |
---|---|
user | プログラムによって利用された時間 (Ruby が動いた時間) |
system | プログラムによって OS が利用された時間 |
total | user + system の時間 |
real | プログラムの実行に掛かったリアルな時間 (プログラムの起動から終了までの実稼働時間) |
上記のサンプルコードの実行結果を見ると、times メソッドを利用したコードが最も実行速度が速いようです。
root@a68e3293a424:/work# ruby sample.rb user system total real for: 0.011802 0.001178 0.012980 ( 0.027640) times: 0.006102 0.000499 0.006601 ( 0.007872) ← これが一番速い upto: 0.007856 0.000000 0.007856 ( 0.011528)
尚、前後してしてしまいますが benchmark メソッドの引数は以下の通りです。(ドキュメントより引用させて頂きました。)
引数名 | 内容 | 例 |
---|---|---|
caption | レポートの一行目に表示する文字列を指定します。 | " "*7 + Benchmark::CAPTION |
label_width | ラベルの幅を指定します。 | 7 |
fmtstr | フォーマット文字列を指定します。この引数を省略すると Benchmark::FORMAT が使用されます。 | Benchmark::FORMAT |
labels | ブロックが Benchmark::Tms オブジェクトの配列を返す場合に指定します。 | ">total:", ">avg:" |
また、benchmark メソッドの引数を省略した bm というメソッドもあります。bm メソッドの場合には、以下のように書くことが出来るとのことです。
require 'benchmark' n = 50000 Benchmark.bm do |x| x.report { for i in 1..n; a = "1"; end } x.report { n.times do ; a = "1"; end } x.report { 1.upto(n) do ; a = "1"; end } end
こちらの方がシンプルで好きです。個人的に。
ベンチマーク!
ということで、前回の記事で書いたコードでベンチマークを走らせてみたいと思います。
データが少ないと一瞬で処理が終わってしまう為、対象のデータを多めに約 24 万行分用意しました。
root@a68e3293a424:/work# wc -l 200000.txt 240084 200000.txt
そして、ベンチマーク対象のコードは以下のような感じです。
datas = File.open('200000.txt').read.split("\n") Benchmark.bm(10) do |x| x.report("chunk: ") { groups = datas.chunk {|x| x != "" || nil}.map {|x| x.last } result = [] groups.each do |g| result << g.map(&:to_i).sum end # puts result.max } x.report("no chunk: ") { datas.push("") _result = [] _array = [] datas.each do |d| if d.empty? _result << _array.sum _array = [] else _array << d.to_i end end # puts _result.max } end
ベンチマークのコードを実行すると、以下のような結果となりました。
root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.136952 0.009732 0.146684 ( 0.157515) no chunk: 0.064187 0.000000 0.064187 ( 0.072388) root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.132214 0.008650 0.140864 ( 0.151078) no chunk: 0.059786 0.002620 0.062406 ( 0.071507) root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.136413 0.008141 0.144554 ( 0.187495) no chunk: 0.065624 0.000606 0.066230 ( 0.092425)
Enumerable#chunk を使った方が速いのかなーと思っていましたが、実際にはそうでもなさそうです。chunk を使わない方が半分くらいの処理時間となっています。
ちょっと気になった (chunk の部分は速いけど result << g.map(&:to_i).sum に時間が掛かっているのかなーと思った) ので、純粋に chunk メソッドが実行されている部分だけでベンチマークを走らせてみました。
datas = File.open('200000.txt').read.split("\n") Benchmark.bm(10) do |x| x.report("chunk: ") { datas.chunk {|x| x != "" || nil}.map {|x| x.last } } end
以下のような結果となりました。
root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.089565 0.012890 0.102455 ( 0.104683) root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.102780 0.014299 0.117079 ( 0.146592) root@142f31c43c0a:/work# ruby test.rb user system total real chunk: 0.084478 0.010283 0.094761 ( 0.098349)
70% くらい chunk の実行に時間を要しているという興味深い結果となりました。
ここから
chunk メソッドがどのように実装されているのかを追うことが出来ると良いのですが、私のスキルでは限界がありますので、Benchmark モジュールの使い方を覚えたところで、今回はここらで筆を置こうと思います。
現場からは以上です。