Skip to content

Fastest and most memory efficient golang concurrent hashmap

License

Notifications You must be signed in to change notification settings

alphadose/haxmap

Repository files navigation

HaxMap

GoDoc Main Actions Status Go Report Card License

A lightning fast concurrent hashmap

The hashing algorithm used was xxHash and the hashmap's buckets were implemented using Harris lock-free list

Installation

You need Golang 1.18.x or above

$ go get github.com/alphadose/haxmap

Usage

package main

import (
	"fmt"

	"github.com/alphadose/haxmap"
)

func main() {
	// initialize map with key type `int` and value type `string`
	mep := haxmap.New[int, string]()

	// set a value (overwrites existing value if present)
	mep.Set(1, "one")

	// get the value and print it
	val, ok := mep.Get(1)
	if ok {
		println(val)
	}

	mep.Set(2, "two")
	mep.Set(3, "three")
	mep.Set(4, "four")

	// ForEach loop to iterate over all key-value pairs and execute the given lambda
	mep.ForEach(func(key int, value string) bool {
		fmt.Printf("Key -> %d | Value -> %s\n", key, value)
		return true // return `true` to continue iteration and `false` to break iteration
	})

	mep.Del(1) // delete a value
	mep.Del(0) // delete is safe even if a key doesn't exists

	// bulk deletion is supported too in the same API call
	// has better performance than deleting keys one by one
	mep.Del(2, 3, 4)

	if mep.Len() == 0 {
		println("cleanup complete")
	}
}

Benchmarks

Benchmarks were performed against golang sync.Map and the latest cornelk-hashmap

All results were computed from benchstat of 20 runs (code available here)

  1. Concurrent Reads Only
name                         time/op
HaxMapReadsOnly-8            6.94µs ± 4%
GoSyncMapReadsOnly-8         21.5µs ± 3%
CornelkMapReadsOnly-8        8.39µs ± 8%
  1. Concurrent Reads with Writes
name                         time/op
HaxMapReadsWithWrites-8      8.23µs ± 3%
GoSyncMapReadsWithWrites-8   25.0µs ± 2%
CornelkMapReadsWithWrites-8  8.83µs ±20%

name                         alloc/op
HaxMapReadsWithWrites-8      1.25kB ± 5%
GoSyncMapReadsWithWrites-8   6.20kB ± 7%
CornelkMapReadsWithWrites-8  1.53kB ± 9%

name                         allocs/op
HaxMapReadsWithWrites-8         156 ± 5%
GoSyncMapReadsWithWrites-8      574 ± 7%
CornelkMapReadsWithWrites-8     191 ± 9%

From the above results it is evident that haxmap takes the least time, memory and allocations in all cases making it the best golang concurrent hashmap in this period of time

Tips

  1. HaxMap by default uses xxHash algorithm, but you can override this and plug-in your own custom hash function. Beneath lies an example for the same.
package main

import (
	"github.com/alphadose/haxmap"
)

// your custom hash function
// the hash function signature must adhere to `func(keyType) uintptr`
func customStringHasher(s string) uintptr {
	return uintptr(len(s))
}

func main() {
	m := haxmap.New[string, string]() // initialize a string-string map
	m.SetHasher(customStringHasher) // this overrides the default xxHash algorithm

	m.Set("one", "1")
	val, ok := m.Get("one")
	if ok {
		println(val)
	}
}
  1. You can pre-allocate the size of the map which will improve performance in some cases.
package main

import (
	"github.com/alphadose/haxmap"
)

func main() {
	const initialSize = 1 << 10

	// pre-allocating the size of the map will prevent all grow operations
	// until that limit is hit thereby improving performance
	m := haxmap.New[int, string](initialSize)

	m.Set(1, "1")
	val, ok := m.Get(1)
	if ok {
		println(val)
	}
}