Skip to content
A Golang lock-free thread-safe HashMap optimized for fastest read access.
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
vendor/github.com/dchest/siphash Add dep vendoring May 7, 2018
.codecov.yml codecov yaml added Aug 5, 2016
.gitignore First version Aug 2, 2016
.travis.yml include git 1.11 testing Oct 21, 2018
Gopkg.lock Add dep vendoring May 7, 2018
Gopkg.toml Add dep vendoring May 7, 2018
LICENSE Name added to license Apr 27, 2018
README.md Benchmark with writes fixed to continue writing. One of the threads w… Apr 29, 2018
benchmark_test.go Benchmark with writes fixed to continue writing. One of the threads w… Apr 29, 2018
go.mod vgo support Jun 19, 2018
hashmap.go
hashmap_get.go Add support to GetOrInsert() for []byte keys Oct 21, 2018
hashmap_test.go Fix race condition in list Jan 21, 2019
list.go Fix race condition in list Jan 21, 2019
list_test.go Renamed list start item from root to head Apr 27, 2018
listelement.go API changed to user interface{} instead of unsafe.Pointer. The perfor… Apr 29, 2018
util.go Minor code style fixes Apr 27, 2018
util_test.go Add support for []byte keys Nov 2, 2017

README.md

hashmap Build Status GoDoc Go Report Card codecov

Overview

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

Usage

Set a value for a key in the map:

m := &HashMap{}
m.Set("amount", 123)

Read a value for a key from the map:

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

Use the map to count URL requests:

var i int64
actual, _ := m.GetOrInsert("api/123", &i)
counter := (actual).(*int64)
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                	  200000	      6830 ns/op
BenchmarkReadGoMapUintUnsafe-8            	  300000	      4280 ns/op
BenchmarkReadGoMapUintMutex-8             	   30000	     51294 ns/op
BenchmarkReadGoSyncMapUint-8              	  200000	     10351 ns/op

If your keys for the map are already hashes, no extra hashing needs to be done by the map:

BenchmarkReadHashMapHashedKey-8           	 1000000	      1692 ns/op

Reading from the map while writes are happening:

BenchmarkReadHashMapWithWritesUint-8      	  200000	      8395 ns/op
BenchmarkReadGoMapWithWritesUintMutex-8   	   10000	    143793 ns/op
BenchmarkReadGoSyncMapWithWritesUint-8    	  100000	     12221 ns/op

Write performance without any concurrent reads:

BenchmarkWriteHashMapUint-8               	   10000	    210383 ns/op
BenchmarkWriteGoMapMutexUint-8            	   30000	     53331 ns/op
BenchmarkWriteGoSyncMapUint-8             	   10000	    176903 ns/op

The benchmarks were run with Golang 1.10.1 on MacOS.

Benefits over Golangs builtin map

  • Faster

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

  • Compare-and-swap access for values

  • Access functions for keys that are hashes and do not need to be hashed again

Benefits over Golangs sync.Map

  • Faster

  • Access functions for keys that are hashes and do not need to be hashed again

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 doubly 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, therefor 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.

You can’t perform that action at this time.