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

若干早い。