internal/persistent: no-op deletion from map does not allocate
We can use a property that split does a dfs search for the key before
doing an actual work. This allows us to do a low-cost early return if
there is no key to delete.
Change-Id: I6ed8068945f9f2dacc356d72b18afce04ec89a3c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/413659
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/internal/persistent/map.go b/internal/persistent/map.go
index 9c17ad0..19b5048 100644
--- a/internal/persistent/map.go
+++ b/internal/persistent/map.go
@@ -185,7 +185,7 @@
second, first, overwrite = first, second, !overwrite
}
- left, mid, right := split(second, first.key, less)
+ left, mid, right := split(second, first.key, less, false)
var result *mapNode
if overwrite && mid != nil {
result = mid.shallowCloneWithRef()
@@ -205,23 +205,31 @@
// Return three new trees: left with all nodes with smaller than key, mid with
// the node matching the key, right with all nodes larger than key.
// If there are no nodes in one of trees, return nil instead of it.
+// If requireMid is set (such as during deletion), then all return arguments
+// are nil if mid is not found.
//
// split(n:-0) (left:+1, mid:+1, right:+1)
// Split borrows n without affecting its refcount, and returns three
// new references that that caller is expected to call decref.
-func split(n *mapNode, key interface{}, less func(a, b interface{}) bool) (left, mid, right *mapNode) {
+func split(n *mapNode, key interface{}, less func(a, b interface{}) bool, requireMid bool) (left, mid, right *mapNode) {
if n == nil {
return nil, nil, nil
}
if less(n.key, key) {
- left, mid, right := split(n.right, key, less)
+ left, mid, right := split(n.right, key, less, requireMid)
+ if requireMid && mid == nil {
+ return nil, nil, nil
+ }
newN := n.shallowCloneWithRef()
newN.left = n.left.incref()
newN.right = left
return newN, mid, right
} else if less(key, n.key) {
- left, mid, right := split(n.left, key, less)
+ left, mid, right := split(n.left, key, less, requireMid)
+ if requireMid && mid == nil {
+ return nil, nil, nil
+ }
newN := n.shallowCloneWithRef()
newN.left = right
newN.right = n.right.incref()
@@ -234,7 +242,10 @@
// Delete deletes the value for a key.
func (pm *Map) Delete(key interface{}) {
root := pm.root
- left, mid, right := split(root, key, pm.less)
+ left, mid, right := split(root, key, pm.less, true)
+ if mid == nil {
+ return
+ }
pm.root = merge(left, right)
left.decref()
mid.decref()
diff --git a/internal/persistent/map_test.go b/internal/persistent/map_test.go
index 059f0da..bd2cbfa 100644
--- a/internal/persistent/map_test.go
+++ b/internal/persistent/map_test.go
@@ -71,6 +71,15 @@
m1.remove(t, 1)
validateRef(t, m1, m2)
+ gotAllocs := int(testing.AllocsPerRun(10, func() {
+ m1.impl.Delete(100)
+ m1.impl.Delete(1)
+ }))
+ wantAllocs := 0
+ if gotAllocs != wantAllocs {
+ t.Errorf("wanted %d allocs, got %d", wantAllocs, gotAllocs)
+ }
+
for i := 10; i < 14; i++ {
m1.set(t, i, i)
validateRef(t, m1, m2)
@@ -298,19 +307,3 @@
t.Fatalf("different maps:\n%v\nvs\n%v", map1, map2)
}
}
-
-func isSameMap(map1, map2 reflect.Value) bool {
- if map1.Len() != map2.Len() {
- return false
- }
- iter := map1.MapRange()
- for iter.Next() {
- key := iter.Key()
- value1 := iter.Value()
- value2 := map2.MapIndex(key)
- if value2.IsZero() || !reflect.DeepEqual(value1.Interface(), value2.Interface()) {
- return false
- }
- }
- return true
-}