Skip to content

Commit

Permalink
fix false sharing
Browse files Browse the repository at this point in the history
  • Loading branch information
phuslu committed Sep 24, 2023
1 parent 79bda29 commit 3ccdda8
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 52 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module github.com/phuslu/shardmap

go 1.18

require github.com/zeebo/xxh3 v1.0.2
require (
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sys v0.12.0
)

require github.com/klauspost/cpuid/v2 v2.0.9 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
106 changes: 55 additions & 51 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"unsafe"

"github.com/zeebo/xxh3"
"golang.org/x/sys/cpu"
)

// Map is a hashmap. Like map[comparable]any, but sharded and thread-safe.
Expand All @@ -15,11 +16,15 @@ type Map[K comparable, V any] struct {
initmu sync.Mutex
cap int
shards int
mus []sync.RWMutex
maps []*rhhMap[K, V]
ksize int
kstr bool
zero V
ss []shard[K, V]
}

type shard[K comparable, V any] struct {
sync.RWMutex
m *rhhMap[K, V]
// avoid false sharing of neighboring shards' mutexes
_ cpu.CacheLinePad
}

// New returns a new hashmap with the specified capacity. This function is only
Expand All @@ -36,9 +41,9 @@ func (m *Map[K, V]) Clear() {
m.initDo()
}
for i := 0; i < m.shards; i++ {
m.mus[i].Lock()
m.maps[i] = rhhNew[K, V](m.cap / m.shards)
m.mus[i].Unlock()
m.ss[i].Lock()
m.ss[i].m = rhhNew[K, V](m.cap / m.shards)
m.ss[i].Unlock()
}
}

Expand All @@ -49,7 +54,7 @@ func (m *Map[K, V]) Set(key K, value V) (prev V, replaced bool) {
m.initDo()
}
var xxh uint64
if m.kstr {
if m.ksize == 0 {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&key)))
} else {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&struct {
Expand All @@ -58,9 +63,9 @@ func (m *Map[K, V]) Set(key K, value V) (prev V, replaced bool) {
}{unsafe.Pointer(&key), m.ksize})))
}
shard := int(xxh & uint64(m.shards-1))
m.mus[shard].Lock()
prev, replaced = m.maps[shard].Set(xxh, key, value)
m.mus[shard].Unlock()
m.ss[shard].Lock()
prev, replaced = m.ss[shard].m.Set(xxh, key, value)
m.ss[shard].Unlock()
return prev, replaced
}

Expand All @@ -74,7 +79,7 @@ func (m *Map[K, V]) SetAccept(key K, value V, accept func(prev V, replaced bool)
m.initDo()
}
var xxh uint64
if m.kstr {
if m.ksize == 0 {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&key)))
} else {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&struct {
Expand All @@ -83,20 +88,21 @@ func (m *Map[K, V]) SetAccept(key K, value V, accept func(prev V, replaced bool)
}{unsafe.Pointer(&key), m.ksize})))
}
shard := int(xxh & uint64(m.shards-1))
m.mus[shard].Lock()
defer m.mus[shard].Unlock()
prev, replaced = m.maps[shard].Set(xxh, key, value)
m.ss[shard].Lock()
defer m.ss[shard].Unlock()
prev, replaced = m.ss[shard].m.Set(xxh, key, value)
if accept != nil {
if !accept(prev, replaced) {
// revert unaccepted change
if !replaced {
// delete the newly set data
m.maps[shard].Delete(xxh, key)
m.ss[shard].m.Delete(xxh, key)
} else {
// reset updated data
m.maps[shard].Set(xxh, key, prev)
m.ss[shard].m.Set(xxh, key, prev)
}
prev, replaced = m.zero, false
var zero V
prev, replaced = zero, false
}
}
return prev, replaced
Expand All @@ -109,7 +115,7 @@ func (m *Map[K, V]) Get(key K) (value V, ok bool) {
m.initDo()
}
var xxh uint64
if m.kstr {
if m.ksize == 0 {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&key)))
} else {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&struct {
Expand All @@ -118,9 +124,9 @@ func (m *Map[K, V]) Get(key K) (value V, ok bool) {
}{unsafe.Pointer(&key), m.ksize})))
}
shard := int(xxh & uint64(m.shards-1))
m.mus[shard].RLock()
value, ok = m.maps[shard].Get(xxh, key)
m.mus[shard].RUnlock()
m.ss[shard].RLock()
value, ok = m.ss[shard].m.Get(xxh, key)
m.ss[shard].RUnlock()
return value, ok
}

