diff --git a/concurrent_map.go b/multiple_map.go similarity index 55% rename from concurrent_map.go rename to multiple_map.go index baccab0..a4f45fc 100644 --- a/concurrent_map.go +++ b/multiple_map.go @@ -1,12 +1,14 @@ -package cmap +// supported by https://github.com/orcaman/concurrent-map/blob/master/concurrent_map.go +package container import ( "encoding/json" "fmt" + "strconv" "sync" ) -var SHARD_COUNT = 32 +const defaultShardCount = 32 type Stringer interface { fmt.Stringer @@ -14,52 +16,80 @@ type Stringer interface { } // A "thread" safe map of type string:Anything. -// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. -type ConcurrentMap[K comparable, V any] struct { - shards []*ConcurrentMapShared[K, V] - sharding func(key K) uint32 +// To avoid lock bottlenecks this map is dived to several (defaultShardCount) map shards. +type ConcurrentMulMap[K Stringer, V any] struct { + shards []*concurrentMulMapShared[K, V] + shardsNum int + shardFn HashFn[K] +} + +type HashFn[K Stringer]func(key K) uint32 + +func (m *ConcurrentMulMap[K, V]) apply(opts []MMapOpt[K, V]) { + for _, opt := range opts { + opt(m) + } +} + +type MMapOpt[K Stringer, V any] func(mulMap *ConcurrentMulMap[K, V]) + +func WithShardsNum[K Stringer, V any](shardsNum int) MMapOpt[K, V] { + if shardsNum < 2 { + shardsNum = defaultShardCount + } + + return func(mulMap *ConcurrentMulMap[K, V]) { + mulMap.shardsNum = shardsNum + mulMap.shards = make([]*concurrentMulMapShared[K, V], shardsNum) + } +} + +func WithShardFn[K Stringer, V any](f func(key K) uint32) MMapOpt[K, V] { + return func(mulMap *ConcurrentMulMap[K, V]) { + mulMap.shardFn = f + } } // A "thread" safe string to anything map. -type ConcurrentMapShared[K comparable, V any] struct { +type concurrentMulMapShared[K Stringer, V any] struct { items map[K]V sync.RWMutex // Read Write mutex, guards access to internal map. } -func create[K comparable, V any](sharding func(key K) uint32) ConcurrentMap[K, V] { - m := ConcurrentMap[K, V]{ - sharding: sharding, - shards: make([]*ConcurrentMapShared[K, V], SHARD_COUNT), - } - for i := 0; i < SHARD_COUNT; i++ { - m.shards[i] = &ConcurrentMapShared[K, V]{items: make(map[K]V)} +func create[K Stringer, V any](opts ...MMapOpt[K, V]) *ConcurrentMulMap[K, V] { + m := &ConcurrentMulMap[K, V]{ + shardFn: strfnv32[K], + shardsNum: defaultShardCount, + shards: make([]*concurrentMulMapShared[K, V], defaultShardCount), } + + m.apply(opts) + return m } // Creates a new concurrent map. -func New[V any]() ConcurrentMap[string, V] { - return create[string, V](fnv32) +func NewMMap[K Stringer, V any](opts ...MMapOpt[K, V]) *ConcurrentMulMap[K, V] { + return create[K, V](opts...) } -// Creates a new concurrent map. -func NewStringer[K Stringer, V any]() ConcurrentMap[K, V] { - return create[K, V](strfnv32[K]) -} - -// Creates a new concurrent map. -func NewWithCustomShardingFunction[K comparable, V any](sharding func(key K) uint32) ConcurrentMap[K, V] { - return create[K, V](sharding) +// getShard returns shard under given key +func (m *ConcurrentMulMap[K, V]) getShard(key K) *concurrentMulMapShared[K, V] { + idx := uint(m.shardFn(key)) % uint(m.shardsNum) + m.needAllocate(int(idx)) + + return m.shards[idx] } -// GetShard returns shard under given key -func (m ConcurrentMap[K, V]) GetShard(key K) *ConcurrentMapShared[K, V] { - return m.shards[uint(m.sharding(key))%uint(SHARD_COUNT)] +func (m *ConcurrentMulMap[K, V]) needAllocate(idx int) { + if m.shards[idx] == nil { + m.shards[idx] = &concurrentMulMapShared[K, V]{items: make(map[K]V)} + } } -func (m ConcurrentMap[K, V]) MSet(data map[K]V) { +func (m *ConcurrentMulMap[K, V]) MSet(data map[K]V) { for key, value := range data { - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() shard.items[key] = value shard.Unlock() @@ -67,9 +97,9 @@ func (m ConcurrentMap[K, V]) MSet(data map[K]V) { } // Sets the given value under the specified key. -func (m ConcurrentMap[K, V]) Set(key K, value V) { +func (m *ConcurrentMulMap[K, V]) Set(key K, value V) { // Get map shard. - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() shard.items[key] = value shard.Unlock() @@ -82,8 +112,8 @@ func (m ConcurrentMap[K, V]) Set(key K, value V) { type UpsertCb[V any] func(exist bool, valueInMap V, newValue V) V // Insert or Update - updates existing element or inserts a new one using UpsertCb -func (m ConcurrentMap[K, V]) Upsert(key K, value V, cb UpsertCb[V]) (res V) { - shard := m.GetShard(key) +func (m *ConcurrentMulMap[K, V]) Upsert(key K, value V, cb UpsertCb[V]) (res V) { + shard := m.getShard(key) shard.Lock() v, ok := shard.items[key] res = cb(ok, v, value) @@ -93,9 +123,9 @@ func (m ConcurrentMap[K, V]) Upsert(key K, value V, cb UpsertCb[V]) (res V) { } // Sets the given value under the specified key if no value was associated with it. -func (m ConcurrentMap[K, V]) SetIfAbsent(key K, value V) bool { +func (m *ConcurrentMulMap[K, V]) SetIfAbsent(key K, value V) bool { // Get map shard. - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() _, ok := shard.items[key] if !ok { @@ -106,9 +136,9 @@ func (m ConcurrentMap[K, V]) SetIfAbsent(key K, value V) bool { } // Get retrieves an element from map under given key. -func (m ConcurrentMap[K, V]) Get(key K) (V, bool) { +func (m *ConcurrentMulMap[K, V]) Get(key K) (V, bool) { // Get shard - shard := m.GetShard(key) + shard := m.getShard(key) shard.RLock() // Get item from shard. val, ok := shard.items[key] @@ -117,10 +147,14 @@ func (m ConcurrentMap[K, V]) Get(key K) (V, bool) { } // Count returns the number of elements within the map. -func (m ConcurrentMap[K, V]) Count() int { +func (m *ConcurrentMulMap[K, V]) Count() int { count := 0 - for i := 0; i < SHARD_COUNT; i++ { + for i := 0; i < m.shardsNum; i++ { shard := m.shards[i] + if shard == nil { + continue + } + shard.RLock() count += len(shard.items) shard.RUnlock() @@ -129,9 +163,9 @@ func (m ConcurrentMap[K, V]) Count() int { } // Looks up an item under specified key -func (m ConcurrentMap[K, V]) Has(key K) bool { +func (m *ConcurrentMulMap[K, V]) Has(key K) bool { // Get shard - shard := m.GetShard(key) + shard := m.getShard(key) shard.RLock() // See if element is within shard. _, ok := shard.items[key] @@ -140,9 +174,9 @@ func (m ConcurrentMap[K, V]) Has(key K) bool { } // Remove removes an element from the map. -func (m ConcurrentMap[K, V]) Remove(key K) { +func (m *ConcurrentMulMap[K, V]) Remove(key K) { // Try to get shard. - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() delete(shard.items, key) shard.Unlock() @@ -155,9 +189,9 @@ type RemoveCb[K any, V any] func(key K, v V, exists bool) bool // RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params // If callback returns true and element exists, it will remove it from the map // Returns the value returned by the callback (even if element was not present in the map) -func (m ConcurrentMap[K, V]) RemoveCb(key K, cb RemoveCb[K, V]) bool { +func (m *ConcurrentMulMap[K, V]) RemoveCb(key K, cb RemoveCb[K, V]) bool { // Try to get shard. - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() v, ok := shard.items[key] remove := cb(key, v, ok) @@ -169,9 +203,9 @@ func (m ConcurrentMap[K, V]) RemoveCb(key K, cb RemoveCb[K, V]) bool { } // Pop removes an element from the map and returns it -func (m ConcurrentMap[K, V]) Pop(key K) (v V, exists bool) { +func (m *ConcurrentMulMap[K, V]) Pop(key K) (v V, exists bool) { // Try to get shard. - shard := m.GetShard(key) + shard := m.getShard(key) shard.Lock() v, exists = shard.items[key] delete(shard.items, key) @@ -180,7 +214,7 @@ func (m ConcurrentMap[K, V]) Pop(key K) (v V, exists bool) { } // IsEmpty checks if map is empty. -func (m ConcurrentMap[K, V]) IsEmpty() bool { +func (m *ConcurrentMulMap[K, V]) IsEmpty() bool { return m.Count() == 0 } @@ -193,7 +227,7 @@ type Tuple[K comparable, V any] struct { // Iter returns an iterator which could be used in a for range loop. // // Deprecated: using IterBuffered() will get a better performence -func (m ConcurrentMap[K, V]) Iter() <-chan Tuple[K, V] { +func (m *ConcurrentMulMap[K, V]) Iter() <-chan Tuple[K, V] { chans := snapshot(m) ch := make(chan Tuple[K, V]) go fanIn(chans, ch) @@ -201,7 +235,7 @@ func (m ConcurrentMap[K, V]) Iter() <-chan Tuple[K, V] { } // IterBuffered returns a buffered iterator which could be used in a for range loop. -func (m ConcurrentMap[K, V]) IterBuffered() <-chan Tuple[K, V] { +func (m *ConcurrentMulMap[K, V]) IterBuffered() <-chan Tuple[K, V] { chans := snapshot(m) total := 0 for _, c := range chans { @@ -213,7 +247,7 @@ func (m ConcurrentMap[K, V]) IterBuffered() <-chan Tuple[K, V] { } // Clear removes all items from map. -func (m ConcurrentMap[K, V]) Clear() { +func (m *ConcurrentMulMap[K, V]) Clear() { for item := range m.IterBuffered() { m.Remove(item.Key) } @@ -223,17 +257,17 @@ func (m ConcurrentMap[K, V]) Clear() { // which likely takes a snapshot of `m`. // It returns once the size of each buffered channel is determined, // before all the channels are populated using goroutines. -func snapshot[K comparable, V any](m ConcurrentMap[K, V]) (chans []chan Tuple[K, V]) { - //When you access map items before initializing. +func snapshot[K Stringer, V any](m *ConcurrentMulMap[K, V]) (chans []chan Tuple[K, V]) { + // When you access map items before initializing. if len(m.shards) == 0 { - panic(`cmap.ConcurrentMap is not initialized. Should run New() before usage.`) + panic(`container.ConcurrentMulMap is not initialized. Should run New() before usage.`) } - chans = make([]chan Tuple[K, V], SHARD_COUNT) + chans = make([]chan Tuple[K, V], m.shardsNum) wg := sync.WaitGroup{} - wg.Add(SHARD_COUNT) + wg.Add(m.shardsNum) // Foreach shard. for index, shard := range m.shards { - go func(index int, shard *ConcurrentMapShared[K, V]) { + go func(index int, shard *concurrentMulMapShared[K, V]) { // Foreach key, value pair. shard.RLock() chans[index] = make(chan Tuple[K, V], len(shard.items)) @@ -250,7 +284,7 @@ func snapshot[K comparable, V any](m ConcurrentMap[K, V]) (chans []chan Tuple[K, } // fanIn reads elements from channels `chans` into channel `out` -func fanIn[K comparable, V any](chans []chan Tuple[K, V], out chan Tuple[K, V]) { +func fanIn[K Stringer, V any](chans []chan Tuple[K, V], out chan Tuple[K, V]) { wg := sync.WaitGroup{} wg.Add(len(chans)) for _, ch := range chans { @@ -266,14 +300,14 @@ func fanIn[K comparable, V any](chans []chan Tuple[K, V], out chan Tuple[K, V]) } // Items returns all items as map[string]V -func (m ConcurrentMap[K, V]) Items() map[K]V { +func (m *ConcurrentMulMap[K, V]) Items() map[K]V { tmp := make(map[K]V) - + // Insert items to temporary map. for item := range m.IterBuffered() { tmp[item.Key] = item.Val } - + return tmp } @@ -281,11 +315,11 @@ func (m ConcurrentMap[K, V]) Items() map[K]V { // maps. RLock is held for all calls for a given shard // therefore callback sess consistent view of a shard, // but not across the shards -type IterCb[K comparable, V any] func(key K, v V) +type IterCb[K Stringer, V any] func(key K, v V) // Callback based iterator, cheapest way to read // all elements in a map. -func (m ConcurrentMap[K, V]) IterCb(fn IterCb[K, V]) { +func (m *ConcurrentMulMap[K, V]) IterCb(fn IterCb[K, V]) { for idx := range m.shards { shard := (m.shards)[idx] shard.RLock() @@ -297,15 +331,15 @@ func (m ConcurrentMap[K, V]) IterCb(fn IterCb[K, V]) { } // Keys returns all keys as []string -func (m ConcurrentMap[K, V]) Keys() []K { +func (m *ConcurrentMulMap[K, V]) Keys() []K { count := m.Count() ch := make(chan K, count) go func() { // Foreach shard. wg := sync.WaitGroup{} - wg.Add(SHARD_COUNT) + wg.Add(m.shardsNum) for _, shard := range m.shards { - go func(shard *ConcurrentMapShared[K, V]) { + go func(shard *concurrentMulMapShared[K, V]) { // Foreach key, value pair. shard.RLock() for key := range shard.items { @@ -318,7 +352,7 @@ func (m ConcurrentMap[K, V]) Keys() []K { wg.Wait() close(ch) }() - + // Generate keys keys := make([]K, 0, count) for k := range ch { @@ -327,18 +361,19 @@ func (m ConcurrentMap[K, V]) Keys() []K { return keys } -// Reviles ConcurrentMap "private" variables to json marshal. -func (m ConcurrentMap[K, V]) MarshalJSON() ([]byte, error) { +// Reviles ConcurrentMulMap "private" variables to json marshal. +func (m *ConcurrentMulMap[K, V]) MarshalJSON() ([]byte, error) { // Create a temporary map, which will hold all item spread across shards. tmp := make(map[K]V) - + // Insert items to temporary map. for item := range m.IterBuffered() { tmp[item.Key] = item.Val } return json.Marshal(tmp) } -func strfnv32[K fmt.Stringer](key K) uint32 { + +func strfnv32[K Stringer](key K) uint32 { return fnv32(key.String()) } @@ -354,17 +389,101 @@ func fnv32(key string) uint32 { } // Reverse process of Marshal. -func (m *ConcurrentMap[K, V]) UnmarshalJSON(b []byte) (err error) { +func (m *ConcurrentMulMap[K, V]) UnmarshalJSON(b []byte) (err error) { tmp := make(map[K]V) - + // Unmarshal into a single map. if err := json.Unmarshal(b, &tmp); err != nil { return err } - + // foreach key,value pair in temporary map insert into our concurrent map. for key, val := range tmp { m.Set(key, val) } return nil } + +type String string + +func (s String) String() string { + return string(s) +} + +type Rune rune + +func (s Rune) String() string { + return string(s) +} + +type Runes []rune + +func (s Runes) String() string { + return string(s) +} + +type Bytes []byte + +func (s Bytes) String() string { + return string(s) +} + +type Int int + +func (i Int) String() string { + return strconv.Itoa(int(i)) +} + +type Int8 int8 + +func (i Int8) String() string { + return strconv.Itoa(int(i)) +} + +type Int16 int16 + +func (i Int16) String() string { + return strconv.Itoa(int(i)) +} + +type Int32 int32 + +func (i Int32) String() string { + return strconv.Itoa(int(i)) +} + +type Int64 int64 + +func (i Int64) String() string { + return strconv.FormatInt(int64(i), 10) +} + +type Uint uint + +func (i Uint) String() string { + return strconv.Itoa(int(i)) +} + +type Uint8 uint8 + +func (i Uint8) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +type Uint16 uint16 + +func (i Uint16) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +type Uint32 uint32 + +func (i Uint32) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +type Uint64 uint64 + +func (i Uint64) String() string { + return strconv.FormatUint(uint64(i), 10) +} diff --git a/concurrent_map_bench_test.go b/multiple_map_bench_test.go similarity index 77% rename from concurrent_map_bench_test.go rename to multiple_map_bench_test.go index 50cd075..02fe3cf 100644 --- a/concurrent_map_bench_test.go +++ b/multiple_map_bench_test.go @@ -1,4 +1,4 @@ -package cmap +package container import ( "strconv" @@ -6,18 +6,12 @@ import ( "testing" ) -type Integer int - -func (i Integer) String() string { - return strconv.Itoa(int(i)) -} - func BenchmarkItems(b *testing.B) { - m := New[Animal]() - - // Insert 100 elements. + m := NewMMap[String, Animal]() + + // Insert 10000 elements. for i := 0; i < 10000; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { m.Items() @@ -25,11 +19,11 @@ func BenchmarkItems(b *testing.B) { } func BenchmarkItemsInteger(b *testing.B) { - m := NewStringer[Integer, Animal]() - - // Insert 100 elements. + m := NewMMap[Int, Animal]() + + // Insert 10000 elements. for i := 0; i < 10000; i++ { - m.Set((Integer)(i), Animal{strconv.Itoa(i)}) + m.Set(Int(i), Animal{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { m.Items() @@ -40,11 +34,14 @@ func directSharding(key uint32) uint32 { } func BenchmarkItemsInt(b *testing.B) { - m := NewWithCustomShardingFunction[uint32, Animal](directSharding) - - // Insert 100 elements. + m := NewMMap[Uint32, Animal]( + WithShardFn[Uint32, Animal](func(key Uint32) uint32 { + return directSharding(uint32(key)) + })) + + // Insert 10000 elements. for i := 0; i < 10000; i++ { - m.Set((uint32)(i), Animal{strconv.Itoa(i)}) + m.Set(Uint32(i), Animal{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { m.Items() @@ -52,11 +49,11 @@ func BenchmarkItemsInt(b *testing.B) { } func BenchmarkMarshalJson(b *testing.B) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 10000; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { _, err := m.MarshalJSON() @@ -73,10 +70,10 @@ func BenchmarkStrconv(b *testing.B) { } func BenchmarkSingleInsertAbsent(b *testing.B) { - m := New[string]() + m := NewMMap[String, string]() b.ResetTimer() for i := 0; i < b.N; i++ { - m.Set(strconv.Itoa(i), "value") + m.Set(String(strconv.Itoa(i)), "value") } } @@ -89,7 +86,7 @@ func BenchmarkSingleInsertAbsentSyncMap(b *testing.B) { } func BenchmarkSingleInsertPresent(b *testing.B) { - m := New[string]() + m := NewMMap[String, string]() m.Set("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -106,13 +103,13 @@ func BenchmarkSingleInsertPresentSyncMap(b *testing.B) { } } -func benchmarkMultiInsertDifferent(b *testing.B) { - m := New[string]() +func benchmarkMultiInsertDifferent(b *testing.B, shardnum int) { + m := NewMMap[String, string](WithShardsNum[String, string](shardnum)) finished := make(chan struct{}, b.N) _, set := GetSet(m, finished) b.ResetTimer() for i := 0; i < b.N; i++ { - go set(strconv.Itoa(i), "value") + go set(String(strconv.Itoa(i)), "value") } for i := 0; i < b.N; i++ { <-finished @@ -123,7 +120,7 @@ func BenchmarkMultiInsertDifferentSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) _, set := GetSetSyncMap[string, string](&m, finished) - + b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i), "value") @@ -147,7 +144,7 @@ func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) { } func BenchmarkMultiInsertSame(b *testing.B) { - m := New[string]() + m := NewMMap[String, string]() finished := make(chan struct{}, b.N) _, set := GetSet(m, finished) m.Set("key", "value") @@ -175,7 +172,7 @@ func BenchmarkMultiInsertSameSyncMap(b *testing.B) { } func BenchmarkMultiGetSame(b *testing.B) { - m := New[string]() + m := NewMMap[String, string]() finished := make(chan struct{}, b.N) get, _ := GetSet(m, finished) m.Set("key", "value") @@ -202,15 +199,15 @@ func BenchmarkMultiGetSameSyncMap(b *testing.B) { } } -func benchmarkMultiGetSetDifferent(b *testing.B) { - m := New[string]() +func benchmarkMultiGetSetDifferent(b *testing.B, shardnum int) { + m := NewMMap[String, string](WithShardsNum[String, string](shardnum)) finished := make(chan struct{}, 2*b.N) get, set := GetSet(m, finished) m.Set("-1", "value") b.ResetTimer() for i := 0; i < b.N; i++ { - go set(strconv.Itoa(i-1), "value") - go get(strconv.Itoa(i), "value") + go set(String(strconv.Itoa(i-1)), "value") + go get(String(strconv.Itoa(i)), "value") } for i := 0; i < 2*b.N; i++ { <-finished @@ -245,17 +242,17 @@ func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 256) } -func benchmarkMultiGetSetBlock(b *testing.B) { - m := New[string]() +func benchmarkMultiGetSetBlock(b *testing.B, shardnum int) { + m := NewMMap[String, string](WithShardsNum[String, string](shardnum)) finished := make(chan struct{}, 2*b.N) get, set := GetSet(m, finished) for i := 0; i < b.N; i++ { - m.Set(strconv.Itoa(i%100), "value") + m.Set(String(strconv.Itoa(i%100)), "value") } b.ResetTimer() for i := 0; i < b.N; i++ { - go set(strconv.Itoa(i%100), "value") - go get(strconv.Itoa(i%100), "value") + go set(String(strconv.Itoa(i%100)), "value") + go get(String(strconv.Itoa(i%100)), "value") } for i := 0; i < 2*b.N; i++ { <-finished @@ -292,8 +289,9 @@ func BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetBlock, b, 256) } - -func GetSet[K comparable, V any](m ConcurrentMap[K, V], finished chan struct{}) (set func(key K, value V), get func(key K, value V)) { +func GetSet[K Stringer, V any](m *ConcurrentMulMap[K, V], finished chan struct{}) ( + set func(key K, value V), get func(key K, value V), +) { return func(key K, value V) { for i := 0; i < 10; i++ { m.Get(key) @@ -307,7 +305,9 @@ func GetSet[K comparable, V any](m ConcurrentMap[K, V], finished chan struct{}) } } -func GetSetSyncMap[K comparable, V any](m *sync.Map, finished chan struct{}) (get func(key K, value V), set func(key K, value V)) { +func GetSetSyncMap[K comparable, V any](m *sync.Map, finished chan struct{}) ( + get func(key K, value V), set func(key K, value V), +) { get = func(key K, value V) { for i := 0; i < 10; i++ { m.Load(key) @@ -323,19 +323,16 @@ func GetSetSyncMap[K comparable, V any](m *sync.Map, finished chan struct{}) (ge return } -func runWithShards(bench func(b *testing.B), b *testing.B, shardsCount int) { - oldShardsCount := SHARD_COUNT - SHARD_COUNT = shardsCount - bench(b) - SHARD_COUNT = oldShardsCount +func runWithShards(bench func(b *testing.B, shardnum int), b *testing.B, shardsCount int) { + bench(b, shardsCount) } func BenchmarkKeys(b *testing.B) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 10000; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { m.Keys() diff --git a/concurrent_map_test.go b/multiple_map_test.go similarity index 82% rename from concurrent_map_test.go rename to multiple_map_test.go index 4e5d52e..8815ff0 100644 --- a/concurrent_map_test.go +++ b/multiple_map_test.go @@ -1,4 +1,4 @@ -package cmap +package container import ( "encoding/json" @@ -13,34 +13,34 @@ type Animal struct { } func TestMapCreation(t *testing.T) { - m := New[string]() + m := NewMMap[String, any]() if m.shards == nil { t.Error("map is null.") } - + if m.Count() != 0 { t.Error("new map should be empty.") } } func TestInsert(t *testing.T) { - m := New[Animal]() + m := NewMMap[String, Animal]() elephant := Animal{"elephant"} monkey := Animal{"monkey"} - + m.Set("elephant", elephant) m.Set("monkey", monkey) - + if m.Count() != 2 { t.Error("map should contain exactly two elements.") } } func TestInsertAbsent(t *testing.T) { - m := New[Animal]() + m := NewMMap[String, Animal]() elephant := Animal{"elephant"} monkey := Animal{"monkey"} - + m.SetIfAbsent("elephant", elephant) if ok := m.SetIfAbsent("elephant", monkey); ok { t.Error("map set a new value even the entry is already present") @@ -48,295 +48,295 @@ func TestInsertAbsent(t *testing.T) { } func TestGet(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Get a missing element. val, ok := m.Get("Money") - + if ok == true { t.Error("ok should be false when item is missing from map.") } - + if (val != Animal{}) { t.Error("Missing values should return as null.") } - + elephant := Animal{"elephant"} m.Set("elephant", elephant) - + // Retrieve inserted element. elephant, ok = m.Get("elephant") if ok == false { t.Error("ok should be true for item stored within the map.") } - + if elephant.name != "elephant" { t.Error("item was modified.") } } func TestHas(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Get a missing element. if m.Has("Money") == true { t.Error("element shouldn't exists") } - + elephant := Animal{"elephant"} m.Set("elephant", elephant) - + if m.Has("elephant") == false { t.Error("element exists, expecting Has to return True.") } } func TestRemove(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + monkey := Animal{"monkey"} m.Set("monkey", monkey) - + m.Remove("monkey") - + if m.Count() != 0 { t.Error("Expecting count to be zero once item was removed.") } - + temp, ok := m.Get("monkey") - + if ok != false { t.Error("Expecting ok to be false for missing items.") } - + if (temp != Animal{}) { t.Error("Expecting item to be nil after its removal.") } - + // Remove a none existing element. m.Remove("noone") } func TestRemoveCb(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + monkey := Animal{"monkey"} m.Set("monkey", monkey) elephant := Animal{"elephant"} m.Set("elephant", elephant) - + var ( mapKey string mapVal Animal wasFound bool ) - cb := func(key string, val Animal, exists bool) bool { - mapKey = key + cb := func(key String, val Animal, exists bool) bool { + mapKey = string(key) mapVal = val wasFound = exists - + return val.name == "monkey" } - + // Monkey should be removed result := m.RemoveCb("monkey", cb) if !result { t.Errorf("Result was not true") } - + if mapKey != "monkey" { t.Error("Wrong key was provided to the callback") } - + if mapVal != monkey { t.Errorf("Wrong value was provided to the value") } - + if !wasFound { t.Errorf("Key was not found") } - + if m.Has("monkey") { t.Errorf("Key was not removed") } - + // Elephant should not be removed result = m.RemoveCb("elephant", cb) if result { t.Errorf("Result was true") } - + if mapKey != "elephant" { t.Error("Wrong key was provided to the callback") } - + if mapVal != elephant { t.Errorf("Wrong value was provided to the value") } - + if !wasFound { t.Errorf("Key was not found") } - + if !m.Has("elephant") { t.Errorf("Key was removed") } - + // Unset key should remain unset result = m.RemoveCb("horse", cb) if result { t.Errorf("Result was true") } - + if mapKey != "horse" { t.Error("Wrong key was provided to the callback") } - + if (mapVal != Animal{}) { t.Errorf("Wrong value was provided to the value") } - + if wasFound { t.Errorf("Key was found") } - + if m.Has("horse") { t.Errorf("Key was created") } } func TestPop(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + monkey := Animal{"monkey"} m.Set("monkey", monkey) - + v, exists := m.Pop("monkey") - + if !exists || v != monkey { t.Error("Pop didn't find a monkey.") } - + v2, exists2 := m.Pop("monkey") - + if exists2 || v2 == monkey { t.Error("Pop keeps finding monkey") } - + if m.Count() != 0 { t.Error("Expecting count to be zero once item was Pop'ed.") } - + temp, ok := m.Get("monkey") - + if ok != false { t.Error("Expecting ok to be false for missing items.") } - + if (temp != Animal{}) { t.Error("Expecting item to be nil after its removal.") } } func TestCount(t *testing.T) { - m := New[Animal]() + m := NewMMap[String, Animal]() for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + if m.Count() != 100 { t.Error("Expecting 100 element within map.") } } func TestIsEmpty(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + if m.IsEmpty() == false { t.Error("new map should be empty") } - + m.Set("elephant", Animal{"elephant"}) - + if m.IsEmpty() != false { t.Error("map shouldn't be empty.") } } func TestIterator(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + counter := 0 // Iterate over elements. for item := range m.Iter() { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 100 { t.Error("We should have counted 100 elements.") } } func TestBufferedIterator(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + counter := 0 // Iterate over elements. for item := range m.IterBuffered() { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 100 { t.Error("We should have counted 100 elements.") } } func TestClear(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + m.Clear() - + if m.Count() != 0 { t.Error("We should have 0 elements.") } } func TestIterCb(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + counter := 0 // Iterate over elements. - m.IterCb(func(key string, v Animal) { + m.IterCb(func(key String, v Animal) { counter++ }) if counter != 100 { @@ -345,53 +345,53 @@ func TestIterCb(t *testing.T) { } func TestItems(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + items := m.Items() - + if len(items) != 100 { t.Error("We should have counted 100 elements.") } } func TestConcurrent(t *testing.T) { - m := New[int]() + m := NewMMap[String, int]() ch := make(chan int) const iterations = 1000 var a [iterations]int - + // Using go routines insert 1000 ints into our map. go func() { for i := 0; i < iterations/2; i++ { // Add item to map. - m.Set(strconv.Itoa(i), i) - + m.Set(String(strconv.Itoa(i)), i) + // Retrieve item from map. - val, _ := m.Get(strconv.Itoa(i)) - + val, _ := m.Get(String(strconv.Itoa(i))) + // Write to channel inserted value. ch <- val } // Call go routine with current index. }() - + go func() { for i := iterations / 2; i < iterations; i++ { // Add item to map. - m.Set(strconv.Itoa(i), i) - + m.Set(String(strconv.Itoa(i)), i) + // Retrieve item from map. - val, _ := m.Get(strconv.Itoa(i)) - + val, _ := m.Get(String(strconv.Itoa(i))) + // Write to channel inserted value. ch <- val } // Call go routine with current index. }() - + // Wait for all go routines to finish. counter := 0 for elem := range ch { @@ -401,15 +401,15 @@ func TestConcurrent(t *testing.T) { break } } - + // Sorts array, will make is simpler to verify all inserted values we're returned. sort.Ints(a[0:iterations]) - + // Make sure map contains 1000 elements. if m.Count() != iterations { t.Error("Expecting 1000 elements.") } - + // Make sure all inserted values we're fetched from map. for i := 0; i < iterations; i++ { if i != a[i] { @@ -419,19 +419,19 @@ func TestConcurrent(t *testing.T) { } func TestJsonMarshal(t *testing.T) { - SHARD_COUNT = 2 + SHARD_COUNT := 2 defer func() { SHARD_COUNT = 32 }() expected := "{\"a\":1,\"b\":2}" - m := New[int]() + m := NewMMap[String, int](WithShardsNum[String, int](SHARD_COUNT)) m.Set("a", 1) m.Set("b", 2) j, err := json.Marshal(m) if err != nil { t.Error(err) } - + if string(j) != expected { t.Error("json", string(j), "differ from expected", expected) return @@ -439,13 +439,13 @@ func TestJsonMarshal(t *testing.T) { } func TestKeys(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. for i := 0; i < 100; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + keys := m.Keys() if len(keys) != 100 { t.Error("We should have counted 100 elements.") @@ -453,13 +453,13 @@ func TestKeys(t *testing.T) { } func TestMInsert(t *testing.T) { - animals := map[string]Animal{ + animals := map[String]Animal{ "elephant": {"elephant"}, "monkey": {"monkey"}, } - m := New[Animal]() + m := NewMMap[String, Animal]() m.MSet(animals) - + if m.Count() != 2 { t.Error("map should contain exactly two elements.") } @@ -467,7 +467,7 @@ func TestMInsert(t *testing.T) { func TestFnv32(t *testing.T) { key := []byte("ABC") - + hasher := fnv.New32() _, err := hasher.Write(key) if err != nil { @@ -476,7 +476,7 @@ func TestFnv32(t *testing.T) { if fnv32(string(key)) != hasher.Sum32() { t.Errorf("Bundled fnv32 produced %d, expected result from hash/fnv32 is %d", fnv32(string(key)), hasher.Sum32()) } - + } func TestUpsert(t *testing.T) { @@ -484,7 +484,7 @@ func TestUpsert(t *testing.T) { whale := Animal{"whale"} tiger := Animal{"tiger"} lion := Animal{"lion"} - + cb := func(exists bool, valueInMap Animal, newValue Animal) Animal { if !exists { return newValue @@ -492,22 +492,22 @@ func TestUpsert(t *testing.T) { valueInMap.name += newValue.name return valueInMap } - - m := New[Animal]() + + m := NewMMap[String, Animal]() m.Set("marine", dolphin) m.Upsert("marine", whale, cb) m.Upsert("predator", tiger, cb) m.Upsert("predator", lion, cb) - + if m.Count() != 2 { t.Error("map should contain exactly two elements.") } - + marineAnimals, ok := m.Get("marine") if marineAnimals.name != "dolphinwhale" || !ok { t.Error("Set, then Upsert failed") } - + predators, ok := m.Get("predator") if !ok || predators.name != "tigerlion" { t.Error("Upsert, then Upsert failed") @@ -515,20 +515,20 @@ func TestUpsert(t *testing.T) { } func TestKeysWhenRemoving(t *testing.T) { - m := New[Animal]() - + m := NewMMap[String, Animal]() + // Insert 100 elements. Total := 100 for i := 0; i < Total; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } - + // Remove 10 elements concurrently. Num := 10 for i := 0; i < Num; i++ { - go func(c *ConcurrentMap[string, Animal], n int) { - c.Remove(strconv.Itoa(n)) - }(&m, i) + go func(c *ConcurrentMulMap[String, Animal], n int) { + c.Remove(String(strconv.Itoa(n))) + }(m, i) } keys := m.Keys() for _, k := range keys { @@ -539,18 +539,18 @@ func TestKeysWhenRemoving(t *testing.T) { } func TestUnDrainedIter(t *testing.T) { - m := New[Animal]() + m := NewMMap[String, Animal]() // Insert 100 elements. Total := 100 for i := 0; i < Total; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } counter := 0 // Iterate over elements. ch := m.Iter() for item := range ch { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } @@ -560,49 +560,49 @@ func TestUnDrainedIter(t *testing.T) { } } for i := Total; i < 2*Total; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } for item := range ch { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 100 { t.Error("We should have been right where we stopped") } - + counter = 0 for item := range m.IterBuffered() { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 200 { t.Error("We should have counted 200 elements.") } } func TestUnDrainedIterBuffered(t *testing.T) { - m := New[Animal]() + m := NewMMap[String, Animal]() // Insert 100 elements. Total := 100 for i := 0; i < Total; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } counter := 0 // Iterate over elements. ch := m.IterBuffered() for item := range ch { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } @@ -612,31 +612,31 @@ func TestUnDrainedIterBuffered(t *testing.T) { } } for i := Total; i < 2*Total; i++ { - m.Set(strconv.Itoa(i), Animal{strconv.Itoa(i)}) + m.Set(String(strconv.Itoa(i)), Animal{strconv.Itoa(i)}) } for item := range ch { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 100 { t.Error("We should have been right where we stopped") } - + counter = 0 for item := range m.IterBuffered() { val := item.Val - + if (val == Animal{}) { t.Error("Expecting an object.") } counter++ } - + if counter != 200 { t.Error("We should have counted 200 elements.") }