Skip to content

Commit b080abf

Browse files
vkuzmin-uberrandall77
authored andcommitted
cmd/compile: map delete should clear value always
Map delete must clear value every time because newly added map optimizations of compound-assignment operators (CL #91557) rely on this behavior of map delete. It slows down map delete operation for non-reference types: name old time/op new time/op delta MapDelete/Int32/100 23.9ns ± 2% 27.8ns ± 4% +16.04% (p=0.000 n=20+20) MapDelete/Int32/1000 21.5ns ± 2% 25.2ns ± 2% +17.06% (p=0.000 n=20+19) MapDelete/Int32/10000 24.2ns ± 6% 27.2ns ± 5% +12.39% (p=0.000 n=19+19) MapDelete/Int64/100 24.2ns ± 4% 27.7ns ± 2% +14.55% (p=0.000 n=20+20) MapDelete/Int64/1000 22.1ns ± 2% 24.8ns ± 2% +12.36% (p=0.000 n=10+20) Fixes #25936 Change-Id: I8499b790cb5bb019938161b3e50f3243d9bbb79c Reviewed-on: https://go-review.googlesource.com/120255 Run-TryBot: Emmanuel Odeke <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 3c586d4 commit b080abf

File tree

5 files changed

+107
-19
lines changed

5 files changed

+107
-19
lines changed

src/runtime/map.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -707,14 +707,13 @@ search:
707707
} else if t.key.kind&kindNoPointers == 0 {
708708
memclrHasPointers(k, t.key.size)
709709
}
710-
// Only clear value if there are pointers in it.
711-
if t.indirectvalue || t.elem.kind&kindNoPointers == 0 {
712-
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
713-
if t.indirectvalue {
714-
*(*unsafe.Pointer)(v) = nil
715-
} else {
716-
memclrHasPointers(v, t.elem.size)
717-
}
710+
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
711+
if t.indirectvalue {
712+
*(*unsafe.Pointer)(v) = nil
713+
} else if t.elem.kind&kindNoPointers == 0 {
714+
memclrHasPointers(v, t.elem.size)
715+
} else {
716+
memclrNoHeapPointers(v, t.elem.size)
718717
}
719718
b.tophash[i] = empty
720719
h.count--

src/runtime/map_fast32.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,11 @@ search:
293293
if t.key.kind&kindNoPointers == 0 {
294294
memclrHasPointers(k, t.key.size)
295295
}
296-
// Only clear value if there are pointers in it.
296+
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*4+i*uintptr(t.valuesize))
297297
if t.elem.kind&kindNoPointers == 0 {
298-
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*4+i*uintptr(t.valuesize))
299298
memclrHasPointers(v, t.elem.size)
299+
} else {
300+
memclrNoHeapPointers(v, t.elem.size)
300301
}
301302
b.tophash[i] = empty
302303
h.count--

src/runtime/map_fast64.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,11 @@ search:
293293
if t.key.kind&kindNoPointers == 0 {
294294
memclrHasPointers(k, t.key.size)
295295
}
296-
// Only clear value if there are pointers in it.
296+
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.valuesize))
297297
if t.elem.kind&kindNoPointers == 0 {
298-
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.valuesize))
299298
memclrHasPointers(v, t.elem.size)
299+
} else {
300+
memclrNoHeapPointers(v, t.elem.size)
300301
}
301302
b.tophash[i] = empty
302303
h.count--

src/runtime/map_faststr.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,11 @@ search:
314314
}
315315
// Clear key's pointer.
316316
k.str = nil
317-
// Only clear value if there are pointers in it.
317+
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.valuesize))
318318
if t.elem.kind&kindNoPointers == 0 {
319-
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.valuesize))
320319
memclrHasPointers(v, t.elem.size)
320+
} else {
321+
memclrNoHeapPointers(v, t.elem.size)
321322
}
322323
b.tophash[i] = empty
323324
h.count--

src/runtime/map_test.go

