types/views: optimize SliceEqualAnyOrderFunc for small slices

If the total number of differences is less than a small amount, just do
the dumb quadratic thing and compare every single object instead of
allocating a map.

Updates tailscale/corp#25479

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I8931b4355a2da4ec0f19739927311cf88711a840
This commit is contained in:
Andrew Dunham 2025-01-09 16:55:07 -05:00
parent 7fa07f3416
commit 6ddeae7556
2 changed files with 32 additions and 0 deletions

View File

@ -381,6 +381,29 @@ func SliceEqualAnyOrderFunc[T any, V comparable](a, b Slice[T], cmp func(T) V) b
return true return true
} }
// For a small number of items, avoid the allocation of a map and just
// do the quadratic thing. We can also only check the items between
// diffStart and the end.
nRemain := a.Len() - diffStart
if nRemain <= 5 {
maxLen := a.Len() // same as b.Len()
for i := diffStart; i < maxLen; i++ {
av := cmp(a.At(i))
found := false
for j := diffStart; j < maxLen; j++ {
bv := cmp(b.At(j))
if av == bv {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// count the occurrences of remaining values and compare // count the occurrences of remaining values and compare
valueCount := make(map[V]int) valueCount := make(map[V]int)
for i, n := diffStart, a.Len(); i < n; i++ { for i, n := diffStart, a.Len(); i < n; i++ {

View File

@ -188,6 +188,15 @@ func TestSliceEqualAnyOrderFunc(t *testing.T) {
// Nothing shared // Nothing shared
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("baz", "qux"), cmp), qt.Equals, false) c.Check(SliceEqualAnyOrderFunc(v, ncFrom("baz", "qux"), cmp), qt.Equals, false)
// Long slice that matches
longSlice := ncFrom("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")
longSame := ncFrom("b", "a", "c", "d", "e", "f", "g", "h", "i", "j") // first 2 elems swapped
c.Check(SliceEqualAnyOrderFunc(longSlice, longSame, cmp), qt.Equals, true)
// Long difference; past the quadratic limit
longDiff := ncFrom("b", "a", "c", "d", "e", "f", "g", "h", "i", "k") // differs at end
c.Check(SliceEqualAnyOrderFunc(longSlice, longDiff, cmp), qt.Equals, false)
} }
func TestSliceEqual(t *testing.T) { func TestSliceEqual(t *testing.T) {