Skip to content

Commit b9c0aa7

Browse files
aykevldeadprogram
authored andcommitted
runtime: implement memhash
This function is used by the hash/maphash package. Unfortunately, I couldn't get the hash/maphash package to pass tests because it's too slow. I think it hits the quadratic complexity of the current map implementation.
1 parent 4c28f1b commit b9c0aa7

File tree

2 files changed

+43
-22
lines changed

2 files changed

+43
-22
lines changed

src/runtime/algorithm.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package runtime
33
// This file implements various core algorithms used in the runtime package and
44
// standard library.
55

6+
import "unsafe"
7+
68
// This function is used by hash/maphash.
79
func fastrand() uint32 {
810
xorshift32State = xorshift32(xorshift32State)
@@ -20,3 +22,35 @@ func xorshift32(x uint32) uint32 {
2022
x ^= x << 9
2123
return x
2224
}
25+
26+
// This function is used by hash/maphash.
27+
func memhash(p unsafe.Pointer, seed, s uintptr) uintptr {
28+
if unsafe.Sizeof(uintptr(0)) > 4 {
29+
return seed ^ uintptr(hash64(p, s))
30+
}
31+
return seed ^ uintptr(hash32(p, s))
32+
}
33+
34+
// Get FNV-1a hash of the given memory buffer.
35+
//
36+
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
37+
func hash32(ptr unsafe.Pointer, n uintptr) uint32 {
38+
var result uint32 = 2166136261 // FNV offset basis
39+
for i := uintptr(0); i < n; i++ {
40+
c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
41+
result ^= uint32(c) // XOR with byte
42+
result *= 16777619 // FNV prime
43+
}
44+
return result
45+
}
46+
47+
// Also a FNV-1a hash.
48+
func hash64(ptr unsafe.Pointer, n uintptr) uint64 {
49+
var result uint64 = 14695981039346656037 // FNV offset basis
50+
for i := uintptr(0); i < n; i++ {
51+
c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
52+
result ^= uint64(c) // XOR with byte
53+
result *= 1099511628211 // FNV prime
54+
}
55+
return result
56+
}

src/runtime/hashmap.go

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,6 @@ type hashmapIterator struct {
3737
bucketIndex uint8
3838
}
3939

40-
// Get FNV-1a hash of this key.
41-
//
42-
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
43-
func hashmapHash(ptr unsafe.Pointer, n uintptr) uint32 {
44-
var result uint32 = 2166136261 // FNV offset basis
45-
for i := uintptr(0); i < n; i++ {
46-
c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
47-
result ^= uint32(c) // XOR with byte
48-
result *= 16777619 // FNV prime
49-
}
50-
return result
51-
}
52-
5340
// Get the topmost 8 bits of the hash, without using a special value (like 0).
5441
func hashmapTopHash(hash uint32) uint8 {
5542
tophash := uint8(hash >> 24)
@@ -308,7 +295,7 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
308295

309296
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
310297
// TODO: detect nil map here and throw a better panic message?
311-
hash := hashmapHash(key, uintptr(m.keySize))
298+
hash := hash32(key, uintptr(m.keySize))
312299
hashmapSet(m, key, value, hash, memequal)
313300
}
314301

@@ -317,15 +304,15 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr)
317304
memzero(value, uintptr(valueSize))
318305
return false
319306
}
320-
hash := hashmapHash(key, uintptr(m.keySize))
307+
hash := hash32(key, uintptr(m.keySize))
321308
return hashmapGet(m, key, value, valueSize, hash, memequal)
322309
}
323310

324311
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
325312
if m == nil {
326313
return
327314
}
328-
hash := hashmapHash(key, uintptr(m.keySize))
315+
hash := hash32(key, uintptr(m.keySize))
329316
hashmapDelete(m, key, hash, memequal)
330317
}
331318

@@ -337,7 +324,7 @@ func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool {
337324

338325
func hashmapStringHash(s string) uint32 {
339326
_s := (*_string)(unsafe.Pointer(&s))
340-
return hashmapHash(unsafe.Pointer(_s.ptr), uintptr(_s.length))
327+
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
341328
}
342329

343330
func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
@@ -370,7 +357,7 @@ func hashmapFloat32Hash(ptr unsafe.Pointer) uint32 {
370357
// convert -0 to 0 for hashing
371358
f = 0
372359
}
373-
return hashmapHash(unsafe.Pointer(&f), 4)
360+
return hash32(unsafe.Pointer(&f), 4)
374361
}
375362

376363
func hashmapFloat64Hash(ptr unsafe.Pointer) uint32 {
@@ -379,7 +366,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer) uint32 {
379366
// convert -0 to 0 for hashing
380367
f = 0
381368
}
382-
return hashmapHash(unsafe.Pointer(&f), 8)
369+
return hash32(unsafe.Pointer(&f), 8)
383370
}
384371

385372
func hashmapInterfaceHash(itf interface{}) uint32 {
@@ -397,9 +384,9 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
397384

398385
switch x.RawType().Kind() {
399386
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
400-
return hashmapHash(ptr, x.RawType().Size())
387+
return hash32(ptr, x.RawType().Size())
401388
case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
402-
return hashmapHash(ptr, x.RawType().Size())
389+
return hash32(ptr, x.RawType().Size())
403390
case reflect.Float32:
404391
// It should be possible to just has the contents. However, NaN != NaN
405392
// so if you're using lots of NaNs as map keys (you shouldn't) then hash
@@ -421,7 +408,7 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
421408
// It might seem better to just return the pointer, but that won't
422409
// result in an evenly distributed hashmap. Instead, hash the pointer
423410
// like most other types.
424-
return hashmapHash(ptr, x.RawType().Size())
411+
return hash32(ptr, x.RawType().Size())
425412
case reflect.Array:
426413
var hash uint32
427414
for i := 0; i < x.Len(); i++ {

0 commit comments

Comments
 (0)