Skip to content

Commit 23ad265

Browse files
committed
minor optimizations
1 parent 773162a commit 23ad265

File tree

3 files changed

+76
-57
lines changed

3 files changed

+76
-57
lines changed

cmap.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,58 +61,56 @@ func NewSize(shardCount int) *CMap {
6161
return cm
6262
}
6363

64-
func (cm *CMap) shard(key KT) *lmap {
64+
func (cm *CMap) shardForKey(key KT) *lmap {
6565
h := cm.HashFn(key)
6666
return &cm.shards[h&cm.mod]
6767
}
6868

6969
// Get is the equivalent of `val := map[key]`.
7070
func (cm *CMap) Get(key KT) (val VT) {
71-
return cm.shard(key).Get(key)
71+
return cm.shardForKey(key).Get(key)
7272
}
7373

7474
// GetOK is the equivalent of `val, ok := map[key]`.
7575
func (cm *CMap) GetOK(key KT) (val VT, ok bool) {
76-
return cm.shard(key).GetOK(key)
76+
return cm.shardForKey(key).GetOK(key)
7777
}
7878

7979
// Set is the equivalent of `map[key] = val`.
8080
func (cm *CMap) Set(key KT, val VT) {
81-
cm.shard(key).Set(key, val)
81+
cm.shardForKey(key).Set(key, val)
8282
}
8383

8484
// SetIfNotExists will only assign val to key if it wasn't already set.
8585
// Use `CMap.Update` if you need more logic.
8686
func (cm *CMap) SetIfNotExists(key KT, val VT) (set bool) {
8787
cm.Update(key, func(oldVal VT) (newVal VT) {
88-
switch oldVal.(type) {
89-
case nil:
88+
if set = oldVal == nil; set {
9089
return newVal
91-
default:
92-
return oldVal
9390
}
91+
return oldVal
9492
})
9593
return
9694
}
9795

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

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

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

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

113111
// Swap is the equivalent of `oldVal, map[key] = map[key], newVal`.
114112
func (cm *CMap) Swap(key KT, val VT) VT {
115-
return cm.shard(key).Swap(key, val)
113+
return cm.shardForKey(key).Swap(key, val)
116114
}
117115

118116
// Keys returns a slice of all the keys of the map.
@@ -192,12 +190,10 @@ func (cm *CMap) iterContext(ctx context.Context, ch chan<- *KV, locked bool) {
192190
return nil
193191
}
194192
}
195-
for i := range cm.shards {
196-
if locked {
197-
cm.shards[i].ForEachLocked(fn)
198-
} else {
199-
cm.shards[i].ForEach(fn)
200-
}
193+
if locked {
194+
cm.ForEachLocked(fn)
195+
} else {
196+
cm.ForEach(fn)
201197
}
202198
}
203199

stringcmap/cmap.go

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import (
66
"github.com/OneOfOne/cmap"
77
)
88

9+
type (
10+
KV struct {
11+
Key string
12+
Value interface{}
13+
}
14+
)
15+
916
// DefaultShardCount is the default number of shards to use when New() or NewFromJSON() are called.
1017
// The default is 256.
1118
const DefaultShardCount = cmap.DefaultShardCount
@@ -42,58 +49,56 @@ func NewSize(shardCount int) *CMap {
4249
return cm
4350
}
4451

45-
func (cm *CMap) shard(key string) *lmap {
52+
func (cm *CMap) shardForKey(key string) *lmap {
4653
h := cmap.Fnv32(key)
4754
return &cm.shards[h&cm.mod]
4855
}
4956

5057
// Get is the equivalent of `val := map[key]`.
5158
func (cm *CMap) Get(key string) (val interface{}) {
52-
return cm.shard(key).Get(key)
59+
return cm.shardForKey(key).Get(key)
5360
}
5461

5562
// GetOK is the equivalent of `val, ok := map[key]`.
5663
func (cm *CMap) GetOK(key string) (val interface{}, ok bool) {
57-
return cm.shard(key).GetOK(key)
64+
return cm.shardForKey(key).GetOK(key)
5865
}
5966

6067
// Set is the equivalent of `map[key] = val`.
6168
func (cm *CMap) Set(key string, val interface{}) {
62-
cm.shard(key).Set(key, val)
69+
cm.shardForKey(key).Set(key, val)
6370
}
6471

6572
// SetIfNotExists will only assign val to key if it wasn't already set.
6673
// Use `CMap.Update` if you need more logic.
6774
func (cm *CMap) SetIfNotExists(key string, val interface{}) (set bool) {
6875
cm.Update(key, func(oldVal interface{}) (newVal interface{}) {
69-
switch oldVal.(type) {
70-
case nil:
76+
if set = oldVal == nil; set {
7177
return newVal
72-
default:
73-
return oldVal
7478
}
79+
return oldVal
7580
})
7681
return
7782
}
7883

7984
// Has is the equivalent of `_, ok := map[key]`.
80-
func (cm *CMap) Has(key string) bool { return cm.shard(key).Has(key) }
85+
func (cm *CMap) Has(key string) bool { return cm.shardForKey(key).Has(key) }
8186

8287
// Delete is the equivalent of `delete(map, key)`.
83-
func (cm *CMap) Delete(key string) { cm.shard(key).Delete(key) }
88+
func (cm *CMap) Delete(key string) { cm.shardForKey(key).Delete(key) }
8489

8590
// DeleteAndGet is the equivalent of `oldVal := map[key]; delete(map, key)`.
86-
func (cm *CMap) DeleteAndGet(key string) interface{} { return cm.shard(key).DeleteAndGet(key) }
91+
func (cm *CMap) DeleteAndGet(key string) interface{} { return cm.shardForKey(key).DeleteAndGet(key) }
8792

8893
// Update calls `fn` with the key's old value (or nil if it didn't exist) and assign the returned value to the key.
8994
// The shard containing the key will be locked, it is NOT safe to call other cmap funcs inside `fn`.
9095
func (cm *CMap) Update(key string, fn func(oldval interface{}) (newval interface{})) {
91-
cm.shard(key).Update(key, fn)
96+
cm.shardForKey(key).Update(key, fn)
9297
}
9398

9499
// Swap is the equivalent of `oldVal, map[key] = map[key], newVal`.
95100
func (cm *CMap) Swap(key string, val interface{}) interface{} {
96-
return cm.shard(key).Swap(key, val)
101+
return cm.shardForKey(key).Swap(key, val)
97102
}
98103

99104
// Keys returns a slice of all the keys of the map.
@@ -112,12 +117,12 @@ func (cm *CMap) Keys() []string {
112117

113118
// ForEach loops over all the key/values in all the shards in order.
114119
// You can break early by returning an error.
115-
// it is safe to change the map inside fn.
120+
// It **is** safe to modify the map while using this iterator, however it uses more memory and is slightly slower.
116121
func (cm *CMap) ForEach(fn func(key string, val interface{}) error) error {
117122
for i := range cm.shards {
118123
if err := cm.shards[i].ForEach(fn); err != nil {
119124
if err == cmap.Break {
120-
err = nil
125+
return nil
121126
}
122127
return err
123128
}
@@ -127,10 +132,10 @@ func (cm *CMap) ForEach(fn func(key string, val interface{}) error) error {
127132

128133
// ForEachLocked loops over all the key/values in the map.
129134
// You can break early by returning an error.
130-
// It is **NOT** safe to change the map during this call.
135+
// It is **NOT* safe to modify the map while using this iterator.
131136
func (cm *CMap) ForEachLocked(fn func(key string, val interface{}) error) error {
132137
for i := range cm.shards {
133-
if err := cm.shards[i].ForEach(fn); err != nil {
138+
if err := cm.shards[i].ForEachLocked(fn); err != nil {
134139
if err == cmap.Break {
135140
return nil
136141
}
@@ -140,34 +145,43 @@ func (cm *CMap) ForEachLocked(fn func(key string, val interface{}) error) error
140145
return nil
141146
}
142147

143-
// KV is returned from the Iter channel.
144-
type KV struct {
145-
Key string
146-
Value interface{}
147-
}
148-
149148
// Iter returns a channel to be used in for range.
150-
// **Warning** breaking early will leak up to cm.NumShards() goroutines, use IterWithCancel if you intend to break early.
151-
// It is safe to modify the map while using this iterator.
149+
// Use `context.WithCancel` if you intend to break early or goroutines will leak.
150+
// It **is** safe to modify the map while using this iterator, however it uses more memory and is slightly slower.
152151
func (cm *CMap) Iter(ctx context.Context, buffer int) <-chan *KV {
153152
ch := make(chan *KV, buffer)
154153
go func() {
155-
cm.iterContext(ctx, ch)
154+
cm.iterContext(ctx, ch, false)
156155
close(ch)
157156
}()
158157
return ch
159158
}
160159

161-
func (cm *CMap) iterContext(ctx context.Context, ch chan<- *KV) {
162-
for i := range cm.shards {
163-
cm.shards[i].ForEach(func(k string, v interface{}) error {
164-
select {
165-
case <-ctx.Done():
166-
return cmap.Break
167-
case ch <- &KV{k, v}:
168-
return nil
169-
}
170-
})
160+
// IterLocked returns a channel to be used in for range.
161+
// Use `context.WithCancel` if you intend to break early or goroutines will leak and map access will deadlock.
162+
// It is **NOT* safe to modify the map while using this iterator.
163+
func (cm *CMap) IterLocked(ctx context.Context, buffer int) <-chan *KV {
164+
ch := make(chan *KV, buffer)
165+
go func() {
166+
cm.iterContext(ctx, ch, false)
167+
close(ch)
168+
}()
169+
return ch
170+
}
171+
172+
func (cm *CMap) iterContext(ctx context.Context, ch chan<- *KV, locked bool) {
173+
fn := func(k string, v interface{}) error {
174+
select {
175+
case <-ctx.Done():
176+
return cmap.Break
177+
case ch <- &KV{k, v}:
178+
return nil
179+
}
180+
}
181+
if locked {
182+
cm.ForEachLocked(fn)
183+
} else {
184+
cm.ForEach(fn)
171185
}
172186
}
173187

utils.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,22 @@ func DefaultKeyHasher(key KT) uint32 {
4949
}
5050
}
5151

52+
const prime32 = uint32(16777619)
53+
5254
// Fnv32 the default hash func we use for strings.
5355
func Fnv32(key string) uint32 {
54-
const prime32 = uint32(16777619)
5556
hash := uint32(2166136261)
56-
for i := 0; i < len(key); i++ {
57+
58+
// workaround not being able to inline for loops
59+
// watching https://github.com/golang/go/issues/21490
60+
if len(key) > 0 {
61+
i := 0
62+
L:
5763
hash *= prime32
5864
hash ^= uint32(key[i])
65+
if i++; i < len(key) {
66+
goto L
67+
}
5968
}
6069
return hash
6170
}

0 commit comments

Comments
 (0)