Go
golang
WACULDay 10

go1.9から追加されたsync.Mapを使う

More than 1 year has passed since last update.


概要

golang 1.9からsync.Mapが導入されました。

このスライドによると,導入のモチベーションとしてはCPUのCore数が上昇すると,同期にコストがかかる(cache-contentionの問題と呼ばれている)ので,

内部ではこれをなんとかするために色々やってるっぽいです。(コード読んでもよくわからなかった)


使ってみる

とりあえず今まででは、sync.Mapっぽいものを実装しようとすると



type Target struct {
foo int
bar string
}

type OldSyncMap struct {
mu sync.Mutex
m map[int]Target
}

func NewOldSyncMap() OldSyncMap {
return OldSyncMap{m: map[int]Target{}}
}

func (s *OldSyncMap) Store(key int, value Target) {
s.mu.Lock()
defer s.mu.Unlock()

s.m[key] = value
}

func (s *OldSyncMap) Load(key int) (Target, error) {
s.mu.Lock()
defer s.mu.Unlock()

t, ok := s.m[key]
if !ok {
return Target{}, errors.New("not found")
}
return t, nil
}

func main() {
s := NewOldSyncMap()
s.Store(1, Target{
foo: 1,
bar: "bar",
})

t, err := s.Load(1)
fmt.Println(t, err) // => {1 bar} <nil>
}

みたいな感じだと思うが、sync.Map使うと



type Target struct {
foo int
bar string
}

type SyncMap struct {
s sync.Map
}

func NewSyncMap() SyncMap {
return SyncMap{}
}

func (s *SyncMap) Store(key int, value Target) {
s.s.Store(key, value)
}

func (s *SyncMap) Load(key int) (Target, error) {
v, ok := s.s.Load(key)
if !ok {
return Target{}, errors.New("not found")
}

t, ok := v.(Target)
if !ok {
return Target{}, errors.New("stored type is invalid")
}

return t, nil
}

func main() {
s := NewSyncMap()
s.Store(1, Target{
foo: 1,
bar: "bar",
})

t, err := s.Load(1)
fmt.Println(t, err) // => {1 bar} <nil>
}

こんな感じで書ける。


実際に測定すると

雑に書いたベンチマークで測定してみる

Intel Core i5-6200U(Core 2, Thread 4), Memory 8GB, Ubuntu 16.04で

func BenchmarkOldSyncMap(b *testing.B) {
for j := 0; j < b.N; j++ {
m := NewOldSyncMap()
wg := sync.WaitGroup{}
for i := 0; i < 1000000; i++ {
wg.Add(1)
go func(i int) {
m.Store(i, Target{
foo: i,
})

_, err := m.Load(i)
if err != nil {
b.Errorf("in %d, %s", i, err)
}
wg.Done()
}(i)
}
wg.Wait()
}
}

func BenchmarkNewSyncMap(b *testing.B) {
for j := 0; j < b.N; j++ {
m := NewSyncMap()
wg := sync.WaitGroup{}

for i := 0; i < 1000000; i++ {
wg.Add(1)
go func(i int) {
m.Store(i, Target{
foo: i,
})

_, err := m.Load(i)
if err != nil {
b.Errorf("in %d, %s", i, err)
}
wg.Done()
}(i)
}
wg.Wait()
}
}


goos: linux
goarch: amd64
pkg: test
BenchmarkOldSyncMap-4 1 5575385714 ns/op
BenchmarkNewSyncMap-4 1 3358420579 ns/op
PASS
ok test 9.298s

若干早い。