Skip to content

Commit

Permalink
Both sync.Map and cowmap use atomic.Value atomic operations to access…
Browse files Browse the repository at this point in the history
… the map during data reading, resulting in similar read performance. However, sync.Map has better write performance. Therefore, cowmap directly utilizes sync.Map as its internal structure.
  • Loading branch information
werbenhu committed Aug 10, 2023
1 parent dafa2af commit b8351ed
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 81 deletions.
82 changes: 14 additions & 68 deletions cowmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,84 +5,30 @@ import (
"sync/atomic"
)

type tmap map[any]any

// CowMap is a wrapper of Copy-On-Write map
type CowMap struct {
mu sync.Mutex
readable atomic.Value
sync.Map
}

// NewCowMap creates a new CowMap instance
// CowMap creates a new CowMap instance
func NewCowMap() *CowMap {
m := make(tmap)
c := &CowMap{}
c.readable.Store(m)
return c
}

// clone creates a copy of the map by iterating over the original map
// and copying its key-value pairs to the new map
func (c *CowMap) clone() tmap {
m := make(tmap)
for k, v := range c.readable.Load().(tmap) {
m[k] = v
}
return m
}

// Load returns the value stored in the map for a given key,
// or nil if the key is not present.
// The ok result indicates whether the value was found in the map.
func (c *CowMap) Load(key any) (value any, ok bool) {
value, ok = c.readable.Load().(tmap)[key]
return
return &CowMap{}
}

// Len returns the number of key-value pairs stored in the map
func (c *CowMap) Len() int {
return len(c.readable.Load().(tmap))
}

// Store sets the value for a given key by creating a new copy of the map
// and adding the new key-value pair to it
func (c *CowMap) Store(key, value any) {
c.mu.Lock()
defer c.mu.Unlock()

copy := c.clone() // create a copy of the map
copy[key] = value // add the new key-value pair to the copy
c.readable.Store(copy) // update the atomic value with the new copy
}

// Delete removes a key-value pair from the map by creating a new copy of the map
// and deleting the specified key from it
func (c *CowMap) Delete(key any) {
c.mu.Lock()
defer c.mu.Unlock()

copy := c.clone()
delete(copy, key)
c.readable.Store(copy)
func (c *CowMap) Len() uint32 {
var size uint32
c.Range(func(k, v any) bool {
atomic.AddUint32(&size, 1)
return true
})
return size
}

// Clear Removes all key-value pairs from the map
func (c *CowMap) Clear() {
c.mu.Lock()
defer c.mu.Unlock()

m := make(tmap)
c.readable.Store(m)
}

// Range calls the provided function for each key-value pair in the map,
// stopping the iteration if the function returns false
func (c *CowMap) Range(f func(key, value any) bool) {
m := c.readable.Load().(tmap)

for k, v := range m {
if !f(k, v) {
break
}
}
c.Range(func(k, v any) bool {
c.Delete(k)
return true
})
}
15 changes: 3 additions & 12 deletions cowmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@ func Test_NewCowMap(t *testing.T) {
assert.NotNil(t, m)
assert.NotNil(t, m.readable)
}

func Test_CowMapClone(t *testing.T) {
m := NewCowMap()
for i := 0; i < 100; i++ {
m.Store(i, strconv.Itoa(i))
}
clone := m.clone()
assert.NotNil(t, clone)
assert.Equal(t, 100, len(clone))
}

func Test_CowMapLoad(t *testing.T) {
Expand All @@ -47,7 +38,7 @@ func Test_CowMapStore(t *testing.T) {
m.Store(i, strconv.Itoa(i))
}

assert.Equal(t, 100, m.Len())
assert.Equal(t, uint32(100), m.Len())
}

func Test_CowMapDelete(t *testing.T) {
Expand Down Expand Up @@ -80,15 +71,15 @@ func Test_CowMapClear(t *testing.T) {
}

m.Clear()
assert.Equal(t, 0, m.Len())
assert.Equal(t, uint32(0), m.Len())
}

func Test_CowMapLen(t *testing.T) {
m := NewCowMap()
for i := 0; i < 100; i++ {
m.Store(i, strconv.Itoa(i))
}
assert.Equal(t, 100, m.Len())
assert.Equal(t, uint32(100), m.Len())
}

func Test_CowMapRange(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion eventbus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func Test_channelClose(t *testing.T) {
err := ch.subscribe(busHandlerOne)
assert.Nil(t, err)
ch.close()
assert.Equal(t, 0, ch.handlers.Len())
assert.Equal(t, uint32(0), ch.handlers.Len())
ch.close()
}

Expand Down

0 comments on commit b8351ed

Please sign in to comment.