Build status go.dev reference Go Report Card codecov
A Golang lock-free thread-safe HashMap optimized for fastest read access.
It is not a general-use HashMap and currently has slow write performance for write heavy uses.
Example uint8 key map uses:
m := New[uint8, int]()
m.Set(1, 123)
value, ok := m.Get(1)
Example string key map uses:
m := New[string, int]()
m.Set("amount", 123)
value, ok := m.Get("amount")
Using the map to count URL requests:
m := New[string, *int64]()
var i int64
counter, _ := m.GetOrInsert("api/123", &i)
atomic.AddInt64(counter, 1) // increase counter
...
count := atomic.LoadInt64(counter) // read counter
Reading from the hash map for numeric key types in a thread-safe way is faster than reading from a standard Golang map
in an unsafe way and three times faster than Golang's sync.Map:
ReadXsyncMapUint-8 924.5n ± ∞ 1
ReadHashMapUint-8 1.072μ ± ∞ 1
ReadHaxMapUint-8 1.296μ ± ∞ 1
ReadGoMapUintUnsafe-8 1.318μ ± ∞ 1
ReadGoSyncMapUint-8 3.389μ ± ∞ 1
ReadSkipMapUint-8 4.820μ ± ∞ 1
ReadGoMapUintMutex-8 32.62μ ± ∞ 1
Reading from the map while writes are happening:
ReadHashMapWithWritesUint-8 1.235μ ± ∞ 1
ReadHaxMapWithWritesUint-8 1.433μ ± ∞ 1
ReadGoSyncMapWithWritesUint-8 4.503μ ± ∞ 1
Write performance without any concurrent reads:
WriteGoMapMutexUint-8 21.72μ ± ∞ 1
WriteHashMapUint-8 27.99μ ± ∞ 1
WriteGoSyncMapUint-8 78.43μ ± ∞ 1
The benchmarks were run with Golang 1.24.4 on Linux and a Ryzen 9 5900X CPU using make benchmark-perflock.
-
Technical design decisions have been made based on benchmarks that are stored in an external repository: go-benchmark
-
The library uses a sorted linked list and a slice as an index into that list.
-
The Get() function contains helper functions that have been inlined manually until the Golang compiler will inline them automatically.
-
It optimizes the slice access by circumventing the Golang size check when reading from the slice. Once a slice is allocated, the size of it does not change. The library limits the index into the slice, therefore the Golang size check is obsolete. When the slice reaches a defined fill rate, a bigger slice is allocated and all keys are recalculated and transferred into the new slice.
-
For hashing, specialized xxhash implementations are used that match the size of the key type where available