Skip to content

Commit e945d87

Browse files
authored
util/uniq: use generics instead of reflect (tailscale#5491)
This takes 75% less time per operation per some benchmarks on my mac. Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
1 parent 1ac4a26 commit e945d87

File tree

3 files changed

+26
-50
lines changed

3 files changed

+26
-50
lines changed

util/uniq/slice.go

+16-46
Original file line numberDiff line numberDiff line change
@@ -6,60 +6,30 @@
66
// It is similar to the unix command uniq.
77
package uniq
88

9-
import (
10-
"fmt"
11-
"reflect"
12-
)
13-
14-
type badTypeError struct {
15-
typ reflect.Type
16-
}
17-
18-
func (e badTypeError) Error() string {
19-
return fmt.Sprintf("uniq.ModifySlice's first argument must have type *[]T, got %v", e.typ)
20-
}
21-
22-
// ModifySlice removes adjacent duplicate elements from the slice pointed to by sliceptr.
23-
// It adjusts the length of the slice appropriately and zeros the tail.
24-
// eq reports whether (*sliceptr)[i] and (*sliceptr)[j] are equal.
25-
// ModifySlice does O(len(*sliceptr)) operations.
26-
func ModifySlice(sliceptr any, eq func(i, j int) bool) {
27-
rvp := reflect.ValueOf(sliceptr)
28-
if rvp.Type().Kind() != reflect.Ptr {
29-
panic(badTypeError{rvp.Type()})
30-
}
31-
rv := rvp.Elem()
32-
if rv.Type().Kind() != reflect.Slice {
33-
panic(badTypeError{rvp.Type()})
34-
}
35-
36-
length := rv.Len()
9+
// ModifySlice removes adjacent duplicate elements from the given slice. It
10+
// adjusts the length of the slice appropriately and zeros the tail.
11+
//
12+
// ModifySlice does O(len(*slice)) operations.
13+
func ModifySlice[E comparable](slice *[]E) {
14+
// Remove duplicates
3715
dst := 0
38-
for i := 1; i < length; i++ {
39-
if eq(dst, i) {
16+
for i := 1; i < len(*slice); i++ {
17+
if (*slice)[i] == (*slice)[dst] {
4018
continue
4119
}
4220
dst++
43-
// slice[dst] = slice[i]
44-
rv.Index(dst).Set(rv.Index(i))
21+
(*slice)[dst] = (*slice)[i]
4522
}
4623

24+
// Zero out the elements we removed at the end of the slice
4725
end := dst + 1
48-
var zero reflect.Value
49-
if end < length {
50-
zero = reflect.Zero(rv.Type().Elem())
51-
}
52-
53-
// for i := range slice[end:] {
54-
// size[i] = 0/nil/{}
55-
// }
56-
for i := end; i < length; i++ {
57-
// slice[i] = 0/nil/{}
58-
rv.Index(i).Set(zero)
26+
var zero E
27+
for i := end; i < len(*slice); i++ {
28+
(*slice)[i] = zero
5929
}
6030

61-
// slice = slice[:end]
62-
if end < length {
63-
rv.SetLen(end)
31+
// Truncate the slice
32+
if end < len(*slice) {
33+
*slice = (*slice)[:end]
6434
}
6535
}

util/uniq/slice_test.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"tailscale.com/util/uniq"
1313
)
1414

15-
func TestModifySlice(t *testing.T) {
15+
func runTests(t *testing.T, cb func(*[]int)) {
1616
tests := []struct {
1717
in []int
1818
want []int
@@ -29,7 +29,7 @@ func TestModifySlice(t *testing.T) {
2929
for _, test := range tests {
3030
in := make([]int, len(test.in))
3131
copy(in, test.in)
32-
uniq.ModifySlice(&test.in, func(i, j int) bool { return test.in[i] == test.in[j] })
32+
cb(&test.in)
3333
if !reflect.DeepEqual(test.in, test.want) {
3434
t.Errorf("uniq.Slice(%v) = %v, want %v", in, test.in, test.want)
3535
}
@@ -43,6 +43,12 @@ func TestModifySlice(t *testing.T) {
4343
}
4444
}
4545

46+
func TestModifySlice(t *testing.T) {
47+
runTests(t, func(slice *[]int) {
48+
uniq.ModifySlice(slice)
49+
})
50+
}
51+
4652
func Benchmark(b *testing.B) {
4753
benches := []struct {
4854
name string
@@ -83,6 +89,6 @@ func benchmark(b *testing.B, size int64, reset func(s []byte)) {
8389
for i := 0; i < b.N; i++ {
8490
s = s[:size]
8591
reset(s)
86-
uniq.ModifySlice(&s, func(i, j int) bool { return s[i] == s[j] })
92+
uniq.ModifySlice(&s)
8793
}
8894
}

wgengine/magicsock/magicsock.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2843,7 +2843,7 @@ func (c *Conn) bindSocket(rucPtr **RebindingUDPConn, network string, curPortFate
28432843
}
28442844
ports = append(ports, 0)
28452845
// Remove duplicates. (All duplicates are consecutive.)
2846-
uniq.ModifySlice(&ports, func(i, j int) bool { return ports[i] == ports[j] })
2846+
uniq.ModifySlice(&ports)
28472847

28482848
var pconn nettype.PacketConn
28492849
for _, port := range ports {

0 commit comments

Comments
 (0)