All Benchmarks

Go Benchmark

Concurrent Map Access

Compare sync.Map against a RWMutex-protected map for concurrent reads and writes.

mapconcurrencysync

The classic map access is done by using the map[key] syntax. This implementation works fine in the most cases, but it is not thread-safe. A solution is to use the sync.Map type or to add a mutex to the map. This benchmark shows which implementation is the fastest.

linux/amd64AMD Ryzen 9 9950X3D 16-Core Processorbenchmarks/concurrent-map-access
Compare atCPUs

1 CPU

Read

Fastest

Mutex

10.7 ns/op

Slowest

Sync

11.94 ns/op

Write

Fastest

Mutex

31.89 ns/op

Slowest

Sync

50.73 ns/op · 1.6x slower

32 CPUs

Read

Fastest

Sync

15.05 ns/op

Slowest

Mutex

22.37 ns/op

Write

Fastest

Sync

41.21 ns/op

Slowest

Mutex

71.51 ns/op · 1.7x slower

Performance Comparison (lower is better)
CPU:

Mutex #

Fastest (Read, 1 CPU)Slowest (Read, 32 CPUs)Fastest (Write, 1 CPU)Slowest (Write, 32 CPUs)

Uses a sync.RWMutex to protect a plain map[int]int. Reads take a shared lock (RLock), writes take an exclusive lock (Lock). This is the standard approach when you control all access sites and need fine-grained locking with concurrent readers.

Performance (lower is better)
CPU:
// mapSize is the key space used by all benchmarks in this package.
const mapSize = 1000

func BenchmarkMutex_write(b *testing.B) {
	var mu sync.RWMutex
	m := make(map[int]int)

	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			mu.Lock()
			m[i%mapSize] = i
			mu.Unlock()
			i++
		}
	})
}

func BenchmarkMutex_read(b *testing.B) {
	var mu sync.RWMutex
	m := make(map[int]int, mapSize)
	for i := range mapSize {
		m[i] = i
	}

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			mu.RLock()
			_ = m[i%mapSize]
			mu.RUnlock()
			i++
		}
	})
}
1 CPU

Read

1.1xfaster(12%)thanSync

Write

1.6xfaster(59%)thanSync
32 CPUs

Read

1.5xslower(49%)thanSync

Write

1.7xslower(74%)thanSync

Sync #

Fastest (Read, 32 CPUs)Slowest (Read, 1 CPU)Fastest (Write, 32 CPUs)Slowest (Write, 1 CPU)

Uses the sync.Map type from the standard library. It is inherently thread-safe and needs no external locking. Optimised for keys that are stable over time. It performs best when entries are written once and read many times.

Performance (lower is better)
CPU:
// mapSize is the key space used by all benchmarks in this package.
const mapSize = 1000

func BenchmarkSync_write(b *testing.B) {
	var m sync.Map

	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			m.Store(i%mapSize, i)
			i++
		}
	})
}

func BenchmarkSync_read(b *testing.B) {
	var m sync.Map
	for i := range mapSize {
		m.Store(i, i)
	}

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		i := 0
		for pb.Next() {
			m.Load(i % mapSize)
			i++
		}
	})
}
1 CPU

Read

1.1xslower(12%)thanMutex

Write

1.6xslower(59%)thanMutex
32 CPUs

Read

1.5xfaster(49%)thanMutex

Write

1.7xfaster(74%)thanMutex

Contributors