haxxmap

package module
v0.0.0-...-3926ce0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2023 License: MIT Imports: 7 Imported by: 1

README

HaxxMap

Main Actions Status Go Report Card License

A lightning fast concurrent hashmap

This is a fork of the original haxmap package developed by alphadose. The goal of this fork is to allow for more customization of the hashmap's behavior at the expense of execution time.

The default hashing algorithm for strings used is xxHash and the hashmap's buckets are implemented using Harris lock-free list

Installation

You need Golang 1.18.x or above

$ go get github.com/aezhar/haxxmap

Usage

package main

import (
	"fmt"

	"github.com/aezhar/haxxmap"
)

func main() {
	// initialize map with key type `int` and value type `string`
	mep := haxxmap.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)

	fmt.Println("entries left: ", mep.Len())
}

Benchmarks

Benchmarks are performed against other implementations of thread-safe hashmaps:

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

  1. Concurrent Reads Only
cpu: AMD Ryzen 7 5800X 8-Core Processor
                       │   sec/op    │
HaxMapReadsOnly-16       2.627µ ± 2%
HaxxMapReadsOnly-16      3.850µ ± 0%
GoSyncMapReadsOnly-16    10.28µ ± 1%
CornelkMapReadsOnly-16   2.997µ ± 2%
XsyncMapReadsOnly-16     1.929µ ± 1%
  1. Concurrent Reads with Writes
cpu: AMD Ryzen 7 5800X 8-Core Processor
                             │   sec/op    │
HaxMapReadsWithWrites-16       3.216µ ± 7%
HaxxMapReadsWithWrites-16      3.778µ ± 4%
GoSyncMapReadsWithWrites-16    11.74µ ± 1%
CornelkMapReadsWithWrites-16   3.545µ ± 5%
XsyncMapReadsWithWrites-16     2.373µ ± 1%

                             │     B/op      │
HaxMapReadsWithWrites-16         339.5 ± 2%
HaxxMapReadsWithWrites-16        444.5 ± 2%
GoSyncMapReadsWithWrites-16    2.408Ki ± 3%
CornelkMapReadsWithWrites-16     398.5 ± 2%
XsyncMapReadsWithWrites-16       396.5 ± 3%

                             │  allocs/op  │
HaxMapReadsWithWrites-16       42.00 ± 2%
HaxxMapReadsWithWrites-16      55.00 ± 2%
GoSyncMapReadsWithWrites-16    228.5 ± 3%
CornelkMapReadsWithWrites-16   49.00 ± 2%
XsyncMapReadsWithWrites-16     24.00 ± 4%

Tips

  1. HaxxMap by default uses xxHash algorithm and compares each value directly, but this behavior can be overriden by specifying a different hash and comparison function before using the hashmap. Beneath lies an example for the same.
package main

import (
	"fmt"
	"strings"

	"github.com/aezhar/haxxmap"
)

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

// your custom comparison function
// This allows for more complex key types to be compared
func customStringCompare(l, r string) bool {
	return strings.ToLower(l) == strings.ToLower(r)
}

func main() {
	// initialize a string-string map
	m := haxxmap.New[string, string](
		// this overrides the default xxHash algorithm
		haxxmap.WithHasher[string, string](customStringHasher),
		// this overrides the default key comparison function
		haxxmap.WithComparator[string, string](customStringCompare),
	)

	m.Set("one", "1")
	m.Set("Two", "2")
	m.Set("three", "3")

	if val, ok := m.Get("One"); ok {
		fmt.Println(val)
	}

	if val, ok := m.Get("two"); ok {
		fmt.Println(val)
	}

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

import (
	"fmt"

	"github.com/aezhar/haxxmap"
)

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 := haxxmap.New[int, string](haxxmap.WithInitialSize[int, string](initialSize))

	m.Set(1, "1")

	if val, ok := m.Get(1); ok {
		fmt.Println(val)
	}
}

Documentation

Overview

Example
package main

import (
	"fmt"
	"strings"

	"github.com/aezhar/haxxmap"
)

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

// your custom comparison function
// This allows for more complex key types to be compared
func customStringCompare(l, r string) bool {
	return strings.ToLower(l) == strings.ToLower(r)
}

