sync.RWMutex vs atomic.Value vs unsafe.Pointer

Date: 23 Aug 2015
Author: Erik Dubbelboer

When using multiple goroutines you often want to have shared access of some read only resource. There are multiple ways to implements this. Three of them are; sync.RWMutex, atomic.Value and unsafe.Pointer.

sync.RWMutex is a whole mutex so I would expect that to be the slowest.

atomic.Value basically stores an interface{} value and makes sure that once assigned the type is never allowed to change.

And unsafe.Pointer is just a simple pointer on which we use the atomic.StorePointer and atomic.LoadPointer methods to get atomic access.

So when your code is simple and you know you are only storing one type using unsafe.Pointer instead of atomic.Value can be much faster. Here are the benchmark results on my mac-book:

$ go test -bench .
BenchmarkPointer 200000000         7.76 ns/op
BenchmarkValue__  50000000          25.7 ns/op
BenchmarkRWMutex  100000000         22.6 ns/op
$ GOMAXPROCS=6 go test -bench .
BenchmarkPointer-6  100000000         11.9 ns/op
BenchmarkValue__-6  30000000          39.9 ns/op
BenchmarkRWMutex-6  5000000       302 ns/op

The code:

package main

import (
        "runtime"
        "sync"
        "sync/atomic"
        "testing"
        "unsafe"
)


func BenchmarkPointer(b *testing.B) {
        var ptr unsafe.Pointer

        m := make(map[int]int)
        m[1] = 2

        atomic.StorePointer(&ptr, unsafe.Pointer(&m))

        var wg sync.WaitGroup

        for g := 0; g < runtime.GOMAXPROCS(0); g++ {
                wg.Add(1)

                go func() {
                        for i := 0; i < b.N; i++ {
                                m := (*map[int]int)(atomic.LoadPointer(&ptr))
                                _ = (*m)[1]
                        }

                        wg.Done()
                }()
        }

        wg.Wait()
}


func BenchmarkValue__(b *testing.B) {
        var ptr atomic.Value

        m := make(map[int]int)
        m[1] = 2

        ptr.Store(m)

        var wg sync.WaitGroup

        for g := 0; g < runtime.GOMAXPROCS(0); g++ {
                wg.Add(1)

                go func() {
                        for i := 0; i < b.N; i++ {
                                m := ptr.Load().(map[int]int)
                                _ = m[1]

                        }

                        wg.Done()
                }()
        }

        wg.Wait()
}

func BenchmarkRWMutex(b *testing.B) {
        var l sync.RWMutex

        m := make(map[int]int)
        m[1] = 2

        var wg sync.WaitGroup

        for g := 0; g < runtime.GOMAXPROCS(0); g++ {
                wg.Add(1)

                go func() {
                        for i := 0; i < b.N; i++ {
                                l.RLock()
                                _ = m[1]
                                l.RUnlock()
                        }

                        wg.Done()
                }()
        }

        wg.Wait()
}

The source of atomic.Value nicely shows how interface{} is implemented:

type ifaceWords struct {
        typ  unsafe.Pointer
        data unsafe.Pointer
}
comments powered by Disqus