hashmap icon indicating copy to clipboard operation
hashmap copied to clipboard

A Golang lock-free thread-safe HashMap optimized for fastest read access.

hashmap

Build status go.dev reference Go Report Card codecov

Overview

A Golang lock-free thread-safe HashMap optimized for fastest read access.

It requires Golang 1.19+ as it makes use of Generics and the new atomic package helpers.

Warning: This library is experimental, there are currently no known bugs but more battle-testing is needed.

Usage

Only Go comparable types are supported as keys.

Set a value for a key in the map:

m := New[string, int]()
m.Set("amount", 123)

Read a value for a key from the map:

value, ok := m.Get("amount")

Use 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

Benchmarks

Reading from the hash map in a thread-safe way is nearly as fast as reading from a standard Golang map in an unsafe way and twice as fast as Go's sync.Map:

BenchmarkReadHashMapUint-8                       2047108               547.2 ns/op
BenchmarkReadHaxMapUint-8                        2295067               532.3 ns/op (not safe to use, can lose writes during concurrent deletes)
BenchmarkReadGoMapUintUnsafe-8                   3303577               360.6 ns/op
BenchmarkReadGoMapUintMutex-8                      83017             14266 ns/op
BenchmarkReadGoSyncMapUint-8                      943773              1208 ns/op

Reading from the map while writes are happening:

BenchmarkReadHashMapWithWritesUint-8             1669750               718.1 ns/op
BenchmarkReadGoMapWithWritesUintMutex-8            24919             54270 ns/op
BenchmarkReadGoSyncMapWithWritesUint-8            823063              1439 ns/op

Write performance without any concurrent reads:

BenchmarkWriteHashMapUint-8                        30104             49176 ns/op
BenchmarkWriteGoMapMutexUint-8                    177536              6571 ns/op
BenchmarkWriteGoSyncMapUint-8                      19857             61428 ns/op

The benchmarks were run with Golang 1.19.0 on Linux using make benchmark.

Benefits over Golang's builtin map

  • Faster

  • thread-safe access without need of a(n extra) mutex

Benefits over Golang's sync.Map

  • Faster

Technical details

  • 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.