mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-03 02:21:58 +00:00
net/art: implement the Table type, a multi-level art route table.
Updates #7781 │ sec/op │ TableInsertion/ipv4/10 1.562µ ± 2% TableInsertion/ipv4/100 2.398µ ± 5% TableInsertion/ipv4/1000 2.097µ ± 3% TableInsertion/ipv4/10000 2.756µ ± 4% TableInsertion/ipv4/100000 2.473µ ± 13% TableInsertion/ipv6/10 7.649µ ± 2% TableInsertion/ipv6/100 12.09µ ± 3% TableInsertion/ipv6/1000 14.84µ ± 5% TableInsertion/ipv6/10000 14.72µ ± 8% TableInsertion/ipv6/100000 13.23µ ± 41% TableDelete/ipv4/10 378.4n ± 5% TableDelete/ipv4/100 366.9n ± 3% TableDelete/ipv4/1000 418.6n ± 3% TableDelete/ipv4/10000 609.2n ± 11% TableDelete/ipv4/100000 679.2n ± 28% TableDelete/ipv6/10 504.2n ± 4% TableDelete/ipv6/100 959.5n ± 12% TableDelete/ipv6/1000 1.436µ ± 6% TableDelete/ipv6/10000 1.772µ ± 15% TableDelete/ipv6/100000 1.172µ ± 113% TableGet/ipv4/10 32.14n ± 11% TableGet/ipv4/100 38.58n ± 2% TableGet/ipv4/1000 45.03n ± 2% TableGet/ipv4/10000 52.90n ± 7% TableGet/ipv4/100000 135.2n ± 11% TableGet/ipv6/10 41.55n ± 1% TableGet/ipv6/100 44.78n ± 2% TableGet/ipv6/1000 49.03n ± 2% TableGet/ipv6/10000 65.38n ± 5% TableGet/ipv6/100000 525.0n ± 39% │ avg-B/op │ TableInsertion/ipv4/10 25.18Ki ± 0% TableInsertion/ipv4/100 17.63Ki ± 0% TableInsertion/ipv4/1000 14.14Ki ± 0% TableInsertion/ipv4/10000 12.92Ki ± 0% TableInsertion/ipv4/100000 11.13Ki ± 0% TableInsertion/ipv6/10 76.87Ki ± 0% TableInsertion/ipv6/100 98.33Ki ± 0% TableInsertion/ipv6/1000 91.44Ki ± 0% TableInsertion/ipv6/10000 90.39Ki ± 0% TableInsertion/ipv6/100000 87.19Ki ± 0% TableDelete/ipv4/10 3.230 ± 0% TableDelete/ipv4/100 4.020 ± 0% TableDelete/ipv4/1000 3.990 ± 0% TableDelete/ipv4/10000 4.000 ± 0% TableDelete/ipv4/100000 4.000 ± 0% TableDelete/ipv6/10 16.00 ± 0% TableDelete/ipv6/100 16.00 ± 0% TableDelete/ipv6/1000 16.00 ± 0% TableDelete/ipv6/10000 16.00 ± 0% TableDelete/ipv6/100000 16.00 ± 0% │ avg-allocs/op │ TableInsertion/ipv4/10 2.900 ± 0% TableInsertion/ipv4/100 2.330 ± 0% TableInsertion/ipv4/1000 2.070 ± 0% TableInsertion/ipv4/10000 1.980 ± 0% TableInsertion/ipv4/100000 1.840 ± 0% TableInsertion/ipv6/10 6.800 ± 0% TableInsertion/ipv6/100 8.420 ± 0% TableInsertion/ipv6/1000 7.900 ± 0% TableInsertion/ipv6/10000 7.820 ± 0% TableInsertion/ipv6/100000 7.580 ± 0% TableDelete/ipv4/10 1.000 ± 0% TableDelete/ipv4/100 1.000 ± 0% TableDelete/ipv4/1000 1.000 ± 0% TableDelete/ipv4/10000 1.000 ± 0% TableDelete/ipv4/100000 1.000 ± 0% TableDelete/ipv6/10 1.000 ± 0% TableDelete/ipv6/100 1.000 ± 0% TableDelete/ipv6/1000 1.000 ± 0% TableDelete/ipv6/10000 1.000 ± 0% TableDelete/ipv6/100000 1.000 ± 0% │ routes/s │ TableInsertion/ipv4/10 640.3k ± 2% TableInsertion/ipv4/100 417.1k ± 5% TableInsertion/ipv4/1000 477.0k ± 3% TableInsertion/ipv4/10000 362.8k ± 5% TableInsertion/ipv4/100000 404.5k ± 15% TableInsertion/ipv6/10 130.7k ± 1% TableInsertion/ipv6/100 82.69k ± 3% TableInsertion/ipv6/1000 67.37k ± 5% TableInsertion/ipv6/10000 67.93k ± 9% TableInsertion/ipv6/100000 75.63k ± 29% TableDelete/ipv4/10 2.642M ± 6% TableDelete/ipv4/100 2.726M ± 3% TableDelete/ipv4/1000 2.389M ± 3% TableDelete/ipv4/10000 1.641M ± 12% TableDelete/ipv4/100000 1.472M ± 27% TableDelete/ipv6/10 1.984M ± 4% TableDelete/ipv6/100 1.042M ± 11% TableDelete/ipv6/1000 696.5k ± 6% TableDelete/ipv6/10000 564.4k ± 13% TableDelete/ipv6/100000 853.6k ± 53% │ addrs/s │ TableGet/ipv4/10 31.11M ± 10% TableGet/ipv4/100 25.92M ± 2% TableGet/ipv4/1000 22.21M ± 2% TableGet/ipv4/10000 18.91M ± 8% TableGet/ipv4/100000 7.397M ± 12% TableGet/ipv6/10 24.07M ± 1% TableGet/ipv6/100 22.33M ± 2% TableGet/ipv6/1000 20.40M ± 2% TableGet/ipv6/10000 15.30M ± 5% TableGet/ipv6/100000 1.905M ± 28% │ B/op │ TableGet/ipv4/10 4.000 ± 0% TableGet/ipv4/100 4.000 ± 0% TableGet/ipv4/1000 4.000 ± 0% TableGet/ipv4/10000 4.000 ± 0% TableGet/ipv4/100000 4.000 ± 0% TableGet/ipv6/10 16.00 ± 0% TableGet/ipv6/100 16.00 ± 0% TableGet/ipv6/1000 16.00 ± 0% TableGet/ipv6/10000 16.00 ± 0% TableGet/ipv6/100000 16.00 ± 0% │ allocs/op │ TableGet/ipv4/10 1.000 ± 0% TableGet/ipv4/100 1.000 ± 0% TableGet/ipv4/1000 1.000 ± 0% TableGet/ipv4/10000 1.000 ± 0% TableGet/ipv4/100000 1.000 ± 0% TableGet/ipv6/10 1.000 ± 0% TableGet/ipv6/100 1.000 ± 0% TableGet/ipv6/1000 1.000 ± 0% TableGet/ipv6/10000 1.000 ± 0% TableGet/ipv6/100000 1.000 ± 0% Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
committed by
Dave Anderson
parent
edb02b63f8
commit
a7c910e361
149
net/art/table.go
149
net/art/table.go
@@ -11,3 +11,152 @@
|
||||
// For more information, see Yoichi Hariguchi's paper:
|
||||
// https://cseweb.ucsd.edu//~varghese/TEACH/cs228/artlookup.pdf
|
||||
package art
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Table is an IPv4 and IPv6 routing table.
|
||||
type Table[T any] struct {
|
||||
v4 strideTable[T]
|
||||
v6 strideTable[T]
|
||||
}
|
||||
|
||||
// Get does a route lookup for addr and returns the associated value, or nil if
|
||||
// no route matched.
|
||||
func (t *Table[T]) Get(addr netip.Addr) *T {
|
||||
st := &t.v4
|
||||
if addr.Is6() {
|
||||
st = &t.v6
|
||||
}
|
||||
|
||||
var ret *T
|
||||
for _, stride := range addr.AsSlice() {
|
||||
rt, child := st.getValAndChild(stride)
|
||||
if rt != nil {
|
||||
// Found a more specific route than whatever we found previously,
|
||||
// keep a note.
|
||||
ret = rt
|
||||
}
|
||||
if child == nil {
|
||||
// No sub-routes further down, whatever we have recorded in ret is
|
||||
// the result.
|
||||
return ret
|
||||
}
|
||||
st = child
|
||||
}
|
||||
|
||||
// Unreachable because Insert/Delete won't allow the leaf strideTables to
|
||||
// have children, so we must return via the nil check in the loop.
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Insert adds pfx to the table, with value val.
|
||||
// If pfx is already present in the table, its value is set to val.
|
||||
func (t *Table[T]) Insert(pfx netip.Prefix, val *T) {
|
||||
if val == nil {
|
||||
panic("Table.Insert called with nil value")
|
||||
}
|
||||
st := &t.v4
|
||||
if pfx.Addr().Is6() {
|
||||
st = &t.v6
|
||||
}
|
||||
bs := pfx.Addr().AsSlice()
|
||||
i := 0
|
||||
numBits := pfx.Bits()
|
||||
|
||||
// The strideTable we want to insert into is potentially at the end of a
|
||||
// chain of parent tables, each one encoding successive 8 bits of the
|
||||
// prefix. Navigate downwards, allocating child tables as needed, until we
|
||||
// find the one this prefix belongs in.
|
||||
for numBits > 8 {
|
||||
st = st.getOrCreateChild(bs[i])
|
||||
i++
|
||||
numBits -= 8
|
||||
}
|
||||
// Finally, insert the remaining 0-8 bits of the prefix into the child
|
||||
// table.
|
||||
st.insert(bs[i], numBits, val)
|
||||
}
|
||||
|
||||
// Delete removes pfx from the table, if it is present.
|
||||
func (t *Table[T]) Delete(pfx netip.Prefix) {
|
||||
st := &t.v4
|
||||
if pfx.Addr().Is6() {
|
||||
st = &t.v6
|
||||
}
|
||||
bs := pfx.Addr().AsSlice()
|
||||
i := 0
|
||||
numBits := pfx.Bits()
|
||||
|
||||
// Deletion may drive the refcount of some strideTables down to zero. We
|
||||
// need to clean up these dangling tables, so we have to keep track of which
|
||||
// tables we touch on the way down, and which strideEntry index each child
|
||||
// is registered in.
|
||||
strideTables := [16]*strideTable[T]{st}
|
||||
var strideIndexes [16]int
|
||||
|
||||
// Similar to Insert, navigate down the tree of strideTables, looking for
|
||||
// the one that houses the last 0-8 bits of the prefix to delete.
|
||||
//
|
||||
// The only difference is that here, we don't create missing child tables.
|
||||
// If a child necessary to pfx is missing, then the pfx cannot exist in the
|
||||
// Table, and we can exit early.
|
||||
for numBits > 8 {
|
||||
child, idx := st.getChild(bs[i])
|
||||
if child == nil {
|
||||
// Prefix can't exist in the table, one of the necessary
|
||||
// strideTables doesn't exit.
|
||||
return
|
||||
}
|
||||
// Note that the strideIndex and strideTables entries are off-by-one.
|
||||
// The child table pointer is recorded at i+1, but it is referenced by a
|
||||
// particular index in the parent table, at index i.
|
||||
strideIndexes[i] = idx
|
||||
i++
|
||||
strideTables[i] = child
|
||||
numBits -= 8
|
||||
st = child
|
||||
}
|
||||
if st.delete(bs[i], numBits) == nil {
|
||||
// Prefix didn't exist in the expected strideTable, refcount hasn't
|
||||
// changed, no need to run through cleanup.
|
||||
return
|
||||
}
|
||||
|
||||
// st.delete reduced st's refcount by one, so we may be hanging onto a chain
|
||||
// of redundant strideTables. Walk back up the path we recorded in the
|
||||
// descent loop, deleting tables until we encounter one that still has other
|
||||
// refs (or we hit the root strideTable, which is never deleted).
|
||||
for i > 0 && strideTables[i].refs == 0 {
|
||||
strideTables[i-1].deleteChild(strideIndexes[i-1])
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
// debugSummary prints the tree of allocated strideTables in t, with each
|
||||
// strideTable's refcount.
|
||||
func (t *Table[T]) debugSummary() string {
|
||||
var ret bytes.Buffer
|
||||
fmt.Fprintf(&ret, "v4: ")
|
||||
strideSummary(&ret, &t.v4, 0)
|
||||
fmt.Fprintf(&ret, "v6: ")
|
||||
strideSummary(&ret, &t.v6, 0)
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
func strideSummary[T any](w io.Writer, st *strideTable[T], indent int) {
|
||||
fmt.Fprintf(w, "%d refs\n", st.refs)
|
||||
indent += 2
|
||||
for i := firstHostIndex; i <= lastHostIndex; i++ {
|
||||
if child := st.entries[i].child; child != nil {
|
||||
addr, len := inversePrefixIndex(i)
|
||||
fmt.Fprintf(w, "%s%d/%d: ", strings.Repeat(" ", indent), addr, len)
|
||||
strideSummary(w, child, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user