Skip to content

Commit df2bb98

Browse files
committed
runtime: during map delete, update entries after new last element
When we delete an element, and it was the last element in the bucket, update the slots between the new last element and the old last element with the marker that says "no more elements beyond here". Change-Id: I8efeeddf4c9b9fc491c678f84220a5a5094c9c76 Reviewed-on: https://go-review.googlesource.com/c/142438 Reviewed-by: Matthew Dempsky <[email protected]>
1 parent 62b850f commit df2bb98

File tree

6 files changed

+187
-4
lines changed

6 files changed

+187
-4
lines changed

src/runtime/export_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,39 @@ func stackOverflow(x *byte) {
477477
var buf [256]byte
478478
stackOverflow(&buf[0])
479479
}
480+
481+
func MapTombstoneCheck(m map[int]int) {
482+
// Make sure emptyOne and emptyRest are distributed correctly.
483+
// We should have a series of filled and emptyOne cells, followed by
484+
// a series of emptyRest cells.
485+
h := *(**hmap)(unsafe.Pointer(&m))
486+
i := interface{}(m)
487+
t := *(**maptype)(unsafe.Pointer(&i))
488+
489+
for x := 0; x < 1<<h.B; x++ {
490+
b0 := (*bmap)(add(h.buckets, uintptr(x)*uintptr(t.bucketsize)))
491+
n := 0
492+
for b := b0; b != nil; b = b.overflow(t) {
493+
for i := 0; i < bucketCnt; i++ {
494+
if b.tophash[i] != emptyRest {
495+
n++
496+
}
497+
}
498+
}
499+
k := 0
500+
for b := b0; b != nil; b = b.overflow(t) {
501+
for i := 0; i < bucketCnt; i++ {
502+
if k < n && b.tophash[i] == emptyRest {
503+
panic("early emptyRest")
504+
}
505+
if k >= n && b.tophash[i] != emptyRest {
506+
panic("late non-emptyRest")
507+
}
508+
if k == n-1 && b.tophash[i] == emptyOne {
509+
panic("last non-emptyRest entry is emptyOne")
510+
}
511+
k++
512+
}
513+
}
514+
}
515+
}

src/runtime/map.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
711711
growWork(t, h, bucket)
712712
}
713713
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
714+
bOrig := b
714715
top := tophash(hash)
715716
search:
716717
for ; b != nil; b = b.overflow(t) {
@@ -744,7 +745,38 @@ search:
744745
memclrNoHeapPointers(v, t.elem.size)
745746
}
746747
b.tophash[i] = emptyOne
747-
// TODO: set up emptyRest here.
748+
// If the bucket now ends in a bunch of emptyOne states,
749+
// change those to emptyRest states.
750+
// It would be nice to make this a separate function, but
751+
// for loops are not currently inlineable.
752+
if i == bucketCnt-1 {
753+
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
754+
goto notLast
755+
}
756+
} else {
757+
if b.tophash[i+1] != emptyRest {
758+
goto notLast
759+
}
760+
}
761+
for {
762+
b.tophash[i] = emptyRest
763+
if i == 0 {
764+
if b == bOrig {
765+
break // beginning of initial bucket, we're done.
766+
}
767+
// Find previous bucket, continue at its last entry.
768+
c := b
769+
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
770+
}
771+
i = bucketCnt - 1
772+
} else {
773+
i--
774+
}
775+
if b.tophash[i] != emptyOne {
776+
break
777+
}
778+
}
779+
notLast:
748780
h.count--
749781
break search
750782
}

src/runtime/map_fast32.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ func mapdelete_fast32(t *maptype, h *hmap, key uint32) {
291291
growWork_fast32(t, h, bucket)
292292
}
293293
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
294+
bOrig := b
294295
search:
295296
for ; b != nil; b = b.overflow(t) {
296297
for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 4) {
@@ -308,7 +309,36 @@ search:
308309
memclrNoHeapPointers(v, t.elem.size)
309310
}
310311
b.tophash[i] = emptyOne
311-
// TODO: emptyRest?
312+
// If the bucket now ends in a bunch of emptyOne states,
313+
// change those to emptyRest states.
314+
if i == bucketCnt-1 {
315+
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
316+
goto notLast
317+
}
318+
} else {
319+
if b.tophash[i+1] != emptyRest {
320+
goto notLast
321+
}
322+
}
323+
for {
324+
b.tophash[i] = emptyRest
325+
if i == 0 {
326+
if b == bOrig {
327+
break // beginning of initial bucket, we're done.
328+
}
329+
// Find previous bucket, continue at its last entry.
330+
c := b
331+
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
332+
}
333+
i = bucketCnt - 1
334+
} else {
335+
i--
336+
}
337+
if b.tophash[i] != emptyOne {
338+
break
339+
}
340+
}
341+
notLast:
312342
h.count--
313343
break search
314344
}

