考える発端となったのは以下の記事。
stackimpact.com
GoのPerformance Turningに関する記事なのだが、気になる項目が。
Favor lock-free algorithms
ふむ🤔
www.slideshare.net
ほう?🤔
MutexやSemaphoreで排他制御を行なうのではなく、CASなどを使ったlock-freeを実装した方が良いらしい。
CAS
値の書き換えが想定通りのものなら、実際に書き換えを行なう。
書き換えが出来るまで、何度でも繰り返す。
→ 但し、問題もある(ex. ABA問題
上記のCASは有限のステップ数で処理が完了しないため、lock-freeではあるがwait-freeではない。
Goの場合はどうやるの?
Goの場合、atomicパッケージにCAS実装がある。
atomic - The Go Programming Language
package main
import (
"sync"
"fmt"
)
var (
balance int32 = 100
wg = sync.WaitGroup{}
)
func main() {
var i int32
for i = 0; i < 10; i++ {
wg.Add(1)
go withdraw(i)
}
wg.Wait()
fmt.Println(balance)
}
func withdraw(amount int32) {
balance = balance - amount
wg.Done()
}
しかし、この方法だと当たり前だがgoroutineの処理タイミングによって全く違った値が算出される。
そこで、以下のようにCASを用いる。
package main
import (
"sync"
"fmt"
"sync/atomic"
)
var (
balance int32 = 100
wg = sync.WaitGroup{}
)
func main() {
var i int32
for i = 0; i < 10; i++ {
wg.Add(1)
go withdraw(i)
}
wg.Wait()
fmt.Println(balance)
}
func withdraw(amount int32) {
for {
if atomic.CompareAndSwapInt32(&balance, balance, balance - amount) {
break
}
}
wg.Done()
}
すると、想定した55の値が常に算出される。
Mutexの中身は・・・
結局、CASを使っていたりする。
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
パフォーマンスの差異
実際にパフォーマンスに違いは出るのだろうか?
ベンチマークを取ってみる。
- old: use Mutex
- new: use CAS
benchmark old ns/op new ns/op delta
BenchmarkDo-4 4028 3670 -8.89%
BenchmarkDo-4 4000 3683 -7.93%
BenchmarkDo-4 4014 2934 -26.91%
BenchmarkDo-4 3516 3043 -13.45%
BenchmarkDo-4 2759 3660 +32.66%
BenchmarkDo-4 4030 2883 -28.46%
BenchmarkDo-4 4025 3802 -5.54%
BenchmarkDo-4 4027 3358 -16.61%
BenchmarkDo-4 4046 3602 -10.97%
BenchmarkDo-4 3749 3363 -10.30%
全体的に早くはなっているが、たまに致命的に遅くなる。
さて、実行するgoroutineの数を増やすとどうなるのだろうか?
benchmark old ns/op new ns/op delta
BenchmarkDo-4 271236 266445 -1.77%
BenchmarkDo-4 275415 267188 -2.99%
BenchmarkDo-4 269019 269459 +0.16%
BenchmarkDo-4 272291 271073 -0.45%
BenchmarkDo-4 273410 269671 -1.37%
benchmark old ns/op new ns/op delta
BenchmarkDo-4 2782914 2831663 +1.75%
BenchmarkDo-4 2712926 2936499 +8.24%
BenchmarkDo-4 2748464 2891509 +5.20%
BenchmarkDo-4 2810436 2844462 +1.21%
BenchmarkDo-4 2738268 2875502 +5.01%
む?なんか段々とMutexに負けてるぞ🤔
何故だ・・・
結局どうすりゃいいのか?
多大なコア数を持つCPUでないと、CASなどのlock-freeの効果は薄そう。
以下のリンク先にも書いてあるが、実際に80coreで動かした場合にMutexがボトルネックになったようである。
そういう時にはlock-freeを使う意味が出てきそう。
texlution.com