Skip to content

Commit

Permalink
Replace FNV-1a with memhash in Map (#14)
Browse files Browse the repository at this point in the history
Includes the following changes:

* Change hash64 return type

It makes no sense to trim the returned result to uint32
in hash64. Moreover, that may make hash code distribution
worse.

* Replace FNV-1a hash function with memhash

Replaces FNV-1a 32-bit function with faster built-in 64-bit
memhash function which is internally used in Golang standard
library.

Co-authored-by: mert <[email protected]>
  • Loading branch information
puzpuzpuz and mertakman authored Aug 15, 2021
1 parent 6a64e66 commit 06f072a
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 25 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Concurrent data structures for Go. An extension for the standard `sync` package.

This library should be considered experimental, so make sure to run tests and benchmarks for your use cases before adding it to your application.

*Important note*. Only 64-bit builds are officially supported at the moment. If you need to run a 32-bit build, make sure to test it and open a GH issue in case of any problems.

### Benchmarks

Benchmark results may be found [here](BENCHMARKS.md).
Expand Down
2 changes: 1 addition & 1 deletion counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (c *Counter) Add(delta int64) {
if !ok {
t = new(ptoken)
// Since cstripes is a power of two, we can use & instead of %.
t.idx = hash64(uintptr(unsafe.Pointer(t))) & (cstripes - 1)
t.idx = uint32(hash64(uintptr(unsafe.Pointer(t))) & (cstripes - 1))
}
stripe := &c.stripes[t.idx]
atomic.AddInt64(&stripe.c, delta)
Expand Down
22 changes: 11 additions & 11 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func newMapTable(size int) *mapTable {
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map) Load(key string) (value interface{}, ok bool) {
hash := fnv32(key)
hash := maphash64(key)
table := (*mapTable)(atomic.LoadPointer(&m.table))
bidx := bucketIdx(table, hash)
b := &table.buckets[bidx]
Expand Down Expand Up @@ -181,7 +181,7 @@ func (m *Map) doStore(key string, value interface{}, loadIfExists bool) (actual
}
}
// Write path.
hash := fnv32(key)
hash := maphash64(key)
for {
store_attempt:
var emptykp, emptyvp *unsafe.Pointer
Expand Down Expand Up @@ -311,7 +311,7 @@ func (m *Map) resize(table *mapTable, hint mapResizeHint) {
}
for i := 0; i < tableLen; i++ {
copied := copyBucket(&table.buckets[i], newTable)
addSizeNonAtomic(newTable, uint32(i), copied)
addSizeNonAtomic(newTable, uint64(i), copied)
}
// Publish the new table and wake up all waiters.
atomic.StorePointer(&m.table, unsafe.Pointer(newTable))
Expand All @@ -328,7 +328,7 @@ func copyBucket(b *bucket, destTable *mapTable) (copied int) {
for i := 0; i < entriesPerMapBucket; i++ {
if b.keys[i] != nil {
k := derefKey(b.keys[i])
hash := fnv32(k)
hash := maphash64(k)
bidx := bucketIdx(destTable, hash)
destb := &destTable.buckets[bidx]
appendToBucket(b.keys[i], b.values[i], destb)
Expand Down Expand Up @@ -367,7 +367,7 @@ func appendToBucket(keyPtr, valPtr unsafe.Pointer, destBucket *bucket) {
// value if any. The loaded result reports whether the key was
// present.
func (m *Map) LoadAndDelete(key string) (value interface{}, loaded bool) {
hash := fnv32(key)
hash := maphash64(key)
for {
hintNonEmpty := 0
table := (*mapTable)(atomic.LoadPointer(&m.table))
Expand Down Expand Up @@ -513,17 +513,17 @@ func derefValue(valuePtr unsafe.Pointer) interface{} {
return value
}

func bucketIdx(table *mapTable, hash uint32) uint32 {
return uint32(len(table.buckets)-1) & hash
func bucketIdx(table *mapTable, hash uint64) uint64 {
return uint64(len(table.buckets)-1) & hash
}

func addSize(table *mapTable, bucketIdx uint32, delta int) {
cidx := uint32(len(table.size)-1) & bucketIdx
func addSize(table *mapTable, bucketIdx uint64, delta int) {
cidx := uint64(len(table.size)-1) & bucketIdx
atomic.AddInt64(&table.size[cidx].c, int64(delta))
}

func addSizeNonAtomic(table *mapTable, bucketIdx uint32, delta int) {
cidx := uint32(len(table.size)-1) & bucketIdx
func addSizeNonAtomic(table *mapTable, bucketIdx uint64, delta int) {
cidx := uint64(len(table.size)-1) & bucketIdx
table.size[cidx].c += int64(delta)
}

Expand Down
2 changes: 1 addition & 1 deletion rbmutex.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (m *RBMutex) RLock() *RToken {
if !ok {
t = new(RToken)
// Since rslots is a power of two, we can use & instead of %.
t.slot = hash64(uintptr(unsafe.Pointer(t))) & (rslots - 1)
t.slot = uint32(hash64(uintptr(unsafe.Pointer(t))) & (rslots - 1))
}
if atomic.CompareAndSwapInt32(&m.readers[t.slot], 0, 1) {
if atomic.LoadInt32(&m.rbias) == 1 {
Expand Down
31 changes: 19 additions & 12 deletions util.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
package xsync

import (
"reflect"
"unsafe"
)

const (
// used in paddings to prevent false sharing;
// 64B are used instead of 128B as a compromise between
// memory footprint and performance; 128B usage may give ~30%
// improvement on NUMA machines
cacheLineSize = 64
fnv32Offset = uint32(2166136261)
fnv32Prime = uint32(16777619)
// the seed value is of an absolutely arbitrary choice
maphashSeed = 42
)

// murmurhash3 64-bit finalizer
func hash64(x uintptr) uint32 {
func hash64(x uintptr) uint64 {
x = ((x >> 33) ^ x) * 0xff51afd7ed558ccd
x = ((x >> 33) ^ x) * 0xc4ceb9fe1a85ec53
x = (x >> 33) ^ x
return uint32(x)
return uint64(x)
}

// FNV-1a 32-bit hash
func fnv32(key string) uint32 {
hash := fnv32Offset
keyLen := len(key)
for i := 0; i < keyLen; i++ {
hash ^= uint32(key[i])
hash *= fnv32Prime
// exposes the built-in memhash function
func maphash64(s string) uint64 {
if s == "" {
return maphashSeed
}
return hash
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
return uint64(memhash(unsafe.Pointer(strh.Data), maphashSeed, uintptr(strh.Len)))
}

//go:noescape
//go:linkname memhash runtime.memhash
func memhash(p unsafe.Pointer, h, s uintptr) uintptr

This comment has been minimized.

Copy link
@mertakman

mertakman Aug 15, 2021

Author Contributor

0 comments on commit 06f072a

Please sign in to comment.