func main() {
	// initialize a string-string map
	m := haxxmap.New[string, string](
		// this overrides the default xxHash algorithm
		haxxmap.WithHasher[string, string](customStringHasher),
		// this overrides the default key comparison function
		haxxmap.WithComparator[string, string](customStringCompare),
	)

	m.Set("one", "1")
	m.Set("Two", "2")
	m.Set("three", "3")

	if val, ok := m.Get("One"); ok {
		fmt.Println(val)
	}

	if val, ok := m.Get("two"); ok {
		fmt.Println(val)
	}

	if val, ok := m.Get("three"); ok {
		fmt.Println(val)
	}
}
Output:

1
2
3

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EqualFn

type EqualFn[K Hashable] func(l, r K) bool

EqualFn tests whenever the 2 given keys are equal

type HashFn

type HashFn[K Hashable] func(K) uintptr

HashFn provides a hash for the given key

type Hashable

type Hashable any

type Map

type Map[K Hashable, V any] struct {
	// contains filtered or unexported fields
}

Map implements the concurrent hashmap

func New

func New[K Hashable, V any](opts ...Option[K, V]) *Map[K, V]

New returns a new HashMap instance with any specified Option

func (*Map[K, V]) CompareAndSwap

func (m *Map[K, V]) CompareAndSwap(key K, oldValue, newValue V) bool

CompareAndSwap atomically updates a map entry given its key by comparing current value to `oldValue` and setting it to `newValue` if the above comparison is successful It returns a boolean indicating whether the CompareAndSwap was successful or not

func (*Map[K, V]) Del

func (m *Map[K, V]) Del(keys ...K)

Del deletes key/keys from the map Bulk deletion is more efficient than deleting keys one by one

func (*Map[K, V]) Fillrate

func (m *Map[K, V]) Fillrate() uintptr

Fillrate returns the fill rate of the map as an percentage integer

func (*Map[K, V]) ForEach

func (m *Map[K, V]) ForEach(lambda func(K, V) bool)

ForEach iterates over key-value pairs and executes the lambda provided for each such pair lambda must return `true` to continue iteration and `false` to break iteration

func (*Map[K, V]) Get

func (m *Map[K, V]) Get(key K) (value V, ok bool)

Get retrieves an element from the map returns `false“ if element is absent

func (*Map[K, V]) GetAndDel

func (m *Map[K, V]) GetAndDel(key K) (value V, ok bool)

GetAndDel deletes the key from the map, returning the previous value if any.

func (*Map[K, V]) GetOrCompute

func (m *Map[K, V]) GetOrCompute(key K, valueFn func() V) (actual V, loaded bool)

GetOrCompute is similar to GetOrSet but the value to be set is obtained from a constructor the value constructor is called only once

func (*Map[K, V]) GetOrSet

func (m *Map[K, V]) GetOrSet(key K, value V) (actual V, loaded bool)

GetOrSet returns the existing value for the key if present Otherwise, it stores and returns the given value The loaded result is true if the value was loaded, false if stored

func (*Map[K, V]) Grow

func (m *Map[K, V]) Grow(newSize uintptr)

Grow resizes the hashmap to a new size, gets rounded up to next power of 2 To double the size of the hashmap use newSize 0 No resizing is done in case of another resize operation already being in progress Growth and map bucket policy is inspired from https://github.com/cornelk/hashmap

func (*Map[K, V]) Len

func (m *Map[K, V]) Len() uintptr

Len returns the number of key-value pairs within the map

func (*Map[K, V]) Set

func (m *Map[K, V]) Set(key K, value V)

Set tries to update an element if key is present else it inserts a new element If a resizing operation is happening concurrently while calling Set() then the item might show up in the map only after the resize operation is finished

func (*Map[K, V]) Swap

func (m *Map[K, V]) Swap(key K, newValue V) (oldValue V, swapped bool)

Swap atomically swaps the value of a map entry given its key It returns the old value if swap was successful and a boolean `swapped` indicating whether the swap was successful or not

type Option

type Option[K Hashable, V any] func(c *Options[K, V])

Option changes the behavior of the hashmap

func WithComparator

func WithComparator[K Hashable, V any](fn EqualFn[K]) Option[K, V]

WithComparator specifies the compare function to test keys for equality

func WithHasher

func WithHasher[K Hashable, V any](fn HashFn[K]) Option[K, V]

WithHasher specifies the hash function to use

func WithInitialSize

func WithInitialSize[K Hashable, V any](s uintptr) Option[K, V]

WithInitialSize specifies the initial size of the hashmap

type Options

type Options[K Hashable, V any] struct {
	// contains filtered or unexported fields
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL