所感
ちゃんと調べないとダメそう・・・
Lock 有りが何もしないより早いなんて・・・
概要
var num int64
num++
単純な数字カウントアップ atomic が早そうだなーなのでベンチマーク撮ってみよ
- 何もせずに
num++ mutex.Loc() num++ mutex.Unlock()atomic.AddInt64-
atomic.ValueのLoad(), Store()
コード
カウントアップ
main.go
import (
"sync"
"sync/atomic"
)
var mutex sync.Mutex
var value atomic.Value
type Counter int64
func (c *Counter) Inc() {
*c++
}
func (c *Counter) AtomicInc() {
atomic.AddInt64((*int64)(c), 1)
}
func (c *Counter) LockInc() {
mutex.Lock()
defer mutex.Unlock()
*c++
}
func incValue() {
cnt := value.Load().(Counter)
cnt++
value.Store(cnt)
}
func countup(inc func(), n int) {
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
inc()
}()
}
wg.Wait()
}
ベンチマーク
main_test.go
package main
// go test -bench . | grep 'ns/op'
import (
"testing"
)
func BenchmarkInc_Countup(b *testing.B) {
var cnt Counter
b.ResetTimer()
countup(cnt.Inc, b.N)
b.Logf("%d -> %d", b.N, cnt)
}
func BenchmarkLock_Countup(b *testing.B) {
var cnt Counter
b.ResetTimer()
countup(cnt.LockInc, b.N)
b.Logf("%d -> %d", b.N, cnt)
}
func BenchmarkAtomic_Countup(b *testing.B) {
var cnt Counter
b.ResetTimer()
countup(cnt.AtomicInc, b.N)
b.Logf("%d -> %d", b.N, cnt)
}
func BenchmarkAtomicValue_Countup(b *testing.B) {
var cnt Counter
value.Store(cnt)
b.ResetTimer()
countup(incValue, b.N)
cnt = value.Load().(Counter)
b.Logf("%d -> %d", b.N, cnt)
}
実行結果
go test -bench .
-
atomic.Valueが速い事は、いいとしてMutexがatomic.AddInt64より速いことが解せぬ - 何もしないとカウントアップの数合わないです(想定通り)他は大丈夫
$ go test -bench .
goos: linux
goarch: amd64
BenchmarkInc_Countup-4 2000000 957 ns/op
--- BENCH: BenchmarkInc_Countup-4
acounter_test.go:13: 1 -> 1
acounter_test.go:13: 100 -> 100
acounter_test.go:13: 10000 -> 9752
acounter_test.go:13: 1000000 -> 982973
acounter_test.go:13: 2000000 -> 1962072
BenchmarkLock_Countup-4 2000000 936 ns/op
--- BENCH: BenchmarkLock_Countup-4
acounter_test.go:20: 1 -> 1
acounter_test.go:20: 100 -> 100
acounter_test.go:20: 10000 -> 10000
acounter_test.go:20: 1000000 -> 1000000
acounter_test.go:20: 2000000 -> 2000000
BenchmarkAtomic_Countup-4 2000000 943 ns/op
--- BENCH: BenchmarkAtomic_Countup-4
acounter_test.go:27: 1 -> 1
acounter_test.go:27: 100 -> 100
acounter_test.go:27: 10000 -> 10000
acounter_test.go:27: 1000000 -> 1000000
acounter_test.go:27: 2000000 -> 2000000
BenchmarkAtomicValue_Countup-4 2000000 904 ns/op
--- BENCH: BenchmarkAtomicValue_Countup-4
acounter_test.go:36: 1 -> 1
acounter_test.go:36: 100 -> 54
acounter_test.go:36: 10000 -> 7125
acounter_test.go:36: 1000000 -> 791697
acounter_test.go:36: 2000000 -> 1586774
PASS
複数回の実行結果
複数回実行すれば・・・ atomic.AddInt 早くなるはずでは・・・
atomic.Value > Mutex > なにもしない > atomic.AddInt こ、これは・・・詳細なチェックしないとフラグでは・・・
$ for i in $(seq 1 3); do go test -bench . | grep 'ns/op'; done
BenchmarkInc_Countup-4 2000000 998 ns/op
BenchmarkInc_Countup-4 2000000 989 ns/op
BenchmarkInc_Countup-4 1000000 1018 ns/op
BenchmarkLock_Countup-4 1000000 1113 ns/op
BenchmarkLock_Countup-4 2000000 986 ns/op
BenchmarkLock_Countup-4 1000000 1311 ns/op
BenchmarkAtomic_Countup-4 1000000 1010 ns/op
BenchmarkAtomic_Countup-4 2000000 1130 ns/op
BenchmarkAtomic_Countup-4 1000000 1051 ns/op
BenchmarkAtomicValue_Countup-4 2000000 1013 ns/op
BenchmarkAtomicValue_Countup-4 2000000 987 ns/op
BenchmarkAtomicValue_Countup-4 1000000 1125 ns/op
Recap
- 実装依存なら、コードを見て原因を探す
- OS依存なら Profile とかしながら、ググったりして原因を探す
- ハード依存なら・・・
- ドキュメントに書いてあるとうれしいな