net/tsdial, ipn/ipnlocal: stop using netmap.NetworkMap in Dialer
tsdial.Dialer.SetNetMap rebuilt an O(n peers) map of MagicDNS names on every netmap change. As we move toward per-peer incremental deltas, this becomes quadratic. This removes it and replaces it with SetResolveMagicDNS, a callback into LocalBackend that looks up hostnames from nodeBackend's new nodeByName index (populated alongside nodeByAddr/nodeByKey on both full and delta paths). The index stores both FQDNs and short names as keys. This is the same treatment applied to netlog (8f210454d), wglog (988b0905b), and drive (1d6989408): stop pushing *netmap.NetworkMap into subsystems and instead have them pull from LocalBackend's live data via callbacks. Updates #12542 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I24557ab0c8a27636e08e4779bcfd3ec633db0a78
This commit is contained in:
committed by
Brad Fitzpatrick
parent
8dde9b725b
commit
aefb1531d1
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package mapx
|
||||
|
||||
// RepopulateNonzero re-uses an existing map (preserving its allocated
|
||||
// buckets) by zeroing all values, calling populate to re-fill it, and
|
||||
// then deleting any entries still at their zero value. If *m is nil it
|
||||
// is lazily initialized.
|
||||
//
|
||||
// This avoids the allocation cost of creating a new map on every call,
|
||||
// which matters for maps that are rebuilt frequently (e.g. on every
|
||||
// netmap update).
|
||||
func RepopulateNonzero[K comparable, V comparable](m *map[K]V, populate func()) {
|
||||
if *m == nil {
|
||||
*m = make(map[K]V)
|
||||
}
|
||||
var zero V
|
||||
for k := range *m {
|
||||
(*m)[k] = zero
|
||||
}
|
||||
populate()
|
||||
for k, v := range *m {
|
||||
if v == zero {
|
||||
delete(*m, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package mapx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRepopulateNonzero(t *testing.T) {
|
||||
var m map[string]int
|
||||
|
||||
// First call: nil map gets initialized and populated.
|
||||
RepopulateNonzero(&m, func() {
|
||||
m["a"] = 1
|
||||
m["b"] = 2
|
||||
})
|
||||
if got, want := len(m), 2; got != want {
|
||||
t.Fatalf("len = %d, want %d", got, want)
|
||||
}
|
||||
if m["a"] != 1 || m["b"] != 2 {
|
||||
t.Fatalf("got %v, want map[a:1 b:2]", m)
|
||||
}
|
||||
|
||||
// Second call: "b" is no longer populated, so it should be removed.
|
||||
RepopulateNonzero(&m, func() {
|
||||
m["a"] = 3
|
||||
m["c"] = 4
|
||||
})
|
||||
if got, want := len(m), 2; got != want {
|
||||
t.Fatalf("len = %d, want %d", got, want)
|
||||
}
|
||||
if m["a"] != 3 || m["c"] != 4 {
|
||||
t.Fatalf("got %v, want map[a:3 c:4]", m)
|
||||
}
|
||||
if _, ok := m["b"]; ok {
|
||||
t.Fatal("key 'b' should have been removed")
|
||||
}
|
||||
|
||||
// Populating nothing should clear the map.
|
||||
RepopulateNonzero(&m, func() {})
|
||||
if got := len(m); got != 0 {
|
||||
t.Fatalf("len = %d after empty populate, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRepopulateNonzero(b *testing.B) {
|
||||
for _, size := range []int{5, 100, 100_000} {
|
||||
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
|
||||
b.Run("RepopulateNonzero", func(b *testing.B) {
|
||||
var m map[string]int
|
||||
keys := makeKeys(size)
|
||||
// Seed the map so the first iteration isn't special.
|
||||
RepopulateNonzero(&m, func() {
|
||||
for i, k := range keys {
|
||||
m[k] = i + 1
|
||||
}
|
||||
})
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
RepopulateNonzero(&m, func() {
|
||||
for i, k := range keys {
|
||||
m[k] = i + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
b.Run("clear", func(b *testing.B) {
|
||||
m := make(map[string]int, size)
|
||||
keys := makeKeys(size)
|
||||
for i, k := range keys {
|
||||
m[k] = i + 1
|
||||
}
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
m = make(map[string]int, size)
|
||||
for i, k := range keys {
|
||||
m[k] = i + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeKeys(n int) []string {
|
||||
keys := make([]string, n)
|
||||
for i := range keys {
|
||||
keys[i] = fmt.Sprintf("key-%d", i)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user