Skip to content

Commit

Permalink
chore: add hashtriemap implementation
Browse files Browse the repository at this point in the history
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 <dmitry.matrenichev@siderolabs.com>
  • Loading branch information
DmitriyMV committed May 27, 2024
1 parent 8485864 commit 5a315f9
Show file tree
Hide file tree
Showing 6 changed files with 1,065 additions and 0 deletions.
74 changes: 74 additions & 0 deletions concurrent/go122.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.22 && !go1.24 && !nomaptypehash

//nolint:revive,govet,stylecheck
package concurrent

import (
"math/rand/v2"
"unsafe"
)

// NewHashTrieMap creates a new HashTrieMap for the provided key and value.
func NewHashTrieMap[K, V comparable]() *HashTrieMap[K, V] {
var m map[K]V

mapType := efaceMapOf(m)
ht := &HashTrieMap[K, V]{
root: newIndirectNode[K, V](nil),
keyHash: mapType._type.Hasher,
seed: uintptr(rand.Uint64()),
}
return ht
}

// _MapType is runtime.maptype from runtime/type.go.
type _MapType struct {
_Type
Key *_Type
Elem *_Type
Bucket *_Type // internal type representing a hash bucket
// function for hashing keys (ptr to key, seed) -> hash
Hasher func(unsafe.Pointer, uintptr) uintptr
KeySize uint8 // size of key slot
ValueSize uint8 // size of elem slot
BucketSize uint16 // size of bucket
Flags uint32
}

// _Type is runtime._type from runtime/type.go.
type _Type struct {
Size_ uintptr
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
Hash uint32 // hash of type; avoids computation in hash tables
TFlag uint8 // extra type information flags
Align_ uint8 // alignment of variable with this type
FieldAlign_ uint8 // alignment of struct field with this type
Kind_ uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
Equal func(unsafe.Pointer, unsafe.Pointer) bool
// GCData stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, GCData is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
GCData *byte
Str int32 // string form
PtrToThis int32 // type for pointer to this type, may be zero
}

// efaceMap is runtime.eface from runtime/runtime2.go.
type efaceMap struct {
_type *_MapType
data unsafe.Pointer
}

func efaceMapOf(ep any) *efaceMap {
return (*efaceMap)(unsafe.Pointer(&ep))
}
71 changes: 71 additions & 0 deletions concurrent/go122_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.22 && !go1.24 && !nomaptypehash

//nolint:wsl,testpackage
package concurrent

import (
"testing"
)

func BenchmarkHashTrieMapLoadSmall(b *testing.B) {
benchmarkHashTrieMapLoad(b, testDataSmall[:])
}

func BenchmarkHashTrieMapLoad(b *testing.B) {
benchmarkHashTrieMapLoad(b, testData[:])
}

func BenchmarkHashTrieMapLoadLarge(b *testing.B) {
benchmarkHashTrieMapLoad(b, testDataLarge[:])
}

func benchmarkHashTrieMapLoad(b *testing.B, data []string) {
b.ReportAllocs()
m := NewHashTrieMap[string, int]()
for i := range data {
m.LoadOrStore(data[i], i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
_, _ = m.Load(data[i])
i++
if i >= len(data) {
i = 0
}
}
})
}

func BenchmarkHashTrieMapLoadOrStore(b *testing.B) {
benchmarkHashTrieMapLoadOrStore(b, testData[:])
}

func BenchmarkHashTrieMapLoadOrStoreLarge(b *testing.B) {
benchmarkHashTrieMapLoadOrStore(b, testDataLarge[:])
}

func benchmarkHashTrieMapLoadOrStore(b *testing.B, data []string) {
b.ReportAllocs()
m := NewHashTrieMap[string, int]()

b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
_, _ = m.LoadOrStore(data[i], i)
i++
if i >= len(data) {
i = 0
}
}
})
}
35 changes: 35 additions & 0 deletions concurrent/go122_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.22 && !go1.24 && !nomaptypehash

//nolint:revive,nlreturn,wsl,gocyclo,unparam,unused,cyclop,testpackage
package concurrent

import (
"testing"
"unsafe"
)

func TestHashTrieMap(t *testing.T) {
testHashTrieMap(t, func() *HashTrieMap[string, int] {
return NewHashTrieMap[string, int]()
})
}

func TestHashTrieMapBadHash(t *testing.T) {
testHashTrieMap(t, func() *HashTrieMap[string, int] {
// Stub out the good hash function with a terrible one.
// Everything should still work as expected.
m := NewHashTrieMap[string, int]()
m.keyHash = func(_ unsafe.Pointer, _ uintptr) uintptr {
return 0
}
return m
})
}
Loading

0 comments on commit 5a315f9

Please sign in to comment.