Skip to content

Commit 5a0c862

Browse files
authored
Replace doLoadOrCompute with a read-only path in doCompute (#167)
Replace doLoadOrCompute with doCompute to enhance performance. Also remove loadOr-related functions. The store function remains efficient and performs well.
1 parent 5cf46af commit 5a0c862

File tree

1 file changed

+47
-119
lines changed

1 file changed

+47
-119
lines changed

map.go

Lines changed: 47 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -244,18 +244,24 @@ func (m *Map[K, V]) Store(key K, value V) {
244244
return value, UpdateOp
245245
},
246246
false,
247+
0,
247248
)
248249
}
249250

250251
// LoadOrStore returns the existing value for the key if present.
251252
// Otherwise, it stores and returns the given value.
252253
// The loaded result is true if the value was loaded, false if stored.
253254
func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
254-
return m.doLoadOrCompute(
255+
return m.doCompute(
255256
key,
256-
func(V, bool) (V, ComputeOp) {
257+
func(oldValue V, loaded bool) (V, ComputeOp) {
258+
if loaded {
259+
return oldValue, CancelOp
260+
}
257261
return value, UpdateOp
258262
},
263+
false,
264+
1,
259265
)
260266
}
261267

@@ -271,6 +277,7 @@ func (m *Map[K, V]) LoadAndStore(key K, value V) (actual V, loaded bool) {
271277
return value, UpdateOp
272278
},
273279
false,
280+
0,
274281
)
275282
}
276283

@@ -290,7 +297,7 @@ func (m *Map[K, V]) LoadOrCompute(
290297
key K,
291298
valueFn func() (newValue V, cancel bool),
292299
) (value V, loaded bool) {
293-
return m.doLoadOrCompute(
300+
return m.doCompute(
294301
key,
295302
func(oldValue V, loaded bool) (V, ComputeOp) {
296303
if loaded {
@@ -302,6 +309,8 @@ func (m *Map[K, V]) LoadOrCompute(
302309
}
303310
return oldValue, CancelOp
304311
},
312+
false,
313+
1,
305314
)
306315
}
307316

@@ -345,7 +354,7 @@ func (m *Map[K, V]) Compute(
345354
key K,
346355
valueFn func(oldValue V, loaded bool) (newValue V, op ComputeOp),
347356
) (actual V, ok bool) {
348-
return m.doCompute(key, valueFn, true)
357+
return m.doCompute(key, valueFn, true, 0)
349358
}
350359

351360
// LoadAndDelete deletes the value for a key, returning the previous
@@ -358,6 +367,7 @@ func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
358367
return value, DeleteOp
359368
},
360369
false,
370+
-1,
361371
)
362372
}
363373

@@ -369,14 +379,17 @@ func (m *Map[K, V]) Delete(key K) {
369379
return value, DeleteOp
370380
},
371381
false,
382+
-1,
372383
)
373384
}
374385

