Skip to content

Commit 86b246f

Browse files
committed
chore: add hashtriemap implementation
This PR adds a hashtriemap implementation to the project. This is mostly a direct copy from unique package [^1] internals [^2] with some adjustments to make it work with our project. Once `maphash.Comparable` lands we can remove dependency on Go internals [^3]. Michael Knyszek says he plans to use this implementation as a general idea for the future generic sync/v2.Map, but nothing is in stone yet. [^1]: golang/go#62483 [^2]: https://go-review.googlesource.com/c/go/+/573956 [^3]: golang/go#54670 Signed-off-by: Dmitriy Matrenichev <[email protected]>
1 parent 8485864 commit 86b246f

File tree

6 files changed

+1065
-0
lines changed

6 files changed

+1065
-0
lines changed

concurrent/go122.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Copyright 2024 The Go Authors. All rights reserved.
6+
// Use of this source code is governed by a BSD-style
7+
// license that can be found in the LICENSE file.
8+
9+
//go:build go1.22 && !go1.24 && !nomaptypehash
10+
11+
//nolint:revive,govet,stylecheck
12+
package concurrent
13+
14+
import (
15+
"math/rand/v2"
16+
"unsafe"
17+
)
18+
19+
// NewHashTrieMap creates a new HashTrieMap for the provided key and value.
20+
func NewHashTrieMap[K, V comparable]() *HashTrieMap[K, V] {
21+
var m map[K]V
22+
23+
mapType := efaceMapOf(m)
24+
ht := &HashTrieMap[K, V]{
25+
root: newIndirectNode[K, V](nil),
26+
keyHash: mapType._type.Hasher,
27+
seed: uintptr(rand.Uint64()),
28+
}
29+
return ht
30+
}
31+
32+
// _MapType is runtime.maptype from runtime/type.go.
33+
type _MapType struct {
34+
_Type
35+
Key *_Type
36+
Elem *_Type
37+
Bucket *_Type // internal type representing a hash bucket
38+
// function for hashing keys (ptr to key, seed) -> hash
39+
Hasher func(unsafe.Pointer, uintptr) uintptr
40+
KeySize uint8 // size of key slot
41+
ValueSize uint8 // size of elem slot
42+
BucketSize uint16 // size of bucket
43+
Flags uint32
44+
}
45+
46+
// _Type is runtime._type from runtime/type.go.
47+
type _Type struct {
48+
Size_ uintptr
49+
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
50+
Hash uint32 // hash of type; avoids computation in hash tables
51+
TFlag uint8 // extra type information flags
52+
Align_ uint8 // alignment of variable with this type
53+
FieldAlign_ uint8 // alignment of struct field with this type
54+
Kind_ uint8 // enumeration for C
55+
// function for comparing objects of this type
56+
// (ptr to object A, ptr to object B) -> ==?
57+
Equal func(unsafe.Pointer, unsafe.Pointer) bool
58+
// GCData stores the GC type data for the garbage collector.
59+
// If the KindGCProg bit is set in kind, GCData is a GC program.
60+
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
61+
GCData *byte
62+
Str int32 // string form
63+
PtrToThis int32 // type for pointer to this type, may be zero
64+
}
65+
66+
// efaceMap is runtime.eface from runtime/runtime2.go.
67+
type efaceMap struct {
68+
_type *_MapType
69+
data unsafe.Pointer
70+
}
71+
72+
func efaceMapOf(ep any) *efaceMap {
73+
return (*efaceMap)(unsafe.Pointer(&ep))
74+
}

concurrent/go122_bench_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Copyright 2024 The Go Authors. All rights reserved.
6+
// Use of this source code is governed by a BSD-style
7+
// license that can be found in the LICENSE file.
8+
9+
//go:build go1.22 && !go1.24 && !nomaptypehash
10+
11+
//nolint:wsl,testpackage
12+
package concurrent
13+
14+
import (
15+
"testing"
16+
)
17+
18+
func BenchmarkHashTrieMapLoadSmall(b *testing.B) {
19+
benchmarkHashTrieMapLoad(b, testDataSmall[:])
20+
}
21+
22+
func BenchmarkHashTrieMapLoad(b *testing.B) {
23+
benchmarkHashTrieMapLoad(b, testData[:])
24+
}
25+
26+
func BenchmarkHashTrieMapLoadLarge(b *testing.B) {
27+
benchmarkHashTrieMapLoad(b, testDataLarge[:])
28+
}
29+
30+
func benchmarkHashTrieMapLoad(b *testing.B, data []string) {
31+
b.ReportAllocs()
32+
m := NewHashTrieMap[string, int]()
33+
for i := range data {
34+
m.LoadOrStore(data[i], i)
35+
}
36+
b.ResetTimer()
37+
b.RunParallel(func(pb *testing.PB) {
38+
i := 0
39+
for pb.Next() {
40+
_, _ = m.Load(data[i])
41+
i++
42+
if i >= len(data) {
43+
i = 0
44+
}
45+
}
46+
})
47+
}
48+
49+
func BenchmarkHashTrieMapLoadOrStore(b *testing.B) {
50+
benchmarkHashTrieMapLoadOrStore(b, testData[:])
51+
}
52+
53+
func BenchmarkHashTrieMapLoadOrStoreLarge(b *testing.B) {
54+
benchmarkHashTrieMapLoadOrStore(b, testDataLarge[:])
55+
}
56+
57+
func benchmarkHashTrieMapLoadOrStore(b *testing.B, data []string) {
58+
b.ReportAllocs()
59+
m := NewHashTrieMap[string, int]()
60+
61+
b.RunParallel(func(pb *testing.PB) {
62+
i := 0
63+
for pb.Next() {
64+
_, _ = m.LoadOrStore(data[i], i)
65+
i++
66+
if i >= len(data) {
67+
i = 0
68+
}
69+
}
70+
})
71+
}

concurrent/go122_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Copyright 2024 The Go Authors. All rights reserved.
6+
// Use of this source code is governed by a BSD-style
7+
// license that can be found in the LICENSE file.
8+
9+
//go:build go1.22 && !go1.24 && !nomaptypehash
10+
11+
//nolint:revive,nlreturn,wsl,gocyclo,unparam,unused,cyclop,testpackage
12+
package concurrent
13+
14+
import (
15+
"testing"
16+
"unsafe"
17+
)
18+
19+
func TestHashTrieMap(t *testing.T) {
20+
testHashTrieMap(t, func() *HashTrieMap[string, int] {
21+
return NewHashTrieMap[string, int]()
22+
})
23+
}
24+
25+
func TestHashTrieMapBadHash(t *testing.T) {
26+
testHashTrieMap(t, func() *HashTrieMap[string, int] {
27+
// Stub out the good hash function with a terrible one.
28+
// Everything should still work as expected.
29+
m := NewHashTrieMap[string, int]()
30+
m.keyHash = func(_ unsafe.Pointer, _ uintptr) uintptr {
31+
return 0
32+
}
33+
return m
34+
})
35+
}

0 commit comments

Comments
 (0)