Repository: tidwall/hashmap
Branch: master
Commit: e128c7c0599b
Files: 9
Total size: 30.3 KB
Directory structure:
gitextract_w3ied2c9/
├── BENCH.md
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── map.go
├── map_test.go
├── set.go
└── set_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: BENCH.md
================================================
# Performance
While this implementation was designed with performance in mind, it's not
necessarily better, faster, smarter, or sexier than the built-in Go hashmap.
Here's a very rough comparison.
The following benchmarks were run on a Linux Desktop (3.8 GHz 8-Core AMD Ryzen 7 5800X) using Go version 1.19. The key types are either strings or ints and the values are always ints.
In all cases the maps start from zero capacity, like:
```go
m := make(map[string]int) // go stdlib
var m hashmap.Map[string, int] // this package
```
```shell
MAPBENCH=100000 go test
```
## 100,000 random string keys
```shell
## STRING KEYS
-- tidwall --
set 100,000 ops 15ms 6,535,956/sec
get 100,000 ops 7ms 15,009,648/sec
reset 100,000 ops 5ms 20,811,745/sec
scan 20 ops 8ms 2,539/sec
delete 100,000 ops 7ms 14,557,933/sec
memory 4,194,288 bytes 41/entry
-- stdlib --
set 100,000 ops 17ms 5,892,223/sec
get 100,000 ops 8ms 12,148,359/sec
reset 100,000 ops 4ms 24,779,419/sec
scan 20 ops 14ms 1,395/sec
delete 100,000 ops 8ms 11,915,708/sec
memory 3,966,288 bytes 39/entry
```
## 100,000 random int keys
```shell
## INT KEYS
-- tidwall --
set 100,000 ops 8ms 12,573,069/sec
get 100,000 ops 4ms 24,821,181/sec
reset 100,000 ops 4ms 25,324,412/sec
scan 20 ops 8ms 2,430/sec
delete 100,000 ops 5ms 22,156,034/sec
memory 3,143,352 bytes 31/entry
-- stdlib --
set 100,000 ops 7ms 13,547,121/sec
get 100,000 ops 4ms 26,458,302/sec
reset 100,000 ops 4ms 28,379,163/sec
scan 20 ops 16ms 1,214/sec
delete 100,000 ops 4ms 24,771,495/sec
memory 2,784,264 bytes 27/entry
```
## 1,000,000 random string keys
```shell
## STRING KEYS
-- tidwall --
set 1,000,000 ops 217ms 4,607,387/sec
get 1,000,000 ops 127ms 7,872,817/sec
reset 1,000,000 ops 130ms 7,709,027/sec
scan 20 ops 136ms 147/sec
delete 1,000,000 ops 149ms 6,716,045/sec
memory 67,108,848 bytes 67/entry
-- stdlib --
set 1,000,000 ops 325ms 3,078,132/sec
get 1,000,000 ops 122ms 8,217,771/sec
reset 1,000,000 ops 133ms 7,510,273/sec
scan 20 ops 163ms 122/sec
delete 1,000,000 ops 148ms 6,761,332/sec
memory 57,931,472 bytes 57/entry
```
## 1,000,000 random int keys
```shell
## INT KEYS
-- tidwall --
set 1,000,000 ops 101ms 9,901,395/sec
get 1,000,000 ops 63ms 15,928,770/sec
reset 1,000,000 ops 66ms 15,107,262/sec
scan 20 ops 139ms 144/sec
delete 1,000,000 ops 66ms 15,216,322/sec
memory 50,329,272 bytes 50/entry
-- stdlib --
set 1,000,000 ops 119ms 8,431,961/sec
get 1,000,000 ops 61ms 16,376,595/sec
reset 1,000,000 ops 59ms 17,032,395/sec
scan 20 ops 153ms 130/sec
delete 1,000,000 ops 67ms 15,026,654/sec
memory 40,146,760 bytes 40/entry
```
## 10,000,000 random string keys (int values)
```shell
## STRING KEYS
-- tidwall --
set 10,000,000 ops 2584ms 3,869,389/sec
get 10,000,000 ops 1418ms 7,051,328/sec
reset 10,000,000 ops 1469ms 6,807,487/sec
scan 20 ops 1049ms 19/sec
delete 10,000,000 ops 1694ms 5,901,787/sec
memory 536,870,896 bytes 53/entry
-- stdlib --
set 10,000,000 ops 3771ms 2,651,828/sec
get 10,000,000 ops 1494ms 6,695,021/sec
reset 10,000,000 ops 1480ms 6,758,881/sec
scan 20 ops 1855ms 10/sec
delete 10,000,000 ops 1629ms 6,138,209/sec
memory 463,468,240 bytes 46/entry
```
## 10,000,000 random int keys (int values)
```shell
## INT KEYS
-- tidwall --
set 10,000,000 ops 1428ms 7,002,173/sec
get 10,000,000 ops 733ms 13,636,196/sec
reset 10,000,000 ops 787ms 12,710,144/sec
scan 20 ops 1098ms 18/sec
delete 10,000,000 ops 900ms 11,108,541/sec
memory 402,650,808 bytes 40/entry
-- stdlib --
set 10,000,000 ops 1709ms 5,850,969/sec
get 10,000,000 ops 797ms 12,551,221/sec
reset 10,000,000 ops 874ms 11,437,820/sec
scan 20 ops 1629ms 12/sec
delete 10,000,000 ops 910ms 10,994,436/sec
memory 321,976,032 bytes 32/entry
```
================================================
FILE: LICENSE
================================================
Copyright 2019, Joshua J Baker
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: README.md
================================================
# hashmap
[](https://godoc.org/github.com/tidwall/hashmap)
An [efficient](BENCH.md) hashmap implementation in Go.
## Features
- Support for [Generics](#generics).
- `Map` and `Set` types for unordered key-value maps and sets,
- [xxh3 algorithm](https://github.com/zeebo/xxh3)
- [Open addressing](https://en.wikipedia.org/wiki/Hash_table#Open_addressing) with [Robin hood hashing](https://en.wikipedia.org/wiki/Hash_table#Robin_Hood_hashing)
- Automatically shinks memory on deletes (no memory leaks).
- Pretty darn good performance. 🚀 ([benchmarks](BENCH.md)).
For ordered key-value data, check out the [tidwall/btree](https://github.com/tidwall/btree) package.
## Getting Started
### Installing
To start using `hashmap`, install Go and run `go get`:
```sh
go get github.com/tidwall/hashmap
```
This will retrieve the library.
## Usage
The `Map` type works similar to a standard Go map, and includes the methods:
`Set`, `Get`, `Delete`, `Len`, `Scan`, `Keys`, `Values`, and `Copy`.
```go
var m hashmap.Map[string, string]
m.Set("Hello", "Dolly!")
val, _ := m.Get("Hello")
fmt.Printf("%v\n", val)
val, _ = m.Delete("Hello")
fmt.Printf("%v\n", val)
val, _ = m.Get("Hello")
fmt.Printf("%v\n", val)
// Output:
// Dolly!
// Dolly!
//
```
The `Set` type is like `Map` but only for keys.
It includes the methods: `Insert`, `Contains`, `Delete`, `Len`, `Scan` and `Keys`.
```go
var m hashmap.Set[string]
m.Insert("Andy")
m.Insert("Kate")
m.Insert("Janet")
fmt.Printf("%v\n", m.Contains("Kate"))
fmt.Printf("%v\n", m.Contains("Bob"))
fmt.Printf("%v\n", m.Contains("Andy"))
// Output:
// true
// false
// true
```
## Performance
See [BENCH.md](BENCH.md) for more info.
## Contact
Josh Baker [@tidwall](http://twitter.com/tidwall)
## License
Source code is available under the MIT [License](LICENSE).
================================================
FILE: go.mod
================================================
module github.com/tidwall/hashmap
go 1.18
require github.com/zeebo/xxh3 v1.0.2
require github.com/klauspost/cpuid/v2 v2.0.9 // indirect
================================================
FILE: go.sum
================================================
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
================================================
FILE: map.go
================================================
// Copyright 2019 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an ISC-style
// license that can be found in the LICENSE file.
package hashmap
import (
"unsafe"
"github.com/zeebo/xxh3"
)
const (
loadFactor = 0.85 // must be above 50%
dibBitSize = 16 // 0xFFFF
hashBitSize = 64 - dibBitSize // 0xFFFFFFFFFFFF
maxHash = ^uint64(0) >> dibBitSize // max 28,147,497,671,0655
maxDIB = ^uint64(0) >> hashBitSize // max 65,535
)
type entry[K comparable, V any] struct {
hdib uint64 // bitfield { hash:48 dib:16 }
value V // user value
key K // user key
}
func (e *entry[K, V]) dib() int {
return int(e.hdib & maxDIB)
}
func (e *entry[K, V]) hash() int {
return int(e.hdib >> dibBitSize)
}
func (e *entry[K, V]) setDIB(dib int) {
e.hdib = e.hdib>>dibBitSize<<dibBitSize | uint64(dib)&maxDIB
}
func (e *entry[K, V]) setHash(hash int) {
e.hdib = uint64(hash)<<dibBitSize | e.hdib&maxDIB
}
func makeHDIB(hash, dib int) uint64 {
return uint64(hash)<<dibBitSize | uint64(dib)&maxDIB
}
// hash returns a 48-bit hash for 64-bit environments, or 32-bit hash for
// 32-bit environments.
func (m *Map[K, V]) hash(key K) int {
// The unsafe package is used here to cast the key into a string container
// so that the hasher can work. The hasher normally only accept a string or
// []byte, but this effectively allows it to accept value type.
// The m.kstr bool, which is set from the New function, indicates that the
// key is known to already be a true string. Otherwise, a fake string is
// derived by setting the string data to value of the key, and the string
// length to the size of the value.
var strKey string
if m.kstr {
strKey = *(*string)(unsafe.Pointer(&key))
} else {
strKey = *(*string)(unsafe.Pointer(&struct {
data unsafe.Pointer
len int
}{unsafe.Pointer(&key), m.ksize}))
}
// Now for the actual hashing.
return int(xxh3.HashString(strKey) >> dibBitSize)
}
// Map is a hashmap. Like map[string]interface{}
type Map[K comparable, V any] struct {
cap int
length int
mask int
growAt int
shrinkAt int
buckets []entry[K, V]
ksize int
kstr bool
}
// New returns a new Map. Like map[string]interface{}
func New[K comparable, V any](cap int) *Map[K, V] {
m := new(Map[K, V])
m.cap = cap
sz := 8
for sz < m.cap {
sz *= 2
}
if m.cap > 0 {
m.cap = sz
}
m.buckets = make([]entry[K, V], sz)
m.mask = len(m.buckets) - 1
m.growAt = int(float64(len(m.buckets)) * loadFactor)
m.shrinkAt = int(float64(len(m.buckets)) * (1 - loadFactor))
m.detectHasher()
return m
}
func (m *Map[K, V]) detectHasher() {
// Detect the key type. This is needed by the hasher.
var k K
switch ((interface{})(k)).(type) {
case string:
m.kstr = true
default:
m.ksize = int(unsafe.Sizeof(k))
}
}
func (m *Map[K, V]) resize(newCap int) {
nmap := New[K, V](newCap)
for i := 0; i < len(m.buckets); i++ {
if m.buckets[i].dib() > 0 {
nmap.set(m.buckets[i].hash(), m.buckets[i].key, m.buckets[i].value)
}
}
cap := m.cap
*m = *nmap
m.cap = cap
}
// Set assigns a value to a key.
// Returns the previous value, or false when no value was assigned.
func (m *Map[K, V]) Set(key K, value V) (V, bool) {
if len(m.buckets) == 0 {
*m = *New[K, V](0)
}
if m.length >= m.growAt {
m.resize(len(m.buckets) * 2)
}
return m.set(m.hash(key), key, value)
}
func (m *Map[K, V]) set(hash int, key K, value V) (prev V, ok bool) {
e := entry[K, V]{makeHDIB(hash, 1), value, key}
i := e.hash() & m.mask
for {
if m.buckets[i].dib() == 0 {
m.buckets[i] = e
m.length++
return prev, false
}
if e.hash() == m.buckets[i].hash() && e.key == m.buckets[i].key {
prev = m.buckets[i].value
m.buckets[i].value = e.value
return prev, true
}
if m.buckets[i].dib() < e.dib() {
e, m.buckets[i] = m.buckets[i], e
}
i = (i + 1) & m.mask
e.setDIB(e.dib() + 1)
}
}
// Get returns a value for a key.
// Returns false when no value has been assign for key.
func (m *Map[K, V]) Get(key K) (value V, ok bool) {
if len(m.buckets) == 0 {
return value, false
}
hash := m.hash(key)
i := hash & m.mask
for {
if m.buckets[i].dib() == 0 {
return value, false
}
if m.buckets[i].hash() == hash && m.buckets[i].key == key {
return m.buckets[i].value, true
}
i = (i + 1) & m.mask
}
}
// Len returns the number of values in map.
func (m *Map[K, V]) Len() int {
return m.length
}
// Delete deletes a value for a key.
// Returns the deleted value, or false when no value was assigned.
func (m *Map[K, V]) Delete(key K) (prev V, deleted bool) {
if len(m.buckets) == 0 {
return prev, false
}
hash := m.hash(key)
i := hash & m.mask
for {
if m.buckets[i].dib() == 0 {
return prev, false
}
if m.buckets[i].hash() == hash && m.buckets[i].key == key {
prev = m.buckets[i].value
m.remove(i)
return prev, true
}
i = (i + 1) & m.mask
}
}
func (m *Map[K, V]) remove(i int) {
m.buckets[i].setDIB(0)
for {
pi := i
i = (i + 1) & m.mask
if m.buckets[i].dib() <= 1 {
m.buckets[pi] = entry[K, V]{}
break
}
m.buckets[pi] = m.buckets[i]
m.buckets[pi].setDIB(m.buckets[pi].dib() - 1)
}
m.length--
if len(m.buckets) > m.cap && m.length <= m.shrinkAt {
m.resize(m.length)
}
}
// Scan iterates over all key/values.
// It's not safe to call or Set or Delete while scanning.
func (m *Map[K, V]) Scan(iter func(key K, value V) bool) {
for i := 0; i < len(m.buckets); i++ {
if m.buckets[i].dib() > 0 {
if !iter(m.buckets[i].key, m.buckets[i].value) {
return
}
}
}
}
// Keys returns all keys as a slice
func (m *Map[K, V]) Keys() []K {
keys := make([]K, 0, m.length)
for i := 0; i < len(m.buckets); i++ {
if m.buckets[i].dib() > 0 {
keys = append(keys, m.buckets[i].key)
}
}
return keys
}
// Values returns all values as a slice
func (m *Map[K, V]) Values() []V {
values := make([]V, 0, m.length)
for i := 0; i < len(m.buckets); i++ {
if m.buckets[i].dib() > 0 {
values = append(values, m.buckets[i].value)
}
}
return values
}
// Copy the hashmap.
func (m *Map[K, V]) Copy() *Map[K, V] {
m2 := new(Map[K, V])
*m2 = *m
m2.buckets = make([]entry[K, V], len(m.buckets))
copy(m2.buckets, m.buckets)
return m2
}
// GetPos gets a single keys/value nearby a position.
// The pos param can be any valid uint64. Useful for grabbing a random item
// from the map.
func (m *Map[K, V]) GetPos(pos uint64) (key K, value V, ok bool) {
for i := 0; i < len(m.buckets); i++ {
index := (pos + uint64(i)) & uint64(m.mask)
if m.buckets[index].dib() > 0 {
return m.buckets[index].key, m.buckets[index].value, true
}
}
// Empty map
return key, value, false
}
================================================
FILE: map_test.go
================================================
// Copyright 2019 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an ISC-style
// license that can be found in the LICENSE file.
package hashmap
import (
"fmt"
"math/rand"
"os"
"reflect"
"runtime"
"sort"
"strconv"
"sync"
"testing"
"time"
)
type keyT = string
type valueT = interface{}
func k(key int) keyT {
return strconv.FormatInt(int64(key), 10)
}
func add(x keyT, delta int) int {
i, err := strconv.ParseInt(x, 10, 64)
if err != nil {
panic(err)
}
return int(i + int64(delta))
}
// /////////////////////////
func random(N int, perm bool) []keyT {
nums := make([]keyT, N)
if perm {
for i, x := range rand.Perm(N) {
nums[i] = k(x)
}
} else {
m := make(map[keyT]bool)
for len(m) < N {
m[k(int(rand.Uint64()))] = true
}
var i int
for k := range m {
nums[i] = k
i++
}
}
return nums
}
func shuffle[K comparable](nums []K) {
for i := range nums {
j := rand.Intn(i + 1)
nums[i], nums[j] = nums[j], nums[i]
}
}
func init() {
//var seed int64 = 1519776033517775607
seed := (time.Now().UnixNano())
println("seed:", seed)
rand.Seed(seed)
}
type imap struct {
m *Map[string, interface{}]
}
func newimap(cap int) *imap {
m := new(imap)
m.m = New[string, interface{}](cap)
return m
}
func (m *imap) Get(key string) (interface{}, bool) {
if m.m == nil {
return nil, false
}
return m.m.Get(key)
}
func (m *imap) Set(key string, value interface{}) (interface{}, bool) {
if m.m == nil {
m.m = new(Map[string, interface{}])
}
return m.m.Set(key, value)
}
func (m *imap) Delete(key string) (interface{}, bool) {
if m.m == nil {
return nil, false
}
return m.m.Delete(key)
}
func (m *imap) Len() int {
if m.m == nil {
return 0
}
return m.m.Len()
}
func (m *imap) Scan(iter func(key string, value interface{}) bool) {
if m.m == nil {
return
}
m.m.Scan(iter)
}
func TestRandomData(t *testing.T) {
N := 10000
start := time.Now()
for time.Since(start) < time.Second*2 {
nums := random(N, true)
var m *imap
switch rand.Int() % 5 {
default:
m = newimap(N / ((rand.Int() % 3) + 1))
case 1:
m = new(imap)
case 2:
m = newimap(0)
}
v, ok := m.Get(k(999))
if ok || v != nil {
t.Fatalf("expected %v, got %v", nil, v)
}
v, ok = m.Delete(k(999))
if ok || v != nil {
t.Fatalf("expected %v, got %v", nil, v)
}
if m.Len() != 0 {
t.Fatalf("expected %v, got %v", 0, m.Len())
}
// set a bunch of items
for i := 0; i < len(nums); i++ {
v, ok := m.Set(nums[i], nums[i])
if ok || v != nil {
t.Fatalf("expected %v, got %v", nil, v)
}
}
if m.Len() != N {
t.Fatalf("expected %v, got %v", N, m.Len())
}
// retrieve all the items
shuffle(nums)
for i := 0; i < len(nums); i++ {
v, ok := m.Get(nums[i])
if !ok || v == nil || v != nums[i] {
t.Fatalf("expected %v, got %v", nums[i], v)
}
}
// replace all the items
shuffle(nums)
for i := 0; i < len(nums); i++ {
v, ok := m.Set(nums[i], add(nums[i], 1))
if !ok || v != nums[i] {
t.Fatalf("expected %v, got %v", nums[i], v)
}
}
if m.Len() != N {
t.Fatalf("expected %v, got %v", N, m.Len())
}
// retrieve all the items
shuffle(nums)
for i := 0; i < len(nums); i++ {
v, ok := m.Get(nums[i])
if !ok || v != add(nums[i], 1) {
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
}
}
// remove half the items
shuffle(nums)
for i := 0; i < len(nums)/2; i++ {
v, ok := m.Delete(nums[i])
if !ok || v != add(nums[i], 1) {
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
}
}
if m.Len() != N/2 {
t.Fatalf("expected %v, got %v", N/2, m.Len())
}
// check to make sure that the items have been removed
for i := 0; i < len(nums)/2; i++ {
v, ok := m.Get(nums[i])
if ok || v != nil {
t.Fatalf("expected %v, got %v", nil, v)
}
}
// check the second half of the items
for i := len(nums) / 2; i < len(nums); i++ {
v, ok := m.Get(nums[i])
if !ok || v != add(nums[i], 1) {
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
}
}
// try to delete again, make sure they don't exist
for i := 0; i < len(nums)/2; i++ {
v, ok := m.Delete(nums[i])
if ok || v != nil {
t.Fatalf("expected %v, got %v", nil, v)
}
}
if m.Len() != N/2 {
t.Fatalf("expected %v, got %v", N/2, m.Len())
}
m.Scan(func(key keyT, value valueT) bool {
if value != add(key, 1) {
t.Fatalf("expected %v, got %v", add(key, 1), value)
}
return true
})
var n int
m.Scan(func(key keyT, value valueT) bool {
n++
return false
})
if n != 1 {
t.Fatalf("expected %v, got %v", 1, n)
}
for i := len(nums) / 2; i < len(nums); i++ {
v, ok := m.Delete(nums[i])
if !ok || v != add(nums[i], 1) {
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
}
}
}
}
func TestBench(t *testing.T) {
N, _ := strconv.ParseUint(os.Getenv("MAPBENCH"), 10, 64)
if N == 0 {
fmt.Printf("Enable benchmarks with MAPBENCH=1000000\n")
return
}
var pnums []int
for i := 0; i < int(N); i++ {
pnums = append(pnums, i)
}
{
fmt.Printf("\n## STRING KEYS\n\n")
nums := random(int(N), false)
t.Run("Tidwall", func(t *testing.T) {
testPerf(nums, pnums, "tidwall")
})
t.Run("Stdlib", func(t *testing.T) {
testPerf(nums, pnums, "stdlib")
})
}
{
fmt.Printf("\n## INT KEYS\n\n")
nums := rand.Perm(int(N))
t.Run("Tidwall", func(t *testing.T) {
testPerf(nums, pnums, "tidwall")
})
t.Run("Stdlib", func(t *testing.T) {
testPerf(nums, pnums, "stdlib")
})
}
}
func printItem(s string, size int, dir int) {
for len(s) < size {
if dir == -1 {
s += " "
} else {
s = " " + s
}
}
fmt.Printf("%s ", s)
}
func testPerf[K comparable, V any](nums []K, pnums []V, which string) {
var ms1, ms2 runtime.MemStats
initSize := 0 //len(nums) * 2
defer func() {
heapBytes := int(ms2.HeapAlloc - ms1.HeapAlloc)
fmt.Printf("memory %13s bytes %19s/entry \n",
commaize(heapBytes), commaize(heapBytes/len(nums)))
fmt.Printf("\n")
}()
runtime.GC()
time.Sleep(time.Millisecond * 100)
runtime.ReadMemStats(&ms1)
var setop, getop, delop func(int, int)
var scnop func()
switch which {
case "stdlib":
m := make(map[K]V, initSize)
setop = func(i, _ int) { m[nums[i]] = pnums[i] }
getop = func(i, _ int) { _ = m[nums[i]] }
delop = func(i, _ int) { delete(m, nums[i]) }
scnop = func() {
for range m {
}
}
case "tidwall":
var m Map[K, V]
setop = func(i, _ int) { m.Set(nums[i], pnums[i]) }
getop = func(i, _ int) { m.Get(nums[i]) }
delop = func(i, _ int) { m.Delete(nums[i]) }
scnop = func() {
m.Scan(func(key K, value V) bool {
return true
})
}
}
fmt.Printf("-- %s --", which)
fmt.Printf("\n")
ops := []func(int, int){setop, getop, setop, nil, delop}
tags := []string{"set", "get", "reset", "scan", "delete"}
for i := range ops {
shuffle(nums)
var na bool
var n int
start := time.Now()
if tags[i] == "scan" {
op := scnop
if op == nil {
na = true
} else {
n = 20
for i := 0; i < n; i++ {
op()
}
}
} else {
n = len(nums)
for j := 0; j < n; j++ {
ops[i](j, 1)
}
}
dur := time.Since(start)
if i == 0 {
runtime.GC()
time.Sleep(time.Millisecond * 100)
runtime.ReadMemStats(&ms2)
}
printItem(tags[i], 9, -1)
if na {
printItem("-- unavailable --", 14, 1)
} else {
if n == -1 {
printItem("unknown ops", 14, 1)
} else {
printItem(fmt.Sprintf("%s ops", commaize(n)), 14, 1)
}
printItem(fmt.Sprintf("%.0fms", dur.Seconds()*1000), 8, 1)
if n != -1 {
printItem(fmt.Sprintf("%s/sec", commaize(int(float64(n)/dur.Seconds()))), 18, 1)
}
}
fmt.Printf("\n")
}
}
func commaize(n int) string {
s1, s2 := fmt.Sprintf("%d", n), ""
for i, j := len(s1)-1, 0; i >= 0; i, j = i-1, j+1 {
if j%3 == 0 && j != 0 {
s2 = "," + s2
}
s2 = string(s1[i]) + s2
}
return s2
}
func TestHashDIB(t *testing.T) {
var e entry[string, interface{}]
e.setDIB(100)
e.setHash(90000)
if e.dib() != 100 {
t.Fatalf("expected %v, got %v", 100, e.dib())
}
if e.hash() != 90000 {
t.Fatalf("expected %v, got %v", 90000, e.hash())
}
}
func TestIntInt(t *testing.T) {
var m Map[int, int]
keys := rand.Perm(1000000)
for i := 0; i < len(keys); i++ {
_, ok := m.Set(keys[i], keys[i]*10)
if ok {
t.Fatalf("expected false")
}
if m.Len() != i+1 {
t.Fatalf("expected %d got %d", i+1, m.Len())
}
}
for i := 0; i < len(keys); i++ {
v, ok := m.Get(keys[i])
if !ok {
t.Fatalf("expected true")
}
if v != keys[i]*10 {
t.Fatalf("expected %d got %d", keys[i]*10, v)
}
}
for i := 0; i < len(keys); i++ {
v, ok := m.Delete(keys[i])
if !ok {
t.Fatalf("expected true")
}
if v != keys[i]*10 {
t.Fatalf("expected %d got %d", keys[i]*10, v)
}
if m.Len() != len(keys)-i-1 {
t.Fatalf("expected %d got %d", len(keys)-i-1, m.Len())
}
}
}
func TestMapValues(t *testing.T) {
var m Map[int, int]
m.Set(1, 2)
expect := []int{2}
got := m.Values()
if !reflect.DeepEqual(got, expect) {
t.Fatal("expected Values equal")
}
}
func copyMapEntries(m *Map[int, int]) []entry[int, int] {
all := make([]entry[int, int], m.Len())
keys := m.Keys()
vals := m.Values()
for i := 0; i < len(keys); i++ {
all[i].key = keys[i]
all[i].value = vals[i]
}
sort.Slice(all, func(i, j int) bool {
return all[i].key < all[j].key
})
return all
}
func mapEntriesEqual(a, b []entry[int, int]) bool {
return reflect.DeepEqual(a, b)
}
func copyMapTest(N int, m1 *Map[int, int], e11 []entry[int, int], deep bool) {
e12 := copyMapEntries(m1)
if !mapEntriesEqual(e11, e12) {
panic("!")
}
// Make a copy and compare the values
m2 := m1.Copy()
e21 := copyMapEntries(m1)
if !mapEntriesEqual(e21, e12) {
panic("!")
}
// Delete every other key
var e22 []entry[int, int]
for i, j := range rand.Perm(N) {
if i&1 == 0 {
e22 = append(e22, e21[j])
} else {
prev, deleted := m2.Delete(e21[j].key)
if !deleted {
panic("!")
}
if prev != e21[j].value {
panic("!")
}
}
}
if m2.Len() != N/2 {
panic("!")
}
sort.Slice(e22, func(i, j int) bool {
return e22[i].key < e22[j].key
})
e23 := copyMapEntries(m2)
if !mapEntriesEqual(e23, e22) {
panic("!")
}
if !deep {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
copyMapTest(N/2, m2, e23, true)
}()
go func() {
defer wg.Done()
copyMapTest(N/2, m2, e23, true)
}()
wg.Wait()
}
e24 := copyMapEntries(m2)
if !mapEntriesEqual(e24, e23) {
panic("!")
}
}
func TestMapCopy(t *testing.T) {
N := 1_000
// create the initial map
m1 := New[int, int](0)
for m1.Len() < N {
m1.Set(rand.Int(), rand.Int())
}
e11 := copyMapEntries(m1)
dur := time.Second * 2
var wg sync.WaitGroup
for i := 0; i < 16; i++ {
wg.Add(1)
go func() {
defer wg.Done()
start := time.Now()
for time.Since(start) < dur {
copyMapTest(N, m1, e11, false)
}
}()
}
wg.Wait()
e12 := copyMapEntries(m1)
if !mapEntriesEqual(e11, e12) {
panic("!")
}
}
func TestEmpty(t *testing.T) {
var m Map[int, int]
if _, ok := m.Get(0); ok {
t.Fatal()
}
if _, ok := m.Delete(0); ok {
t.Fatal()
}
}
func TestGetPos(t *testing.T) {
var m Map[int, int]
if _, _, ok := m.GetPos(100); ok {
t.Fatal()
}
for i := 0; i < 1000; i++ {
m.Set(i, i+1)
}
m2 := make(map[int]int)
for i := 0; i < 10000; i++ {
key, val, ok := m.GetPos(uint64(i))
if !ok {
t.Fatal()
}
m2[key] = val
}
if len(m2) != m.Len() {
t.Fatal()
}
}
func TestIssue3(t *testing.T) {
m := New[string, int](50)
m.Set("key:808943", 1)
m.Set("key:5834", 2)
m.Set("key:51630", 3)
m.Set("key:49504", 4)
m.Set("key:346528", 5)
m.Set("key:189743", 6)
m.Set("key:4112608", 7)
m.Set("key:21749", 8)
m.Set("key:844131", 9)
if v, _ := m.Delete("key:844131"); v != 9 {
t.Fatal()
}
if _, ok := m.Get("key:844131"); ok {
t.Fatal()
}
for j := 0; j < 1000; j++ {
m = New[string, int](50)
keys := make([]string, j)
for i := 0; i < len(keys); i++ {
keys[i] = fmt.Sprintf("key:%d", i)
m.Set(keys[i], i)
}
for i := 0; i < len(keys); i++ {
if v, _ := m.Get(keys[i]); v != i {
t.Fatal()
}
if v, _ := m.Delete(keys[i]); v != i {
t.Fatal()
}
if _, ok := m.Get(keys[i]); ok {
t.Fatal()
}
}
}
}
================================================
FILE: set.go
================================================
package hashmap
type Set[K comparable] struct {
base Map[K, struct{}]
}
// Insert an item
func (tr *Set[K]) Insert(key K) {
tr.base.Set(key, struct{}{})
}
// Get a value for key
func (tr *Set[K]) Contains(key K) bool {
_, ok := tr.base.Get(key)
return ok
}
// Len returns the number of items in the tree
func (tr *Set[K]) Len() int {
return tr.base.Len()
}
// Delete an item
func (tr *Set[K]) Delete(key K) {
tr.base.Delete(key)
}
func (tr *Set[K]) Scan(iter func(key K) bool) {
tr.base.Scan(func(key K, value struct{}) bool {
return iter(key)
})
}
// Keys returns all keys as a slice
func (tr *Set[K]) Keys() []K {
return tr.base.Keys()
}
// Copy the set. This is a copy-on-write operation and is very fast because
// it only performs a shadow copy.
func (tr *Set[K]) Copy() *Set[K] {
tr2 := new(Set[K])
tr2.base = *tr.base.Copy()
return tr2
}
// GetPos gets a single keys/value nearby a position.
// The pos param can be any valid uint64. Useful for grabbing a random item
// from the Set.
func (s *Set[K]) GetPos(pos uint64) (key K, ok bool) {
key, _, ok = s.base.GetPos(pos)
return key, ok
}
================================================
FILE: set_test.go
================================================
package hashmap
import (
"math/rand"
"reflect"
"sort"
"sync"
"testing"
"time"
)
func TestSet(t *testing.T) {
var s Set[int]
keys := rand.Perm(1000000)
for i := 0; i < len(keys); i++ {
s.Insert(keys[i])
if s.Len() != i+1 {
t.Fatalf("expected %d got %d", i+1, s.Len())
}
}
for i := 0; i < len(keys); i++ {
ok := s.Contains(keys[i])
if !ok {
t.Fatalf("expected true")
}
}
var skeys []int
s.Scan(func(key int) bool {
skeys = append(skeys, key)
return true
})
if len(skeys) != s.Len() {
t.Fatalf("expected %d got %d", len(skeys), s.Len())
}
for i := 0; i < len(keys); i++ {
s.Delete(keys[i])
if s.Len() != len(keys)-i-1 {
t.Fatalf("expected %d got %d", len(keys)-i-1, s.Len())
}
}
}
func TestSetKeys(t *testing.T) {
var s Set[string]
s.Insert("key")
expect := []string{"key"}
got := s.Keys()
if !reflect.DeepEqual(got, expect) {
t.Fatal("expected Keys equal")
}
}
func copySetEntries(m *Set[int]) []int {
all := m.Keys()
sort.Ints(all)
return all
}
func setEntriesEqual(a, b []int) bool {
return reflect.DeepEqual(a, b)
}
func copySetTest(N int, s1 *Set[int], e11 []int, deep bool) {
e12 := copySetEntries(s1)
if !setEntriesEqual(e11, e12) {
panic("!")
}
// Make a copy and compare the values
s2 := s1.Copy()
e21 := copySetEntries(s1)
if !setEntriesEqual(e21, e12) {
panic("!")
}
// Delete every other key
var e22 []int
for i, j := range rand.Perm(N) {
if i&1 == 0 {
e22 = append(e22, e21[j])
} else {
s2.Delete(e21[j])
}
}
if s2.Len() != N/2 {
panic("!")
}
sort.Ints(e22)
e23 := copySetEntries(s2)
if !setEntriesEqual(e23, e22) {
panic("!")
}
if !deep {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
copySetTest(N/2, s2, e23, true)
}()
go func() {
defer wg.Done()
copySetTest(N/2, s2, e23, true)
}()
wg.Wait()
}
e24 := copySetEntries(s2)
if !setEntriesEqual(e24, e23) {
panic("!")
}
}
func TestSetCopy(t *testing.T) {
N := 1_000
// create the initial map
s1 := new(Set[int])
for s1.Len() < N {
s1.Insert(rand.Int())
}
e11 := copySetEntries(s1)
dur := time.Second * 2
var wg sync.WaitGroup
for i := 0; i < 16; i++ {
wg.Add(1)
go func() {
defer wg.Done()
start := time.Now()
for time.Since(start) < dur {
copySetTest(N, s1, e11, false)
}
}()
}
wg.Wait()
e12 := copySetEntries(s1)
if !setEntriesEqual(e11, e12) {
panic("!")
}
}
func TestSetGetPos(t *testing.T) {
var m Set[int]
if _, ok := m.GetPos(100); ok {
t.Fatal()
}
for i := 0; i < 1000; i++ {
m.Insert(i)
}
m2 := make(map[int]int)
for i := 0; i < 10000; i++ {
key, ok := m.GetPos(uint64(i))
if !ok {
t.Fatal()
}
m2[key] = key
}
if len(m2) != m.Len() {
t.Fatal()
}
}
gitextract_w3ied2c9/ ├── BENCH.md ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── map.go ├── map_test.go ├── set.go └── set_test.go
SYMBOL INDEX (70 symbols across 4 files)
FILE: map.go
constant loadFactor (line 14) | loadFactor = 0.85
constant dibBitSize (line 15) | dibBitSize = 16
constant hashBitSize (line 16) | hashBitSize = 64 - dibBitSize
constant maxHash (line 17) | maxHash = ^uint64(0) >> dibBitSize
constant maxDIB (line 18) | maxDIB = ^uint64(0) >> hashBitSize
type entry (line 21) | type entry struct
method dib (line 27) | func (e *entry[K, V]) dib() int {
method hash (line 30) | func (e *entry[K, V]) hash() int {
method setDIB (line 33) | func (e *entry[K, V]) setDIB(dib int) {
method setHash (line 36) | func (e *entry[K, V]) setHash(hash int) {
function makeHDIB (line 39) | func makeHDIB(hash, dib int) uint64 {
method hash (line 45) | func (m *Map[K, V]) hash(key K) int {
type Map (line 67) | type Map struct
function New (line 79) | func New[K comparable, V any](cap int) *Map[K, V] {
method detectHasher (line 97) | func (m *Map[K, V]) detectHasher() {
method resize (line 108) | func (m *Map[K, V]) resize(newCap int) {
method Set (line 122) | func (m *Map[K, V]) Set(key K, value V) (V, bool) {
method set (line 132) | func (m *Map[K, V]) set(hash int, key K, value V) (prev V, ok bool) {
method Get (line 156) | func (m *Map[K, V]) Get(key K) (value V, ok bool) {
method Len (line 174) | func (m *Map[K, V]) Len() int {
method Delete (line 180) | func (m *Map[K, V]) Delete(key K) (prev V, deleted bool) {
method remove (line 199) | func (m *Map[K, V]) remove(i int) {
method Scan (line 219) | func (m *Map[K, V]) Scan(iter func(key K, value V) bool) {
method Keys (line 230) | func (m *Map[K, V]) Keys() []K {
method Values (line 241) | func (m *Map[K, V]) Values() []V {
method Copy (line 252) | func (m *Map[K, V]) Copy() *Map[K, V] {
method GetPos (line 263) | func (m *Map[K, V]) GetPos(pos uint64) (key K, value V, ok bool) {
FILE: map_test.go
function k (line 23) | func k(key int) keyT {
function add (line 27) | func add(x keyT, delta int) int {
function random (line 36) | func random(N int, perm bool) []keyT {
function shuffle (line 56) | func shuffle[K comparable](nums []K) {
function init (line 63) | func init() {
type imap (line 70) | type imap struct
method Get (line 80) | func (m *imap) Get(key string) (interface{}, bool) {
method Set (line 86) | func (m *imap) Set(key string, value interface{}) (interface{}, bool) {
method Delete (line 92) | func (m *imap) Delete(key string) (interface{}, bool) {
method Len (line 98) | func (m *imap) Len() int {
method Scan (line 104) | func (m *imap) Scan(iter func(key string, value interface{}) bool) {
function newimap (line 74) | func newimap(cap int) *imap {
function TestRandomData (line 111) | func TestRandomData(t *testing.T) {
function TestBench (line 231) | func TestBench(t *testing.T) {
function printItem (line 266) | func printItem(s string, size int, dir int) {
function testPerf (line 277) | func testPerf[K comparable, V any](nums []K, pnums []V, which string) {
function commaize (line 363) | func commaize(n int) string {
function TestHashDIB (line 374) | func TestHashDIB(t *testing.T) {
function TestIntInt (line 386) | func TestIntInt(t *testing.T) {
function TestMapValues (line 425) | func TestMapValues(t *testing.T) {
function copyMapEntries (line 435) | func copyMapEntries(m *Map[int, int]) []entry[int, int] {
function mapEntriesEqual (line 449) | func mapEntriesEqual(a, b []entry[int, int]) bool {
function copyMapTest (line 453) | func copyMapTest(N int, m1 *Map[int, int], e11 []entry[int, int], deep b...
function TestMapCopy (line 511) | func TestMapCopy(t *testing.T) {
function TestEmpty (line 538) | func TestEmpty(t *testing.T) {
function TestGetPos (line 548) | func TestGetPos(t *testing.T) {
function TestIssue3 (line 569) | func TestIssue3(t *testing.T) {
FILE: set.go
type Set (line 3) | type Set struct
method Insert (line 8) | func (tr *Set[K]) Insert(key K) {
method Contains (line 13) | func (tr *Set[K]) Contains(key K) bool {
method Len (line 19) | func (tr *Set[K]) Len() int {
method Delete (line 24) | func (tr *Set[K]) Delete(key K) {
method Scan (line 28) | func (tr *Set[K]) Scan(iter func(key K) bool) {
method Keys (line 35) | func (tr *Set[K]) Keys() []K {
method Copy (line 41) | func (tr *Set[K]) Copy() *Set[K] {
method GetPos (line 50) | func (s *Set[K]) GetPos(pos uint64) (key K, ok bool) {
FILE: set_test.go
function TestSet (line 12) | func TestSet(t *testing.T) {
function TestSetKeys (line 48) | func TestSetKeys(t *testing.T) {
function copySetEntries (line 58) | func copySetEntries(m *Set[int]) []int {
function setEntriesEqual (line 64) | func setEntriesEqual(a, b []int) bool {
function copySetTest (line 68) | func copySetTest(N int, s1 *Set[int], e11 []int, deep bool) {
function TestSetCopy (line 120) | func TestSetCopy(t *testing.T) {
function TestSetGetPos (line 148) | func TestSetGetPos(t *testing.T) {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
{
"path": "BENCH.md",
"chars": 5016,
"preview": "# Performance\n\nWhile this implementation was designed with performance in mind, it's not\nnecessarily better, faster, sma"
},
{
"path": "LICENSE",
"chars": 728,
"preview": "Copyright 2019, Joshua J Baker\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or"
},
{
"path": "README.md",
"chars": 1893,
"preview": "# hashmap\n\n[](https://godoc.org/github.co"
},
{
"path": "go.mod",
"chars": 139,
"preview": "module github.com/tidwall/hashmap\n\ngo 1.18\n\nrequire github.com/zeebo/xxh3 v1.0.2\n\nrequire github.com/klauspost/cpuid/v2 "
},
{
"path": "go.sum",
"chars": 417,
"preview": "github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=\ngithub.com/klauspost/cpuid/v2 v2.0."
},
{
"path": "map.go",
"chars": 6730,
"preview": "// Copyright 2019 Joshua J Baker. All rights reserved.\n// Use of this source code is governed by an ISC-style\n// license"
},
{
"path": "map_test.go",
"chars": 12282,
"preview": "// Copyright 2019 Joshua J Baker. All rights reserved.\n// Use of this source code is governed by an ISC-style\n// license"
},
{
"path": "set.go",
"chars": 1121,
"preview": "package hashmap\n\ntype Set[K comparable] struct {\n\tbase Map[K, struct{}]\n}\n\n// Insert an item\nfunc (tr *Set[K]) Insert(ke"
},
{
"path": "set_test.go",
"chars": 2751,
"preview": "package hashmap\n\nimport (\n\t\"math/rand\"\n\t\"reflect\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestSet(t *testing.T) {\n\tva"
}
]
About this extraction
This page contains the full source code of the tidwall/hashmap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (30.3 KB), approximately 10.9k tokens, and a symbol index with 70 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.