Skip to content

Commit 2a6675a

Browse files
committed
Decent optimization, prevent most of heap leaks.
Before: ``` BenchmarkCMap/2048-8 50000000 105 ns/op 48 B/op 3 allocs/op BenchmarkCMap/4096-8 50000000 109 ns/op 48 B/op 3 allocs/op BenchmarkCMap/8192-8 50000000 107 ns/op 48 B/op 3 allocs/op ``` After: ``` BenchmarkCMap/2048-8 100000000 84.1 ns/op 48 B/op 3 allocs/op BenchmarkCMap/4096-8 100000000 86.3 ns/op 48 B/op 3 allocs/op BenchmarkCMap/8192-8 100000000 92.1 ns/op 48 B/op 3 allocs/op ```
1 parent 23ad265 commit 2a6675a

File tree

6 files changed

+163
-149
lines changed

6 files changed

+163
-149
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,21 @@ func main() {
4848

4949
## Benchmark
5050
```bash
51-
➤ go1.9rc2 test -v -bench=. -benchtime=5s -tags streamrail -benchmem -cpu 8 -short ./ ./stringcmap
51+
➤ go1.9rc2 test -v -bench=. -benchtime=5s -tags streamrail -benchmem -cpu 8 -short ./...
5252

5353
goos: linux
5454
goarch: amd64
5555
pkg: github.com/OneOfOne/cmap
5656

57-
BenchmarkCMap/2048-8 50000000 147 ns/op 48 B/op 3 allocs/op
58-
BenchmarkCMap/4096-8 50000000 134 ns/op 48 B/op 3 allocs/op
59-
BenchmarkCMap/8192-8 50000000 128 ns/op 48 B/op 3 allocs/op
57+
BenchmarkCMap/2048-8 100000000 85.3 ns/op 48 B/op 3 allocs/op
58+
BenchmarkCMap/4096-8 100000000 88.2 ns/op 48 B/op 3 allocs/op
59+
BenchmarkCMap/8192-8 100000000 92.3 ns/op 48 B/op 3 allocs/op
6060

6161
# simple map[interface{}]interface{} wrapped with a sync.RWMutex
62-
BenchmarkMutexMap-8 20000000 404 ns/op 32 B/op 2 allocs/op
62+
BenchmarkMutexMap-8 20000000 491 ns/op 32 B/op 2 allocs/op
6363

6464
# sync.Map
65-
BenchmarkSyncMap-8 50000000 141 ns/op 48 B/op 3 allocs/op
65+
BenchmarkSyncMap-8 50000000 124 ns/op 48 B/op 3 allocs/op
6666

6767
PASS
6868
ok github.com/OneOfOne/cmap 40.197s
@@ -72,14 +72,14 @@ goarch: amd64
7272
pkg: github.com/OneOfOne/cmap/stringcmap
7373

7474
# specialized version of CMap, using map[string]interface{} internally
75-
BenchmarkStringCMap/2048-8 100000000 61.5 ns/op 16 B/op 1 allocs/op
76-
BenchmarkStringCMap/4096-8 100000000 58.0 ns/op 16 B/op 1 allocs/op
77-
BenchmarkStringCMap/8192-8 100000000 51.1 ns/op 16 B/op 1 allocs/op
75+
BenchmarkStringCMap/2048-8 200000000 38.3 ns/op 16 B/op 1 allocs/op
76+
BenchmarkStringCMap/4096-8 200000000 39.8 ns/op 16 B/op 1 allocs/op
77+
BenchmarkStringCMap/8192-8 200000000 41.3 ns/op 16 B/op 1 allocs/op
7878

7979
# github.com/streamrail/concurrent-map
80-
BenchmarkStreamrail/2048-8 100000000 64.7 ns/op 16 B/op 1 allocs/op
81-
BenchmarkStreamrail/4096-8 100000000 62.1 ns/op 16 B/op 1 allocs/op
82-
BenchmarkStreamrail/8192-8 100000000 61.5 ns/op 16 B/op 1 allocs/op
80+
BenchmarkStreamrail/2048-8 100000000 51.6 ns/op 16 B/op 1 allocs/op
81+
BenchmarkStreamrail/4096-8 100000000 51.2 ns/op 16 B/op 1 allocs/op
82+
BenchmarkStreamrail/8192-8 100000000 50.6 ns/op 16 B/op 1 allocs/op
8383

8484
PASS
8585
ok github.com/OneOfOne/cmap/stringcmap 36.413s

cmap.go

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
package cmap
22

3-
import "context"
4-
5-
type (
6-
7-
// KT is the KeyType of the map, used for generating specialized versions.
8-
KT interface{}
9-
// VT is the ValueType of the map.
10-
VT interface{}
11-
12-
// KV is returned from the Iter channel.
13-
KV struct {
14-
Key KT
15-
Value VT
16-
}
3+
import (
4+
"context"
175
)
186

7+
type KV struct {
8+
Key KT
9+
Value VT
10+
}
11+
1912
// DefaultShardCount is the default number of shards to use when New() or NewFromJSON() are called.
2013
// The default is 256.
2114
const DefaultShardCount = 1 << 8
@@ -27,7 +20,7 @@ type KeyHasher interface {
2720

2821
// CMap is a concurrent safe sharded map to scale on multiple cores.
2922
type CMap struct {
30-
shards []lmap
23+
shards []*lmap
3124
// HashFn allows using a custom hash function that's used to determain the key's shard.
3225
// Defaults to DefaultKeyHasher
3326
HashFn func(KT) uint32
@@ -49,36 +42,36 @@ func NewSize(shardCount int) *CMap {
4942
}
5043

5144
cm := &CMap{
52-
shards: make([]lmap, shardCount),
45+
shards: make([]*lmap, shardCount),
5346
mod: uint32(shardCount) - 1,
5447
HashFn: DefaultKeyHasher,
5548
}
5649

5750
for i := range cm.shards {
58-
cm.shards[i].m = make(map[KT]VT)
51+
cm.shards[i] = newLmap(shardCount)
5952
}
6053

6154
return cm
6255
}
6356

64-
func (cm *CMap) shardForKey(key KT) *lmap {
57+
func (cm *CMap) shard(key KT) *lmap {
6558
h := cm.HashFn(key)
66-
return &cm.shards[h&cm.mod]
59+
return cm.shards[h&cm.mod]
6760
}
6861

6962
// Get is the equivalent of `val := map[key]`.
7063
func (cm *CMap) Get(key KT) (val VT) {
71-
return cm.shardForKey(key).Get(key)
64+
return cm.shard(key).Get(key)
7265
}
7366

7467
// GetOK is the equivalent of `val, ok := map[key]`.
7568
func (cm *CMap) GetOK(key KT) (val VT, ok bool) {
76-
return cm.shardForKey(key).GetOK(key)
69+
return cm.shard(key).GetOK(key)
7770
}
7871

7972
// Set is the equivalent of `map[key] = val`.
8073
func (cm *CMap) Set(key KT, val VT) {
81-
cm.shardForKey(key).Set(key, val)
74+
cm.shard(key).Set(key, val)
8275
}
8376

8477
// SetIfNotExists will only assign val to key if it wasn't already set.
@@ -94,30 +87,30 @@ func (cm *CMap) SetIfNotExists(key KT, val VT) (set bool) {
9487
}
9588

9689
// Has is the equivalent of `_, ok := map[key]`.
97-
func (cm *CMap) Has(key KT) bool { return cm.shardForKey(key).Has(key) }
90+
func (cm *CMap) Has(key KT) bool { return cm.shard(key).Has(key) }
9891

9992
// Delete is the equivalent of `delete(map, key)`.
100-
func (cm *CMap) Delete(key KT) { cm.shardForKey(key).Delete(key) }
93+
func (cm *CMap) Delete(key KT) { cm.shard(key).Delete(key) }
10194

10295
// DeleteAndGet is the equivalent of `oldVal := map[key]; delete(map, key)`.
103-
func (cm *CMap) DeleteAndGet(key KT) VT { return cm.shardForKey(key).DeleteAndGet(key) }
96+
func (cm *CMap) DeleteAndGet(key KT) VT { return cm.shard(key).DeleteAndGet(key) }
10497

10598
// Update calls `fn` with the key's old value (or nil if it didn't exist) and assign the returned value to the key.
10699
// The shard containing the key will be locked, it is NOT safe to call other cmap funcs inside `fn`.
107100
func (cm *CMap) Update(key KT, fn func(oldval VT) (newval VT)) {
108-
cm.shardForKey(key).Update(key, fn)
101+
cm.shard(key).Update(key, fn)
109102
}
110103

111104
// Swap is the equivalent of `oldVal, map[key] = map[key], newVal`.
112105
func (cm *CMap) Swap(key KT, val VT) VT {
113-
return cm.shardForKey(key).Swap(key, val)
106+
return cm.shard(key).Swap(key, val)
114107
}
115108

116109
// Keys returns a slice of all the keys of the map.
117110
func (cm *CMap) Keys() []KT {
118111
out := make([]KT, 0, cm.Len())
119112
for i := range cm.shards {
120-
sh := &cm.shards[i]
113+
sh := cm.shards[i]
121114
sh.l.RLock()
122115
for k := range sh.m {
123116
out = append(out, k)

lmap.go

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,82 +4,89 @@ import "sync"
44

55
type lmap struct {
66
m map[KT]VT
7-
l sync.RWMutex
7+
l *sync.RWMutex
88
}
99

10-
func (ms *lmap) Set(key KT, v VT) {
11-
ms.l.Lock()
12-
ms.m[key] = v
13-
ms.l.Unlock()
10+
func newLmap(cap int) *lmap {
11+
return &lmap{
12+
m: make(map[KT]VT, cap),
13+
l: new(sync.RWMutex),
14+
}
15+
}
16+
17+
func (lm lmap) Set(key KT, v VT) {
18+
lm.l.Lock()
19+
lm.m[key] = v
20+
lm.l.Unlock()
1421
}
1522

16-
func (ms *lmap) Update(key KT, fn func(oldVal VT) (newVal VT)) {
17-
ms.l.Lock()
18-
ms.m[key] = fn(ms.m[key])
19-
ms.l.Unlock()
23+
func (lm lmap) Update(key KT, fn func(oldVal VT) (newVal VT)) {
24+
lm.l.Lock()
25+
lm.m[key] = fn(lm.m[key])
26+
lm.l.Unlock()
2027
}
2128

22-
func (ms *lmap) Swap(key KT, newV VT) (oldV VT) {
23-
ms.l.Lock()
24-
oldV = ms.m[key]
25-
ms.m[key] = newV
26-
ms.l.Unlock()
29+
func (lm lmap) Swap(key KT, newV VT) (oldV VT) {
30+
lm.l.Lock()
31+
oldV = lm.m[key]
32+
lm.m[key] = newV
33+
lm.l.Unlock()
2734
return
2835
}
2936

30-
func (ms *lmap) Get(key KT) (v VT) {
31-
ms.l.RLock()
32-
v = ms.m[key]
33-
ms.l.RUnlock()
37+
func (lm lmap) Get(key KT) (v VT) {
38+
lm.l.RLock()
39+
v = lm.m[key]
40+
lm.l.RUnlock()
3441
return
3542
}
36-
func (ms *lmap) GetOK(key KT) (v VT, ok bool) {
37-
ms.l.RLock()
38-
v, ok = ms.m[key]
39-
ms.l.RUnlock()
43+
func (lm lmap) GetOK(key KT) (v VT, ok bool) {
44+
lm.l.RLock()
45+
v, ok = lm.m[key]
46+
lm.l.RUnlock()
4047
return
4148
}
4249

43-
func (ms *lmap) Has(key KT) (ok bool) {
44-
ms.l.RLock()
45-
_, ok = ms.m[key]
46-
ms.l.RUnlock()
50+
func (lm lmap) Has(key KT) (ok bool) {
51+
lm.l.RLock()
52+
_, ok = lm.m[key]
53+
lm.l.RUnlock()
4754
return
4855
}
4956

50-
func (ms *lmap) Delete(key KT) {
51-
ms.l.Lock()
52-
delete(ms.m, key)
53-
ms.l.Unlock()
57+
func (lm lmap) Delete(key KT) {
58+
lm.l.Lock()
59+
delete(lm.m, key)
60+
lm.l.Unlock()
5461
}
5562

56-
func (ms *lmap) DeleteAndGet(key KT) (v VT) {
57-
ms.l.Lock()
58-
v = ms.m[key]
59-
delete(ms.m, key)
60-
ms.l.Unlock()
63+
func (lm lmap) DeleteAndGet(key KT) (v VT) {
64+
lm.l.Lock()
65+
v = lm.m[key]
66+
delete(lm.m, key)
67+
lm.l.Unlock()
6168
return v
6269
}
6370

64-
func (ms *lmap) Len() (ln int) {
65-
ms.l.RLock()
66-
ln = len(ms.m)
67-
ms.l.RUnlock()
71+
func (lm lmap) Len() (ln int) {
72+
lm.l.RLock()
73+
ln = len(lm.m)
74+
lm.l.RUnlock()
6875
return
6976
}
7077

71-
func (ms *lmap) ForEach(fn func(key KT, val VT) error) (err error) {
72-
ms.l.RLock()
73-
keys := make([]KT, 0, len(ms.m))
74-
for key := range ms.m {
78+
func (lm lmap) ForEach(fn func(key KT, val VT) error) (err error) {
79+
lm.l.RLock()
80+
keys := make([]KT, 0, len(lm.m))
81+
for key := range lm.m {
7582
keys = append(keys, key)
7683
}
77-
ms.l.RUnlock()
84+
lm.l.RUnlock()
7885

7986
for _, key := range keys {
80-
ms.l.RLock()
81-
val, ok := ms.m[key]
82-
ms.l.RUnlock()
87+
lm.l.RLock()
88+
val, ok := lm.m[key]
89+
lm.l.RUnlock()
8390
if !ok {
8491
continue
8592
}
@@ -91,11 +98,11 @@ func (ms *lmap) ForEach(fn func(key KT, val VT) error) (err error) {
9198
return
9299
}
93100

94-
func (ms *lmap) ForEachLocked(fn func(key KT, val VT) error) (err error) {
95-
ms.l.RLock()
96-
defer ms.l.RUnlock()
101+
func (lm lmap) ForEachLocked(fn func(key KT, val VT) error) (err error) {
102+
lm.l.RLock()
103+
defer lm.l.RUnlock()
97104

98-
for key, val := range ms.m {
105+
for key, val := range lm.m {
99106
if err = fn(key, val); err != nil {
100107
return
101108
}

stringcmap/cmap.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DefaultShardCount = cmap.DefaultShardCount
1919

2020
// CMap is a concurrent safe sharded map to scale on multiple cores.
2121
type CMap struct {
22-
shards []lmap
22+
shards []*lmap
2323
mod uint32
2424
}
2525

@@ -38,20 +38,20 @@ func NewSize(shardCount int) *CMap {
3838
}
3939

4040
cm := &CMap{
41-
shards: make([]lmap, shardCount),
41+
shards: make([]*lmap, shardCount),
4242
mod: uint32(shardCount) - 1,
4343
}
4444

4545
for i := range cm.shards {
46-
cm.shards[i].m = make(map[string]interface{})
46+
cm.shards[i] = newLmap(shardCount)
4747
}
4848

4949
return cm
5050
}
5151

5252
func (cm *CMap) shardForKey(key string) *lmap {
5353
h := cmap.Fnv32(key)
54-
return &cm.shards[h&cm.mod]
54+
return cm.shards[h&cm.mod]
5555
}
5656

5757
// Get is the equivalent of `val := map[key]`.
@@ -105,7 +105,7 @@ func (cm *CMap) Swap(key string, val interface{}) interface{} {
105105
func (cm *CMap) Keys() []string {
106106
out := make([]string, 0, cm.Len())
107107
for i := range cm.shards {
108-
sh := &cm.shards[i]
108+
sh := cm.shards[i]
109109
sh.l.RLock()
110110
for k := range sh.m {
111111
out = append(out, k)

0 commit comments

Comments
 (0)