diff --git a/concurrent_map.go b/concurrent_map.go index baccab0..9247846 100644 --- a/concurrent_map.go +++ b/concurrent_map.go @@ -42,6 +42,11 @@ func New[V any]() ConcurrentMap[string, V] { return create[string, V](fnv32) } +// Creates a new concurrent map with generic key type. +func NewGeneric[K comparable, V any]() ConcurrentMap[K, V] { + return create[K, V](genericfnv32[K]) +} + // Creates a new concurrent map. func NewStringer[K Stringer, V any]() ConcurrentMap[K, V] { return create[K, V](strfnv32[K]) @@ -224,7 +229,7 @@ func (m ConcurrentMap[K, V]) Clear() { // 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. + // When you access map items before initializing. if len(m.shards) == 0 { panic(`cmap.ConcurrentMap is not initialized. Should run New() before usage.`) } @@ -338,6 +343,11 @@ func (m ConcurrentMap[K, V]) MarshalJSON() ([]byte, error) { } return json.Marshal(tmp) } + +func genericfnv32[K comparable](key K) uint32 { + return fnv32(fmt.Sprintf("%v", key)) +} + func strfnv32[K fmt.Stringer](key K) uint32 { return fnv32(key.String()) } diff --git a/concurrent_map_bench_test.go b/concurrent_map_bench_test.go index 50cd075..9a7ec3e 100644 --- a/concurrent_map_bench_test.go +++ b/concurrent_map_bench_test.go @@ -24,6 +24,30 @@ func BenchmarkItems(b *testing.B) { } } +func BenchmarkGenericStruct(b *testing.B) { + m := NewGeneric[Animal, int]() + + // Insert 10000 elements. + for i := 0; i < 10000; i++ { + m.Set(Animal{strconv.Itoa(i)}, i) + } + for i := 0; i < b.N; i++ { + m.Items() + } +} + +func BenchmarkGenericInt(b *testing.B) { + m := NewGeneric[int, Animal]() + + // Insert 10000 elements. + for i := 0; i < 10000; i++ { + m.Set(i, Animal{strconv.Itoa(i)}) + } + for i := 0; i < b.N; i++ { + m.Items() + } +} + func BenchmarkItemsInteger(b *testing.B) { m := NewStringer[Integer, Animal]() @@ -35,6 +59,7 @@ func BenchmarkItemsInteger(b *testing.B) { m.Items() } } + func directSharding(key uint32) uint32 { return key } @@ -136,12 +161,15 @@ func BenchmarkMultiInsertDifferentSyncMap(b *testing.B) { func BenchmarkMultiInsertDifferent_1_Shard(b *testing.B) { runWithShards(benchmarkMultiInsertDifferent, b, 1) } + func BenchmarkMultiInsertDifferent_16_Shard(b *testing.B) { runWithShards(benchmarkMultiInsertDifferent, b, 16) } + func BenchmarkMultiInsertDifferent_32_Shard(b *testing.B) { runWithShards(benchmarkMultiInsertDifferent, b, 32) } + func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 256) } @@ -235,12 +263,15 @@ func BenchmarkMultiGetSetDifferentSyncMap(b *testing.B) { func BenchmarkMultiGetSetDifferent_1_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 1) } + func BenchmarkMultiGetSetDifferent_16_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 16) } + func BenchmarkMultiGetSetDifferent_32_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 32) } + func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetDifferent, b, 256) } @@ -282,17 +313,19 @@ func BenchmarkMultiGetSetBlockSyncMap(b *testing.B) { func BenchmarkMultiGetSetBlock_1_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetBlock, b, 1) } + func BenchmarkMultiGetSetBlock_16_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetBlock, b, 16) } + func BenchmarkMultiGetSetBlock_32_Shard(b *testing.B) { runWithShards(benchmarkMultiGetSetBlock, b, 32) } + 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)) { return func(key K, value V) { for i := 0; i < 10; i++ { diff --git a/concurrent_map_test.go b/concurrent_map_test.go index 4e5d52e..37b36af 100644 --- a/concurrent_map_test.go +++ b/concurrent_map_test.go @@ -476,7 +476,6 @@ 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) { @@ -641,3 +640,68 @@ func TestUnDrainedIterBuffered(t *testing.T) { t.Error("We should have counted 200 elements.") } } + +func TestMapGenericInt(t *testing.T) { + m := NewGeneric[int, string]() + if m.shards == nil { + t.Error("map is null.") + } + if m.Count() != 0 { + t.Error("new map should be empty.") + } + + m.Set(1, "elephant") + m.Set(2, "monkey") + value, ok := m.Get(1) + if !ok || value != "elephant" { + t.Error("Cannot get value of key 1, which should be elephant") + } + value, ok = m.Get(2) + if !ok || value != "monkey" { + t.Error("Cannot get value of key 2, which should be monkey") + } + + if m.Count() != 2 { + t.Error("map should contain exactly two elements.") + } + + m.Remove(1) + m.Remove(2) + if m.Count() != 0 { + t.Error("Expecting count to be zero once item was removed.") + } +} + +func TestMapGenericStruct(t *testing.T) { + type TmpStruct struct { + Name string + } + m := NewGeneric[TmpStruct, string]() + if m.shards == nil { + t.Error("map is null.") + } + if m.Count() != 0 { + t.Error("new map should be empty.") + } + + m.Set(TmpStruct{"elephant"}, "elephant") + m.Set(TmpStruct{"monkey"}, "monkey") + value, ok := m.Get(TmpStruct{"elephant"}) + if !ok || value != "elephant" { + t.Error("Cannot get value of key elephant, which should be elephant") + } + value, ok = m.Get(TmpStruct{"monkey"}) + if !ok || value != "monkey" { + t.Error("Cannot get value of key monkey, which should be monkey") + } + + if m.Count() != 2 { + t.Error("map should contain exactly two elements.") + } + + m.Remove(TmpStruct{"elephant"}) + m.Remove(TmpStruct{"monkey"}) + if m.Count() != 0 { + t.Error("Expecting count to be zero once item was removed.") + } +}