Skip to content

Commit 5178093

Browse files
authored
feat: supply concurrent an non-concurrent verions (#5)
* feat: provide concurrent and non-concurrent version of both maps * refactor: make benchmarking functions parameterized
1 parent 24e5d4e commit 5178093

File tree

9 files changed

+1076
-407
lines changed

9 files changed

+1076
-407
lines changed

README.md

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ See docs: https://pkg.go.dev/github.com/aeimer/go-multikeymap
55
DISCLAIMER: Until version 1 is reached, the API may change.
66

77
A go lib which handles maps with multiple keys.
8-
Both data-structures are go routine safe.
8+
Both data-structures are available in go routine safe (concurrent) and a non-concurrent version.
99

1010
* **MultiKeyMap** is a data structure based on go native maps.
1111
It has a primary key which directly maps to the values.
@@ -32,7 +32,8 @@ func main() {
3232
Name string
3333
Population int
3434
}
35-
mm := multikeymap.NewMultiKeyMap[string, City]()
35+
mm := multikeymap.New[string, City]()
36+
// or: mm := multikeymap.NewConcurrent[string, City]()
3637
mm.Put("Berlin", City{"Berlin", 3_500_000})
3738
mm.PutSecondaryKeys("Berlin", "postcode", "10115", "10117", "10119")
3839
mm.Get("Berlin") // City{"Berlin", 3_500_000}
@@ -43,20 +44,37 @@ func main() {
4344
Benchmark results (`task gotb`):
4445

4546
```
46-
BenchmarkMultiKeyMapGet100-12 770486 1551 ns/op 0 B/op 0 allocs/op
47-
BenchmarkMultiKeyMapGet1000-12 32757 36681 ns/op 2880 B/op 900 allocs/op
48-
BenchmarkMultiKeyMapGet10000-12 2816 434499 ns/op 38880 B/op 9900 allocs/op
49-
BenchmarkMultiKeyMapGet100000-12 241 4755312 ns/op 518884 B/op 99900 allocs/op
50-
51-
BenchmarkMultiKeyMapPut100-12 450188 2608 ns/op 0 B/op 0 allocs/op
52-
BenchmarkMultiKeyMapPut1000-12 30291 39424 ns/op 2884 B/op 900 allocs/op
53-
BenchmarkMultiKeyMapPut10000-12 2382 455831 ns/op 39291 B/op 9900 allocs/op
54-
BenchmarkMultiKeyMapPut100000-12 220 5090152 ns/op 555726 B/op 99918 allocs/op
55-
56-
BenchmarkMultiKeyMapRemove100-12 507541 2374 ns/op 0 B/op 0 allocs/op
57-
BenchmarkMultiKeyMapRemove1000-12 42507 28215 ns/op 2880 B/op 900 allocs/op
58-
BenchmarkMultiKeyMapRemove10000-12 4044 289343 ns/op 38880 B/op 9900 allocs/op
59-
BenchmarkMultiKeyMapRemove100000-12 414 2850928 ns/op 518897 B/op 99900 allocs/op
47+
# => Non-Concurrent <= Iterations
48+
BenchmarkMultiKeyMapGet/size_100-12 1430344 824.4 ns/ 0 B/op 0 allocs/op
49+
BenchmarkMultiKeyMapGet/size_1000-12 36643 33912 ns/op 2880 B/op 900 allocs/op
50+
BenchmarkMultiKeyMapGet/size_10000-12 3036 390264 ns/op 38880 B/op 9900 allocs/op
51+
BenchmarkMultiKeyMapGet/size_100000-12 261 4601110 ns/op 518882 B/op 99900 allocs/op
52+
53+
BenchmarkMultiKeyMapPut/size_100-12 888855 1412 ns/op 0 B/op 0 allocs/op
54+
BenchmarkMultiKeyMapPut/size_1000-12 36153 33815 ns/op 2883 B/op 900 allocs/op
55+
BenchmarkMultiKeyMapPut/size_10000-12 3026 394762 ns/op 39202 B/op 9900 allocs/op
56+
BenchmarkMultiKeyMapPut/size_100000-12 248 4571614 ns/op 551453 B/op 99915 allocs/op
57+
58+
BenchmarkMultiKeyMapRemove/size_100-12 2821974 425.8 ns/ 0 B/op 0 allocs/op
59+
BenchmarkMultiKeyMapRemove/size_1000-12 78538 15281 ns/op 2880 B/op 900 allocs/op
60+
BenchmarkMultiKeyMapRemove/size_10000-12 6922 169382 ns/op 38880 B/op 9900 allocs/op
61+
BenchmarkMultiKeyMapRemove/size_100000-12 670 1764847 ns/op 518884 B/op 99900 allocs/op
62+
63+
# => Concurrent <= Iterations
64+
BenchmarkConcurrentMultiKeyMapGet/size_100-12 735793 1578 ns/op 0 B/op 0 allocs/op
65+
BenchmarkConcurrentMultiKeyMapGet/size_1000-12 32385 37066 ns/op 2880 B/op 900 allocs/op
66+
BenchmarkConcurrentMultiKeyMapGet/size_10000-12 2756 433572 ns/op 38880 B/op 9900 allocs/op
67+
BenchmarkConcurrentMultiKeyMapGet/size_100000-12 240 4950545 ns/op 518883 B/op 99900 allocs/op
68+
69+
BenchmarkConcurrentMultiKeyMapPut/size_100-12 454376 2609 ns/op 0 B/op 0 allocs/op
70+
BenchmarkConcurrentMultiKeyMapPut/size_1000-12 29721 40303 ns/op 2884 B/op 900 allocs/op
71+
BenchmarkConcurrentMultiKeyMapPut/size_10000-12 2592 459977 ns/op 39256 B/op 9900 allocs/op
72+
BenchmarkConcurrentMultiKeyMapPut/size_100000-12 224 5369616 ns/op 555038 B/op 99918 allocs/op
73+
74+
BenchmarkConcurrentMultiKeyMapRemove/size_100-12 490444 2412 ns/op 0 B/op 0 allocs/op
75+
BenchmarkConcurrentMultiKeyMapRemove/size_1000-12 41236 29073 ns/op 2880 B/op 900 allocs/op
76+
BenchmarkConcurrentMultiKeyMapRemove/size_10000-12 4030 298559 ns/op 38880 B/op 9900 allocs/op
77+
BenchmarkConcurrentMultiKeyMapRemove/size_100000-12 400 2968791 ns/op 518884 B/op 99900 allocs/op
6078
```
6179

6280
# BiKeyMap
@@ -75,7 +93,8 @@ func main() {
7593
Population int
7694
}
7795
// keyA: Cityname, keyB: Population
78-
bm := bikeymap.NewBiKeyMap[string, int, City]()
96+
bm := bikeymap.New[string, int, City]()
97+
// or: bm := bikeymap.NewConcurrent[string, int, City]()
7998
bm.Put("Berlin", 3_500_000, City{"Berlin", 3_500_000})
8099
bm.Put("Hamburg", 1_800_000, City{"Hamburg", 1_800_000})
81100
bm.GetByKeyA("Berlin") // City{"Berlin", 3_500_000}
@@ -86,20 +105,37 @@ func main() {
86105
Benchmark results (`task gotb`):
87106

88107
```
89-
BenchmarkBiKeyMapGet100-12 348357 3399 ns/op 0 B/op 0 allocs/op
90-
BenchmarkBiKeyMapGet1000-12 20005 58957 ns/op 2880 B/op 900 allocs/op
91-
BenchmarkBiKeyMapGet10000-12 1480 816056 ns/op 38880 B/op 9900 allocs/op
92-
BenchmarkBiKeyMapGet100000-12 128 9118454 ns/op 518881 B/op 99900 allocs/op
93-
94-
BenchmarkBiKeyMapPut100-12 211629 5452 ns/op 0 B/op 0 allocs/op
95-
BenchmarkBiKeyMapPut1000-12 12333 97718 ns/op 5792 B/op 1800 allocs/op
96-
BenchmarkBiKeyMapPut10000-12 907 1313887 ns/op 81296 B/op 19800 allocs/op
97-
BenchmarkBiKeyMapPut100000-12 78 20215060 ns/op 1381083 B/op 199951 allocs/op
98-
99-
BenchmarkBiKeyMapRemove100-12 344187 3304 ns/op 1600 B/op 99 allocs/op
100-
BenchmarkBiKeyMapRemove1000-12 36328 33004 ns/op 15999 B/op 999 allocs/op
101-
BenchmarkBiKeyMapRemove10000-12 3446 334289 ns/op 159953 B/op 9997 allocs/op
102-
BenchmarkBiKeyMapRemove100000-12 351 3368186 ns/op 1595457 B/op 99715 allocs/op
108+
# => Non-Concurrent <= Iterations
109+
BenchmarkBiKeyMapGet/size_100-12 531668 2094 ns/op 0 B/op 0 allocs/op
110+
BenchmarkBiKeyMapGet/size_1000-12 24627 49464 ns/op 2880 B/op 900 allocs/op
111+
BenchmarkBiKeyMapGet/size_10000-12 1644 729522 ns/op 38880 B/op 9900 allocs/op
112+
BenchmarkBiKeyMapGet/size_100000-12 139 8544642 ns/op 518882 B/op 99900 allocs/op
113+
114+
BenchmarkBiKeyMapPut/size_100-12 254956 4653 ns/op 0 B/op 0 allocs/op
115+
BenchmarkBiKeyMapPut/size_1000-12 12880 92922 ns/op 5791 B/op 1800 allocs/op
116+
BenchmarkBiKeyMapPut/size_10000-12 914 1289536 ns/op 81248 B/op 19800 allocs/op
117+
BenchmarkBiKeyMapPut/size_100000-12 67 17404694 ns/op 1436872 B/op 199973 allocs/op
118+
119+
BenchmarkBiKeyMapRemove/size_100-12 5802380 206.4 ns/op 0 B/op 0 allocs/op
120+
BenchmarkBiKeyMapRemove/size_1000-12 612367 1984 ns/op 0 B/op 0 allocs/op
121+
BenchmarkBiKeyMapRemove/size_10000-12 60577 19730 ns/op 0 B/op 0 allocs/op
122+
BenchmarkBiKeyMapRemove/size_100000-12 5990 199194 ns/op 0 B/op 0 allocs/op
123+
124+
# => Concurrent <= Iterations
125+
BenchmarkConcurrentBiKeyMapGet/size_100-12 325862 3638 ns/op 0 B/op 0 allocs/op
126+
BenchmarkConcurrentBiKeyMapGet/size_1000-12 19471 61879 ns/op 2880 B/op 900 allocs/op
127+
BenchmarkConcurrentBiKeyMapGet/size_10000-12 1428 832351 ns/op 38880 B/op 9900 allocs/op
128+
BenchmarkConcurrentBiKeyMapGet/size_100000-12 100 10026768 ns/op 518882 B/op 99900 allocs/op
129+
130+
BenchmarkConcurrentBiKeyMapPut/size_100-12 207386 5683 ns/op 0 B/op 0 allocs/op
131+
BenchmarkConcurrentBiKeyMapPut/size_1000-12 12157 99885 ns/op 5793 B/op 1800 allocs/op
132+
BenchmarkConcurrentBiKeyMapPut/size_10000-12 870 1371878 ns/op 81438 B/op 19800 allocs/op
133+
BenchmarkConcurrentBiKeyMapPut/size_100000-12 62 19618138 ns/op 1469608 B/op 199989 allocs/op
134+
135+
BenchmarkConcurrentBiKeyMapRemove/size_100-12 353018 3667 ns/op 1599 B/op 99 allocs/op
136+
BenchmarkConcurrentBiKeyMapRemove/size_1000-12 34488 34653 ns/op 15999 B/op 999 allocs/op
137+
BenchmarkConcurrentBiKeyMapRemove/size_10000-12 3363 364001 ns/op 159952 B/op 9997 allocs/op
138+
BenchmarkConcurrentBiKeyMapRemove/size_100000-12 320 3551128 ns/op 1595034 B/op 99687 allocs/op
103139
```
104140

105141
## Contribution

bikeymap/bikeymap.go

Lines changed: 34 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package bikeymap
33
import (
44
"errors"
55
"fmt"
6-
"sync"
76
)
87

98
// BiKeyMap is a generic in-memory map with two independent keys for each value.
109
// It implements container/Container.
1110
type BiKeyMap[KeyA comparable, KeyB comparable, V any] struct {
12-
mu sync.RWMutex
1311
dataByKeyA map[KeyA]V
1412
keyAByKeyB map[KeyB]KeyA
1513
keyBByKeyA map[KeyA]KeyB
@@ -25,123 +23,99 @@ func New[KeyA comparable, KeyB comparable, V any]() *BiKeyMap[KeyA, KeyB, V] {
2523
}
2624

2725
// Put stores a value with two keys. It only fails if one of the keys is already set without the other.
28-
func (c *BiKeyMap[KeyA, KeyB, V]) Put(keyA KeyA, keyB KeyB, value V) error {
29-
c.mu.Lock()
30-
defer c.mu.Unlock()
31-
26+
func (m *BiKeyMap[KeyA, KeyB, V]) Put(keyA KeyA, keyB KeyB, value V) error {
3227
// Check if one key is set without the other, or if they do not point to each other.
33-
if existingKeyA, keyBExists := c.keyAByKeyB[keyB]; keyBExists && existingKeyA != keyA {
28+
if existingKeyA, keyBExists := m.keyAByKeyB[keyB]; keyBExists && existingKeyA != keyA {
3429
return errors.New("keyB is already set with a different keyA")
3530
}
36-
if existingKeyB, keyAExists := c.keyBByKeyA[keyA]; keyAExists && existingKeyB != keyB {
31+
if existingKeyB, keyAExists := m.keyBByKeyA[keyA]; keyAExists && existingKeyB != keyB {
3732
return errors.New("keyA is already set with a different keyB")
3833
}
3934

4035
// Put the new values for both keys.
41-
c.dataByKeyA[keyA] = value
42-
c.keyAByKeyB[keyB] = keyA
43-
c.keyBByKeyA[keyA] = keyB
36+
m.dataByKeyA[keyA] = value
37+
m.keyAByKeyB[keyB] = keyA
38+
m.keyBByKeyA[keyA] = keyB
4439
return nil
4540
}
4641

4742
// GetByKeyA retrieves a value using the first key.
48-
func (c *BiKeyMap[KeyA, KeyB, V]) GetByKeyA(keyA KeyA) (V, bool) {
49-
c.mu.RLock()
50-
defer c.mu.RUnlock()
51-
52-
value, exists := c.dataByKeyA[keyA]
43+
func (m *BiKeyMap[KeyA, KeyB, V]) GetByKeyA(keyA KeyA) (V, bool) {
44+
value, exists := m.dataByKeyA[keyA]
5345
return value, exists
5446
}
5547

5648
// GetByKeyB retrieves a value using the second key.
57-
func (c *BiKeyMap[KeyA, KeyB, V]) GetByKeyB(keyB KeyB) (V, bool) {
58-
c.mu.RLock()
59-
defer c.mu.RUnlock()
60-
61-
keyA, exists := c.keyAByKeyB[keyB]
49+
func (m *BiKeyMap[KeyA, KeyB, V]) GetByKeyB(keyB KeyB) (V, bool) {
50+
keyA, exists := m.keyAByKeyB[keyB]
6251
if !exists {
6352
var zero V
6453
return zero, false
6554
}
6655

67-
value, exists := c.dataByKeyA[keyA]
56+
value, exists := m.dataByKeyA[keyA]
6857
return value, exists
6958
}
7059

7160
// RemoveByKeyA removes a value using the first key, ensuring the corresponding second key is also deleted.
72-
func (c *BiKeyMap[KeyA, KeyB, V]) RemoveByKeyA(keyA KeyA) error {
73-
c.mu.Lock()
74-
defer c.mu.Unlock()
75-
61+
func (m *BiKeyMap[KeyA, KeyB, V]) RemoveByKeyA(keyA KeyA) error {
7662
// Verify that keyA exists and retrieve the associated keyB.
77-
keyB, ok := c.keyBByKeyA[keyA]
63+
keyB, ok := m.keyBByKeyA[keyA]
7864
if !ok {
7965
return errors.New("keyA does not exist")
8066
}
8167

8268
// Remove keyA, keyB, and the associated value.
83-
delete(c.dataByKeyA, keyA)
84-
delete(c.keyAByKeyB, keyB)
85-
delete(c.keyBByKeyA, keyA)
69+
delete(m.dataByKeyA, keyA)
70+
delete(m.keyAByKeyB, keyB)
71+
delete(m.keyBByKeyA, keyA)
8672

8773
return nil
8874
}
8975

9076
// RemoveByKeyB removes a value using the second key, ensuring the corresponding first key is also deleted.
91-
func (c *BiKeyMap[KeyA, KeyB, V]) RemoveByKeyB(keyB KeyB) error {
92-
c.mu.Lock()
93-
defer c.mu.Unlock()
94-
77+
func (m *BiKeyMap[KeyA, KeyB, V]) RemoveByKeyB(keyB KeyB) error {
9578
// Verify that keyB exists and retrieve the associated keyA.
96-
keyA, ok := c.keyAByKeyB[keyB]
79+
keyA, ok := m.keyAByKeyB[keyB]
9780
if !ok {
9881
return errors.New("keyB does not exist")
9982
}
10083

10184
// Remove keyA, keyB, and the associated value.
102-
delete(c.dataByKeyA, keyA)
103-
delete(c.keyAByKeyB, keyB)
104-
delete(c.keyBByKeyA, keyA)
85+
delete(m.dataByKeyA, keyA)
86+
delete(m.keyAByKeyB, keyB)
87+
delete(m.keyBByKeyA, keyA)
10588

10689
return nil
10790
}
10891

10992
// Empty checks if the map is empty.
110-
func (c *BiKeyMap[KeyA, KeyB, V]) Empty() bool {
111-
return len(c.dataByKeyA) == 0
93+
func (m *BiKeyMap[KeyA, KeyB, V]) Empty() bool {
94+
return len(m.dataByKeyA) == 0
11295
}
11396

11497
// Size returns the number of elements in the map.
115-
func (c *BiKeyMap[KeyA, KeyB, V]) Size() int {
116-
return len(c.dataByKeyA)
98+
func (m *BiKeyMap[KeyA, KeyB, V]) Size() int {
99+
return len(m.dataByKeyA)
117100
}
118101

119102
// Values returns a slice of all values in the map.
120-
func (c *BiKeyMap[KeyA, KeyB, V]) Values() []V {
121-
c.mu.RLock()
122-
defer c.mu.RUnlock()
123-
124-
values := make([]V, 0, len(c.dataByKeyA))
125-
for _, value := range c.dataByKeyA {
103+
func (m *BiKeyMap[KeyA, KeyB, V]) Values() []V {
104+
values := make([]V, 0, len(m.dataByKeyA))
105+
for _, value := range m.dataByKeyA {
126106
values = append(values, value)
127107
}
128108
return values
129109
}
130110

131111
// Clear removes all elements from the map.
132-
func (c *BiKeyMap[KeyA, KeyB, V]) Clear() {
133-
c.mu.Lock()
134-
defer c.mu.Unlock()
135-
136-
c.dataByKeyA = make(map[KeyA]V)
137-
c.keyAByKeyB = make(map[KeyB]KeyA)
138-
c.keyBByKeyA = make(map[KeyA]KeyB)
112+
func (m *BiKeyMap[KeyA, KeyB, V]) Clear() {
113+
m.dataByKeyA = make(map[KeyA]V)
114+
m.keyAByKeyB = make(map[KeyB]KeyA)
115+
m.keyBByKeyA = make(map[KeyA]KeyB)
139116
}
140117

141118
// String returns a string representation of the map.
142-
func (c *BiKeyMap[KeyA, KeyB, V]) String() string {
143-
c.mu.RLock()
144-
defer c.mu.RUnlock()
145-
146-
return fmt.Sprintf("BiKeyMap: %v", c.dataByKeyA)
119+
func (m *BiKeyMap[KeyA, KeyB, V]) String() string {
120+
return fmt.Sprintf("BiKeyMap: %v", m.dataByKeyA)
147121
}

0 commit comments

Comments
 (0)