+91-5
Original file line numberDiff line numberDiff line change
@@ -435,11 +435,11 @@ func TestEmptyKeyAndValue(t *testing.T) {
435435
// ("quick keys") as well as long keys.
436436
func TestSingleBucketMapStringKeys_DupLen(t *testing.T) {
437437
testMapLookups(t, map[string]string{
438-
"x": "x1val",
439-
"xx": "x2val",
440-
"foo": "fooval",
441-
"bar": "barval", // same key length as "foo"
442-
"xxxx": "x4val",
438+
"x": "x1val",
439+
"xx": "x2val",
440+
"foo": "fooval",
441+
"bar": "barval", // same key length as "foo"
442+
"xxxx": "x4val",
443443
strings.Repeat("x", 128): "longval1",
444444
strings.Repeat("y", 128): "longval2",
445445
})
@@ -1045,3 +1045,89 @@ func TestDeferDeleteSlow(t *testing.T) {
10451045
t.Errorf("want 0 elements, got %d", len(m))
10461046
}
10471047
}
1048+
1049+
// TestIncrementAfterDeleteValueInt and other test Issue 25936.
1050+
// Value types int, int32, int64 are affected. Value type string
1051+
// works as expected.
1052+
func TestIncrementAfterDeleteValueInt(t *testing.T) {
1053+
const key1 = 12
1054+
const key2 = 13
1055+
1056+
m := make(map[int]int)
1057+
m[key1] = 99
1058+
delete(m, key1)
1059+
m[key2]++
1060+
if n2 := m[key2]; n2 != 1 {
1061+
t.Errorf("incremented 0 to %d", n2)
1062+
}
1063+
}
1064+
1065+
func TestIncrementAfterDeleteValueInt32(t *testing.T) {
1066+
const key1 = 12
1067+
const key2 = 13
1068+
1069+
m := make(map[int]int32)
1070+
m[key1] = 99
1071+
delete(m, key1)
1072+
m[key2]++
1073+
if n2 := m[key2]; n2 != 1 {
1074+
t.Errorf("incremented 0 to %d", n2)
1075+
}
1076+
}
1077+
1078+
func TestIncrementAfterDeleteValueInt64(t *testing.T) {
1079+
const key1 = 12
1080+
const key2 = 13
1081+
1082+
m := make(map[int]int64)
1083+
m[key1] = 99
1084+
delete(m, key1)
1085+
m[key2]++
1086+
if n2 := m[key2]; n2 != 1 {
1087+
t.Errorf("incremented 0 to %d", n2)
1088+
}
1089+
}
1090+
1091+
func TestIncrementAfterDeleteKeyStringValueInt(t *testing.T) {
1092+
const key1 = ""
1093+
const key2 = "x"
1094+
1095+
m := make(map[string]int)
1096+
m[key1] = 99
1097+
delete(m, key1)
1098+
m[key2] += 1
1099+
if n2 := m[key2]; n2 != 1 {
1100+
t.Errorf("incremented 0 to %d", n2)
1101+
}
1102+
}
1103+
1104+
func TestIncrementAfterDeleteKeyValueString(t *testing.T) {
1105+
const key1 = ""
1106+
const key2 = "x"
1107+
1108+
m := make(map[string]string)
1109+
m[key1] = "99"
1110+
delete(m, key1)
1111+
m[key2] += "1"
1112+
if n2 := m[key2]; n2 != "1" {
1113+
t.Errorf("appended '1' to empty (nil) string, got %s", n2)
1114+
}
1115+
}
1116+
1117+
// TestIncrementAfterBulkClearKeyStringValueInt tests that map bulk
1118+
// deletion (mapclear) still works as expected. Note that it was not
1119+
// affected by Issue 25936.
1120+
func TestIncrementAfterBulkClearKeyStringValueInt(t *testing.T) {
1121+
const key1 = ""
1122+
const key2 = "x"
1123+
1124+
m := make(map[string]int)
1125+
m[key1] = 99
1126+
for k := range m {
1127+
delete(m, k)
1128+
}
1129+
m[key2]++
1130+
if n2 := m[key2]; n2 != 1 {
1131+
t.Errorf("incremented 0 to %d", n2)
1132+
}
1133+
}

0 commit comments

Comments
 (0)