この記事ではAWS ec2上のCentOS7にインストールしたGo1.7を使っています。
とあるgoroutineを使ったGo言語のプログラムを作成して、マルチコア環境で実行してみたところ、シングルコアよりも性能が落ちてしまう、という不思議な現象に遭遇しました。
いろいろ切り分けてみると、math/randライブラリで、グローバルrandを使っていると、この現象に陥ることがわかりました。ローカルrandに書き換えると、マルチコアで順当に性能が出ます。マルチスレッドでグローバル関数を利用したために競合が起きているように思います。
シングルコアの時にgoroutineでスレッドを増やしてもこの現象は発生せず、マルチコアになると発生します。
そのものズバリを解説している記事を見つけてしまいました。
Goroutine と math/rand とベンチマークの罠
二番煎じですが、役に立つ情報かもしれませんので、ベンチマークのコードと結果を示します。
package main
import (
"testing"
"math/rand"
)
func BenchmarkGlobal(b *testing.B) {
c := make(chan bool, 10000)
for i := 0; i < b.N; i++ {
c <- true
go func() {
defer func() { <-c }()
for j := 0; j < 100; j++ {
_ = rand.Intn(100) // use global rand
}
}()
}
}
func BenchmarkLocal(b *testing.B) {
c := make(chan bool, 10000)
for i := 0; i < b.N; i++ {
c <- true
go func() {
defer func() { <-c }()
r := rand.New(rand.NewSource(1)) // create local rand
for j := 0; j < 100; j++ {
_ = r.Intn(100) // use local rand
}
}()
}
}
go test -bench . -benchmem
でベンチマークを実行します。
以下に結果を表にまとめます。
AWS ec2のインスタンスタイプを変えて、ベンチマーク結果の変化を比較しています。
BenchmarkGlobal
インスタンスタイプ | コア数 | ns/op | B/op | allocs/op |
---|---|---|---|---|
t2.nano | 1 | 6082 | 4 | 0 |
t2.small | 1 | 6365 | 4 | 0 |
t2.medium | 2 | 24269 | 28 | 0 |
t2.xlarge | 4 | 30654 | 56 | 0 |
t2.2xlarge | 8 | 22361 | 6 | 0 |
BenchmarkLocal
インスタンスタイプ | コア数 | ns/op | B/op | allocs/op |
---|---|---|---|---|
t2.nano | 1 | 20738 | 5387 | 1 |
t2.small | 1 | 21593 | 5399 | 1 |
t2.medium | 2 | 11624 | 5359 | 1 |
t2.xlarge | 4 | 6038 | 5410 | 1 |
t2.2xlarge | 8 | 3112 | 5405 | 1 |
BenchmarkGlobalだとコア数が増えてもむしろ性能悪化してしまいます。一方、BenchmarkLocalはコア数に対してほぼリニアに性能向上しています。
参考リンク:
Goroutineの最大数を制御する方法
go言語でベンチマーク
Go言語でランダムな文字列を生成する方法の比較