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:
David Anderson
2023-04-04 09:00:51 -07:00
committed by Dave Anderson
parent edb02b63f8
commit a7c910e361
4 changed files with 708 additions and 6 deletions

View File

@@ -16,6 +16,7 @@ import (
)
func TestInversePrefix(t *testing.T) {
t.Parallel()
for i := 0; i < 256; i++ {
for len := 0; len < 9; len++ {
addr := i & (0xFF << (8 - len))
@@ -29,6 +30,7 @@ func TestInversePrefix(t *testing.T) {
}
func TestHostIndex(t *testing.T) {
t.Parallel()
for i := 0; i < 256; i++ {
got := hostIndex(uint8(i))
want := prefixIndex(uint8(i), 8)
@@ -39,6 +41,7 @@ func TestHostIndex(t *testing.T) {
}
func TestStrideTableInsert(t *testing.T) {
t.Parallel()
// Verify that strideTable's lookup results after a bunch of inserts exactly
// match those of a naive implementation that just scans all prefixes on
// every lookup. The naive implementation is very slow, but its behavior is
@@ -66,6 +69,7 @@ func TestStrideTableInsert(t *testing.T) {
}
func TestStrideTableInsertShuffled(t *testing.T) {
t.Parallel()
// The order in which routes are inserted into a route table does not
// influence the final shape of the table, as long as the same set of
// prefixes is being inserted. This test verifies that strideTable behaves
@@ -111,6 +115,7 @@ func TestStrideTableInsertShuffled(t *testing.T) {
}
func TestStrideTableDelete(t *testing.T) {
t.Parallel()
// Compare route deletion to our reference slowTable.
pfxs := shufflePrefixes(allPrefixes())[:100]
slow := slowTable[int]{pfxs}
@@ -145,6 +150,7 @@ func TestStrideTableDelete(t *testing.T) {
}
func TestStrideTableDeleteShuffle(t *testing.T) {
t.Parallel()
// Same as TestStrideTableInsertShuffle, the order in which prefixes are
// deleted should not impact the final shape of the route table.
@@ -191,17 +197,17 @@ func TestStrideTableDeleteShuffle(t *testing.T) {
}
}
var benchRouteCount = []int{10, 50, 100, 200}
var strideRouteCount = []int{10, 50, 100, 200}
// forCountAndOrdering runs the benchmark fn with different sets of routes.
//
// fn is called once for each combination of {num_routes, order}, where
// num_routes is the values in benchRouteCount, and order is the order of the
// num_routes is the values in strideRouteCount, and order is the order of the
// routes in the list: random, largest prefix first (/0 to /8), and smallest
// prefix first (/8 to /0).
func forCountAndOrdering(b *testing.B, fn func(b *testing.B, routes []slowEntry[int])) {
func forStrideCountAndOrdering(b *testing.B, fn func(b *testing.B, routes []slowEntry[int])) {
routes := shufflePrefixes(allPrefixes())
for _, nroutes := range benchRouteCount {
for _, nroutes := range strideRouteCount {
b.Run(fmt.Sprint(nroutes), func(b *testing.B) {
routes := append([]slowEntry[int](nil), routes[:nroutes]...)
b.Run("random_order", func(b *testing.B) {
@@ -233,7 +239,7 @@ func forCountAndOrdering(b *testing.B, fn func(b *testing.B, routes []slowEntry[
}
func BenchmarkStrideTableInsertion(b *testing.B) {
forCountAndOrdering(b, func(b *testing.B, routes []slowEntry[int]) {
forStrideCountAndOrdering(b, func(b *testing.B, routes []slowEntry[int]) {
val := 0
for i := 0; i < b.N; i++ {
var rt strideTable[int]
@@ -250,7 +256,7 @@ func BenchmarkStrideTableInsertion(b *testing.B) {
}
func BenchmarkStrideTableDeletion(b *testing.B) {
forCountAndOrdering(b, func(b *testing.B, routes []slowEntry[int]) {
forStrideCountAndOrdering(b, func(b *testing.B, routes []slowEntry[int]) {
val := 0
var rt strideTable[int]
for _, route := range routes {