375386
func (m *Map[K, V]) doCompute(
376387
key K,
377388
valueFn func(oldValue V, loaded bool) (V, ComputeOp),
378389
computeOnly bool,
390+
loadExitFlag int8,
379391
) (V, bool) {
392+
380393
for {
381394
compute_attempt:
382395
var (
@@ -391,6 +404,36 @@ func (m *Map[K, V]) doCompute(
391404
h2w := broadcast(h2)
392405
bidx := uint64(len(table.buckets)-1) & h1
393406
rootb := &table.buckets[bidx]
407+
408+
if loadExitFlag != 0 {
409+
b := rootb
410+
load:
411+
for {
412+
metaw := b.meta.Load()
413+
markedw := markZeroBytes(metaw^h2w) & metaMask
414+
for markedw != 0 {
415+
idx := firstMarkedByteIndex(markedw)
416+
e := b.entries[idx].Load()
417+
if e != nil {
418+
if e.key == key {
419+
if loadExitFlag == 1 {
420+
return e.value, true
421+
}
422+
break load
423+
}
424+
}
425+
markedw &= markedw - 1
426+
}
427+
b = b.next.Load()
428+
if b == nil {
429+
if loadExitFlag == -1 {
430+
return *new(V), false
431+
}
432+
break load
433+
}
434+
}
435+
}
436+
394437
rootb.mu.Lock()
395438
// The following two checks must go in reverse to what's
396439
// in the resize method.
@@ -517,121 +560,6 @@ func (m *Map[K, V]) doCompute(
517560
}
518561
}
519562

520-
func (m *Map[K, V]) doLoadOrCompute(
521-
key K,
522-
valueFn func(oldValue V, loaded bool) (V, ComputeOp),
523-
) (V, bool) {
524-
// Read-only path.
525-
if v, ok := m.Load(key); ok {
526-
return v, true
527-
}
528-
// Write path.
529-
for {
530-
compute_attempt:
531-
var (
532-
emptyb *bucketPadded[K, V]
533-
emptyidx int
534-
)
535-
table := m.table.Load()
536-
tableLen := len(table.buckets)
537-
hash := maphash.Comparable(table.seed, key)
538-
h1 := h1(hash)
539-
h2 := h2(hash)
540-
h2w := broadcast(h2)
541-
bidx := uint64(len(table.buckets)-1) & h1
542-
rootb := &table.buckets[bidx]
543-
rootb.mu.Lock()
544-
// The following two checks must go in reverse to what's
545-
// in the resize method.
546-
if m.resizeInProgress() {
547-
// Resize is in progress. Wait, then go for another attempt.
548-
rootb.mu.Unlock()
549-
m.waitForResize()
550-
goto compute_attempt
551-
}
552-
if m.newerTableExists(table) {
553-
// Someone resized the table. Go for another attempt.
554-
rootb.mu.Unlock()
555-
goto compute_attempt
556-
}
557-
b := rootb
558-
for {
559-
metaw := b.meta.Load()
560-
markedw := markZeroBytes(metaw^h2w) & metaMask
561-
for markedw != 0 {
562-
idx := firstMarkedByteIndex(markedw)
563-
e := b.entries[idx].Load()
564-
if e != nil {
565-
if e.key == key {
566-
rootb.mu.Unlock()
567-
return e.value, true
568-
}
569-
}
570-
markedw &= markedw - 1
571-
}
572-
if emptyb == nil {
573-
// Search for empty entries (up to 5 per bucket).
574-
emptyw := metaw & defaultMetaMasked
575-
if emptyw != 0 {
576-
idx := firstMarkedByteIndex(emptyw)
577-
emptyb = b
578-
emptyidx = idx
579-
}
580-
}
581-
if b.next.Load() == nil {
582-
if emptyb != nil {
583-
// Insertion into an existing bucket.
584-
var zeroV V
585-
newValue, op := valueFn(zeroV, false)
586-
switch op {
587-
case DeleteOp, CancelOp:
588-
rootb.mu.Unlock()
589-
return zeroV, false
590-
default:
591-
newe := new(entry[K, V])
592-
newe.key = key
593-
newe.value = newValue
594-
// First we update meta, then the entry.
595-
emptyb.meta.Store(setByte(emptyb.meta.Load(), h2, emptyidx))
596-
emptyb.entries[emptyidx].Store(newe)
597-
rootb.mu.Unlock()
598-
table.addSize(bidx, 1)
599-
return newValue, false
600-
}
601-
}
602-
growThreshold := float64(tableLen) * entriesPerMapBucket * mapLoadFactor
603-
if table.sumSize() > int64(growThreshold) {
604-
// Need to grow the table. Then go for another attempt.
605-
rootb.mu.Unlock()
606-
m.resize(table, mapGrowHint)
607-
goto compute_attempt
608-
}
609-
// Insertion into a new bucket.
610-
var zeroV V
611-
newValue, op := valueFn(zeroV, false)
612-
switch op {
613-
case DeleteOp, CancelOp:
614-
rootb.mu.Unlock()
615-
return newValue, false
616-
default:
617-
// Create and append a bucket.
618-
newb := new(bucketPadded[K, V])
619-
newb.meta.Store(setByte(defaultMeta, h2, 0))
620-
newe := new(entry[K, V])
621-
newe.key = key
622-
newe.value = newValue
623-
newb.entries[0].Store(newe)
624-
b.next.Store(newb)
625-
rootb.mu.Unlock()
626-
table.addSize(bidx, 1)
627-
return newValue, false
628-
}
629-
}
630-
b = b.next.Load()
631-
}
632-
}
633-
}
634-
635563
func (m *Map[K, V]) newerTableExists(table *mapTable[K, V]) bool {
636564
return table != m.table.Load()
637565
}

0 commit comments

Comments
 (0)