src/runtime/map_fast64.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
291291
growWork_fast64(t, h, bucket)
292292
}
293293
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
294+
bOrig := b
294295
search:
295296
for ; b != nil; b = b.overflow(t) {
296297
for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
@@ -308,7 +309,36 @@ search:
308309
memclrNoHeapPointers(v, t.elem.size)
309310
}
310311
b.tophash[i] = emptyOne
311-
//TODO: emptyRest
312+
// If the bucket now ends in a bunch of emptyOne states,
313+
// change those to emptyRest states.
314+
if i == bucketCnt-1 {
315+
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
316+
goto notLast
317+
}
318+
} else {
319+
if b.tophash[i+1] != emptyRest {
320+
goto notLast
321+
}
322+
}
323+
for {
324+
b.tophash[i] = emptyRest
325+
if i == 0 {
326+
if b == bOrig {
327+
break // beginning of initial bucket, we're done.
328+
}
329+
// Find previous bucket, continue at its last entry.
330+
c := b
331+
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
332+
}
333+
i = bucketCnt - 1
334+
} else {
335+
i--
336+
}
337+
if b.tophash[i] != emptyOne {
338+
break
339+
}
340+
}
341+
notLast:
312342
h.count--
313343
break search
314344
}

src/runtime/map_faststr.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ func mapdelete_faststr(t *maptype, h *hmap, ky string) {
317317
growWork_faststr(t, h, bucket)
318318
}
319319
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
320+
bOrig := b
320321
top := tophash(hash)
321322
search:
322323
for ; b != nil; b = b.overflow(t) {
@@ -337,7 +338,36 @@ search:
337338
memclrNoHeapPointers(v, t.elem.size)
338339
}
339340
b.tophash[i] = emptyOne
340-
// TODO: emptyRest
341+
// If the bucket now ends in a bunch of emptyOne states,
342+
// change those to emptyRest states.
343+
if i == bucketCnt-1 {
344+
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
345+
goto notLast
346+
}
347+
} else {
348+
if b.tophash[i+1] != emptyRest {
349+
goto notLast
350+
}
351+
}
352+
for {
353+
b.tophash[i] = emptyRest
354+
if i == 0 {
355+
if b == bOrig {
356+
break // beginning of initial bucket, we're done.
357+
}
358+
// Find previous bucket, continue at its last entry.
359+
c := b
360+
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
361+
}
362+
i = bucketCnt - 1
363+
} else {
364+
i--
365+
}
366+
if b.tophash[i] != emptyOne {
367+
break
368+
}
369+
}
370+
notLast:
341371
h.count--
342372
break search
343373
}

src/runtime/map_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,3 +1131,28 @@ func TestIncrementAfterBulkClearKeyStringValueInt(t *testing.T) {
11311131
t.Errorf("incremented 0 to %d", n2)
11321132
}
11331133
}
1134+
1135+
func TestMapTombstones(t *testing.T) {
1136+
m := map[int]int{}
1137+
const N = 10000
1138+
// Fill a map.
1139+
for i := 0; i < N; i++ {
1140+
m[i] = i
1141+
}
1142+
runtime.MapTombstoneCheck(m)
1143+
// Delete half of the entries.
1144+
for i := 0; i < N; i += 2 {
1145+
delete(m, i)
1146+
}
1147+
runtime.MapTombstoneCheck(m)
1148+
// Add new entries to fill in holes.
1149+
for i := N; i < 3*N/2; i++ {
1150+
m[i] = i
1151+
}
1152+
runtime.MapTombstoneCheck(m)
1153+
// Delete everything.
1154+
for i := 0; i < 3*N/2; i++ {
1155+
delete(m, i)
1156+
}
1157+
runtime.MapTombstoneCheck(m)
1158+
}

0 commit comments

Comments
 (0)