前回、前々回にひきつづき、コツコツつくっているgem Rgotはgo testを模しているので当然Benchmark機能をもっています。
go test
コマンドでは-cpu
オプションをつけると、並列するgoroutineの数と、runtime.GOMAXPROCSで設定できる使うCPUの数をオプションで指定でき、並列化によって性能が向上しているのかBenchmarkをとることができます。
こちらもパク参考にさせていただき、Rubyにおける並列性のBenchmarkが取れるように実装しました。
ついでに並行性のBenchmarkもやっておきました。
タテマエ
Rubyでは並列動作させるにはforkしてプロセスを作ることになります。 また、並行動作させるにはthreadを作ることになります。
Rgotでは、--cpu
オプションでforkするプロセスの数を、
--thread
でThread.newで作るthreadの数をそれぞれ調整できるようになっています。
$ rgot --cpu=2,4 --thread=2,4 foo_test-2(2) 100 100 ns/op foo_test-2(4) 100 100 ns/op foo_test-4(2) 100 100 ns/op foo_test-4(4) 100 100 ns/op
このように、プロセスが2個のときと4個のとき、threadが2個のときと4個のときの全ての組み合わせをBenchmarkできます。
-2
となっているのがプロセス数、(2)
がthread数を表しています。
その次の数字はこれだけのタスクをこなしたと読めばOK.
ns/opはイチ動作あたり何ナノ秒かかったかを示しています。
構文の翻訳
構文はgoだと
func BenchmarkFoo (b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { fibo(27) } }) }
のような感じですが、これをRubyっぽくブロック構文を使って
def benchmark_foo(b) b.run_parallel do |pb| while pb.next fibo(27) end end end
というように翻訳しました。
実践
さっそく試してみます。
フィボナッチ数の計算と、sleepで並列/並行性能を測ってみました。
module BenchmarkTest def fibo(n) if n < 2 n else fibo(n - 1) + fibo(n - 2) end end def benchmark_fibo(b) b.run_parallel do |pb| while pb.next fibo(27) end end end def benchmark_sleep(b) b.run_parallel do |pb| while pb.next sleep 0.1 end end end end
$ rgot benchmark_test.rb --bench . --cpu=2,4 --thread=2,4 benchmark_fibo-2(2) 160 13083452.156 ns/op benchmark_fibo-2(4) 320 13029861.872 ns/op benchmark_fibo-4(2) 640 7369437.953 ns/op benchmark_fibo-4(4) 1280 7677284.290 ns/op benchmark_sleep-2(2) 80 26198324.537 ns/op benchmark_sleep-2(4) 320 12980783.569 ns/op benchmark_sleep-4(2) 320 12982687.494 ns/op benchmark_sleep-4(4) 1280 6472016.920 ns/op ok BenchmarkTest 40.527s
CPUバウンスなフィボナッチ数を計算する場合は、プロセス数が増えると性能が向上していることがわかりますが、スレッド数が増えても性能の向上は見られません。
Rubyのスレッドは同時に動けるのは1つだけという制限があるので、並列に使えるCPUの数はプロセス数に依存するためだとBenchmarkから考察できます。
また、I/Oバウンスな(処理を模した)sleepでは、プロセス数が増えてもthread数が増えても性能が向上しています。
RubyのThreadは、並列では動けないとはいえ並行では動けるので、sleepやHTTPリクエストなどCPUを使わないブロッキングがある処理では効果を発揮することがBenchmarkから読み取ることができます。
ちなみに
同等のコードをgolangで書いてみた場合のBenchmarkです
$ go test -bench . -cpu=4,8,16 testing: warning: no tests to run PASS BenchmarkFibo-4 5000 293746 ns/op BenchmarkFibo-8 5000 268685 ns/op BenchmarkFibo-16 5000 280306 ns/op BenchmarkSleep-4 50 26219605 ns/op BenchmarkSleep-8 100 13097673 ns/op BenchmarkSleep-16 200 6561824 ns/op ok github.com/ksss/gotest 9.155s
golang速い……。
というわけで
RgotをつかったRubyの並行/並列ベンチマークの書き方でした。
並列/並行ベンチマークはRgot v0.0.5から使用可能です。