github.com/fufuok/utils@v1.0.10/xsync/util_hash_mapof_test.go (about) 1 //go:build go1.18 2 // +build go1.18 3 4 package xsync_test 5 6 //lint:file-ignore U1000 unused fields are necessary to access the hasher 7 //lint:file-ignore SA4000 hash code comparisons use identical expressions 8 9 import ( 10 "fmt" 11 "testing" 12 "unsafe" 13 14 . "github.com/fufuok/utils/xsync" 15 ) 16 17 func TestMakeHashFunc(t *testing.T) { 18 type User struct { 19 Name string 20 City string 21 } 22 23 seed := MakeSeed() 24 25 hashString := MakeHasher[string]() 26 hashUser := MakeHasher[User]() 27 28 hashUserMap := makeMapHasher[User]() 29 30 // Not that much to test TBH. 31 32 // check that hash is not always the same 33 for i := 0; ; i++ { 34 if hashString("foo", seed) != hashString("bar", seed) { 35 break 36 } 37 if i >= 100 { 38 t.Error("hashString is always the same") 39 break 40 } 41 42 seed = MakeSeed() // try with a new seed 43 } 44 45 if hashString("foo", seed) != hashString("foo", seed) { 46 t.Error("hashString is not deterministic") 47 } 48 49 if hashUser(User{Name: "Ivan", City: "Sofia"}, seed) != hashUser(User{Name: "Ivan", City: "Sofia"}, seed) { 50 t.Error("hashUser is not deterministic") 51 } 52 53 // just for fun, compare with native hash function 54 if hashUser(User{Name: "Ivan", City: "Sofia"}, seed) != hashUserMap(User{Name: "Ivan", City: "Sofia"}, seed) { 55 t.Error("hashUser and hashUserNative return different values") 56 } 57 } 58 59 func makeMapHasher[T comparable]() func(T, uint64) uint64 { 60 hasher := makeMapHasherInternal(make(map[T]struct{})) 61 62 is64Bit := unsafe.Sizeof(uintptr(0)) == 8 63 64 if is64Bit { 65 return func(value T, seed uint64) uint64 { 66 seed64 := *(*uint64)(unsafe.Pointer(&seed)) 67 return uint64(hasher(runtime_noescape(unsafe.Pointer(&value)), uintptr(seed64))) 68 } 69 } else { 70 return func(value T, seed uint64) uint64 { 71 seed64 := *(*uint64)(unsafe.Pointer(&seed)) 72 lo := hasher(runtime_noescape(unsafe.Pointer(&value)), uintptr(seed64)) 73 hi := hasher(runtime_noescape(unsafe.Pointer(&value)), uintptr(seed64>>32)) 74 return uint64(hi)<<32 | uint64(lo) 75 } 76 } 77 } 78 79 //go:noescape 80 //go:linkname runtime_noescape runtime.noescape 81 func runtime_noescape(p unsafe.Pointer) unsafe.Pointer 82 83 type nativeHasher func(unsafe.Pointer, uintptr) uintptr 84 85 func makeMapHasherInternal(mapValue any) nativeHasher { 86 // go/src/runtime/type.go 87 type tflag uint8 88 type nameOff int32 89 type typeOff int32 90 91 // go/src/runtime/type.go 92 type _type struct { 93 size uintptr 94 ptrdata uintptr 95 hash uint32 96 tflag tflag 97 align uint8 98 fieldAlign uint8 99 kind uint8 100 equal func(unsafe.Pointer, unsafe.Pointer) bool 101 gcdata *byte 102 str nameOff 103 ptrToThis typeOff 104 } 105 106 // go/src/runtime/type.go 107 type maptype struct { 108 typ _type 109 key *_type 110 elem *_type 111 bucket *_type 112 // function for hashing keys (ptr to key, seed) -> hash 113 hasher nativeHasher 114 keysize uint8 115 elemsize uint8 116 bucketsize uint16 117 flags uint32 118 } 119 120 type mapiface struct { 121 typ *maptype 122 val uintptr 123 } 124 125 i := (*mapiface)(unsafe.Pointer(&mapValue)) 126 return i.typ.hasher 127 } 128 129 func BenchmarkMakeHashFunc(b *testing.B) { 130 type Point struct { 131 X, Y, Z int 132 } 133 134 type User struct { 135 ID int 136 FirstName string 137 LastName string 138 IsActive bool 139 City string 140 } 141 142 type PadInside struct { 143 A int 144 B byte 145 C int 146 } 147 148 type PadTrailing struct { 149 A int 150 B byte 151 } 152 153 doBenchmarkMakeHashFunc(b, int64(116)) 154 doBenchmarkMakeHashFunc(b, int32(116)) 155 doBenchmarkMakeHashFunc(b, 3.14) 156 doBenchmarkMakeHashFunc(b, "test key test key test key test key test key test key test key test key test key ") 157 doBenchmarkMakeHashFunc(b, Point{1, 2, 3}) 158 doBenchmarkMakeHashFunc(b, User{ID: 1, FirstName: "Ivan", LastName: "Ivanov", IsActive: true, City: "Sofia"}) 159 doBenchmarkMakeHashFunc(b, PadInside{}) 160 doBenchmarkMakeHashFunc(b, PadTrailing{}) 161 doBenchmarkMakeHashFunc(b, [1024]byte{}) 162 doBenchmarkMakeHashFunc(b, [128]Point{}) 163 doBenchmarkMakeHashFunc(b, [128]User{}) 164 doBenchmarkMakeHashFunc(b, [128]PadInside{}) 165 doBenchmarkMakeHashFunc(b, [128]PadTrailing{}) 166 } 167 168 func doBenchmarkMakeHashFunc[T comparable](b *testing.B, val T) { 169 hash := MakeHasher[T]() 170 hashNativeMap := makeMapHasher[T]() 171 seed := MakeSeed() 172 173 b.Run(fmt.Sprintf("%T normal", val), func(b *testing.B) { 174 b.ReportAllocs() 175 for i := 0; i < b.N; i++ { 176 _ = hash(val, seed) 177 } 178 }) 179 180 b.Run(fmt.Sprintf("%T map native", val), func(b *testing.B) { 181 b.ReportAllocs() 182 for i := 0; i < b.N; i++ { 183 _ = hashNativeMap(val, seed) 184 } 185 }) 186 }