Expand All @@ -131,7 +137,7 @@ func (m *Map[K, V]) Delete(key K) (prev V, deleted bool) {
m.initDo()
}
var xxh uint64
if m.kstr {
if m.ksize == 0 {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&key)))
} else {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&struct {
Expand All @@ -140,9 +146,9 @@ func (m *Map[K, V]) Delete(key K) (prev V, deleted bool) {
}{unsafe.Pointer(&key), m.ksize})))
}
shard := int(xxh & uint64(m.shards-1))
m.mus[shard].Lock()
prev, deleted = m.maps[shard].Delete(xxh, key)
m.mus[shard].Unlock()
m.ss[shard].Lock()
prev, deleted = m.ss[shard].m.Delete(xxh, key)
m.ss[shard].Unlock()
return prev, deleted
}

Expand All @@ -156,7 +162,7 @@ func (m *Map[K, V]) DeleteAccept(key K, accept func(prev V, replaced bool) bool)
m.initDo()
}
var xxh uint64
if m.kstr {
if m.ksize == 0 {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&key)))
} else {
xxh = xxh3.HashString(*(*string)(unsafe.Pointer(&struct {
Expand All @@ -165,17 +171,18 @@ func (m *Map[K, V]) DeleteAccept(key K, accept func(prev V, replaced bool) bool)
}{unsafe.Pointer(&key), m.ksize})))
}
shard := int(xxh & uint64(m.shards-1))
m.mus[shard].Lock()
defer m.mus[shard].Unlock()
prev, deleted = m.maps[shard].Delete(xxh, key)
m.ss[shard].Lock()
defer m.ss[shard].Unlock()
prev, deleted = m.ss[shard].m.Delete(xxh, key)
if accept != nil {
if !accept(prev, deleted) {
// revert unaccepted change
if deleted {
// reset updated data
m.maps[shard].Set(xxh, key, prev)
m.ss[shard].m.Set(xxh, key, prev)
}
prev, deleted = m.zero, false
var zero V
prev, deleted = zero, false
}
}

Expand All @@ -189,9 +196,9 @@ func (m *Map[K, V]) Len() int {
}
var len int
for i := 0; i < m.shards; i++ {
m.mus[i].Lock()
len += m.maps[i].Len()
m.mus[i].Unlock()
m.ss[i].Lock()
len += m.ss[i].m.Len()
m.ss[i].Unlock()
}
return len
}
Expand All @@ -204,17 +211,15 @@ func (m *Map[K, V]) Range(iter func(key K, value V) bool) {
}
var done bool
for i := 0; i < m.shards; i++ {
func(i int) {
m.mus[i].RLock()
defer m.mus[i].RUnlock()
m.maps[i].Range(func(key K, value V) bool {
if !iter(key, value) {
done = true
return false
}
return true
})
}(i)
m.ss[i].RLock()
m.ss[i].m.Range(func(key K, value V) bool {
if !iter(key, value) {
done = true
return false
}
return true
})
m.ss[i].RUnlock()
if done {
break
}
Expand All @@ -235,16 +240,15 @@ func (m *Map[K, V]) initDo() {
m.shards *= 2
}
scap := m.cap / m.shards
m.mus = make([]sync.RWMutex, m.shards)
m.maps = make([]*rhhMap[K, V], m.shards)
for i := 0; i < len(m.maps); i++ {
m.maps[i] = rhhNew[K, V](scap)
m.ss = make([]shard[K, V], m.shards)
for i := 0; i < m.shards; i++ {
m.ss[i].m = rhhNew[K, V](scap)
}

var k K
switch ((any)(k)).(type) {
case string:
m.kstr = true
m.ksize = 0
default:
m.ksize = int(unsafe.Sizeof(k))
}
Expand Down

0 comments on commit 3ccdda8

Please sign in to comment.