Full Code of tidwall/hashmap for AI

master e128c7c0599b cached
9 files
30.3 KB
10.9k tokens
70 symbols
1 requests
Download .txt
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

[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](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()
	}
}
Download .txt
gitextract_w3ied2c9/

├── BENCH.md
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── map.go
├── map_test.go
├── set.go
└── set_test.go
Download .txt
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[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](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.

Copied to clipboard!