diff --git a/maps.go b/maps.go index 480a7cb..4a6075f 100644 --- a/maps.go +++ b/maps.go @@ -2,6 +2,7 @@ package go_concurrency import ( "sync" + "sync/atomic" ) func nextPowerOf2(v int) int { @@ -104,6 +105,41 @@ func (m *ShardCache) Put(key int, value int) { m.maps[key%10][key] = value } +const SharedShardMask = 16 - 1 + +type SharedShardCache struct { + maps [16]atomic.Value + locks [16]sync.Mutex +} + +func NewSharedShardCache() *SharedShardCache { + m := SharedShardCache{} + for i := 0; i < 16; i++ { + m.maps[i].Store(make(map[int]int)) + } + return &m +} + +func (m *SharedShardCache) Get(key int) int { + m0 := m.maps[key&SharedShardMask] + return m0.Load().(map[int]int)[key] +} + +func (m *SharedShardCache) Put(key int, value int) { + lock := m.locks[key&SharedShardMask] + lock.Lock() + m0 := m.maps[key&SharedShardMask].Load().(map[int]int) + // make a new map and atomically store, this could be optimized because if + // the key already exists in the map, we can safely update the value in the + // main map and just restore to enforce the memory fence + m1 := make(map[int]int) + for k, v := range m0 { + m1[k] = v + } + m1[key] = value + m.maps[key&SharedShardMask].Store(m1) +} + type UnsharedCache map[int]int func NewUnsharedCache() *UnsharedCache { diff --git a/maps_test.go b/maps_test.go index f0f69e4..325ae1c 100644 --- a/maps_test.go +++ b/maps_test.go @@ -9,7 +9,7 @@ import ( "time" ) -const NGOS = 2 // number of concurrent go routines for read/load tests +const NGOS = 8 // number of concurrent go routines for read/load tests const Mask = (1024 * 1024) - 1 var um = go_concurrency.NewUnsharedCache() @@ -17,6 +17,7 @@ var lm = go_concurrency.NewLockCache() var sm = go_concurrency.NewSyncCache() var cm = go_concurrency.NewChannelCache() var sc = go_concurrency.NewShardCache() +var ssc = go_concurrency.NewSharedShardCache() var im = go_concurrency.NewIntMap(256000) // so there are 4x collisions var im2 = go_concurrency.NewIntMap(1000000) // so there are no collisions @@ -80,8 +81,8 @@ func BenchmarkMain(m *testing.B) { m.ResetTimer() impls := []go_concurrency.Cache{um, lm, sm, cm, sc, im, im2} - names := []string{"unshared", "lock", "sync", "channel", "shard", "intmap", "intmap2"} - multi := []bool{false, true, true, true, false, true, true} + names := []string{"unshared", "lock", "sync", "channel", "shard", "shareshard", "intmap", "intmap2"} + multi := []bool{false, true, true, true, false, true, true, true} for i := 0; i < len(impls); i++ { impl := impls[i]