tree: remove explicit parent pointer

- remove the explicit parent pointer
- instead, keep track of parents while diving down the tree
- this is in preparation to convert all operations to copy-on-write
This commit is contained in:
Johannes Kimmel 2023-11-23 21:21:26 +01:00
parent 83ae602c23
commit 3b3299139d
2 changed files with 205 additions and 45 deletions

View File

@ -13,7 +13,6 @@ type tree struct {
prefix netip.Prefix prefix netip.Prefix
lo *tree lo *tree
hi *tree hi *tree
parent *tree
minfreelen int minfreelen int
} }
@ -36,9 +35,6 @@ func (t *tree) Walk(f func(*tree) bool) {
} }
t.hi.Walk(f) t.hi.Walk(f)
} }
func (t *tree) haveSpace() bool {
return t != nil && !t.Full() && (t.parent == nil || !t.Leaf())
}
func (t *tree) Find(p netip.Prefix) *tree { func (t *tree) Find(p netip.Prefix) *tree {
best := t.FindBestPrefix(p) best := t.FindBestPrefix(p)
@ -65,6 +61,106 @@ func (t *tree) FindBestPrefix(p netip.Prefix) *tree {
} }
return t return t
} }
func (t *tree) FindPath(p netip.Prefix) prefixpath {
best := t.FindBestPrefixPath(p)
if best.top() != nil && best.top().prefix == p {
return best
}
return nil
}
func (t *tree) FindBestPrefixPath(p netip.Prefix) prefixpath {
if t == nil {
return nil
}
pp := make(prefixpath, 0, t.prefix.Addr().BitLen())
pp = pp.push(t)
cur := t
for {
switch {
case cur.lo != nil && cur.lo.prefix.Overlaps(p):
cur = cur.lo
pp = pp.push(cur)
case cur.hi != nil && cur.hi.prefix.Overlaps(p):
cur = cur.hi
pp = pp.push(cur)
default:
return pp
}
}
}
func (t *tree) FindBestPrefixLenPath(bits int) prefixpath {
return t.findBestPrefixLenPath(bits, make(prefixpath, 0, t.prefix.Addr().BitLen()))
}
func (t *tree) findBestPrefixLenPath(bits int, pp prefixpath) prefixpath {
if t == nil {
return nil
}
if t.Leaf() {
// already taken
return nil
}
if t.minfreelen > bits {
return nil
}
if t.prefix.Bits() >= bits {
return nil
}
pp = append(pp, t)
lobest := t.lo.findBestPrefixLenPath(bits, pp)
if lobest != nil && lobest.top().minfreelen == bits {
// found exact match with with the lowest possible address
// take it and don't search in high branches
return lobest
}
if lobest == nil {
// reuse pp and safe on allocations
hibest := t.hi.findBestPrefixLenPath(bits, pp)
if hibest != nil {
return hibest
}
return pp
} else {
// need to clip pp, so lower path isn't overwritten
hibest := t.hi.findBestPrefixLenPath(bits, slices.Clip(pp))
switch {
case hibest == nil:
return lobest
case lobest.top().minfreelen >= hibest.top().minfreelen:
return lobest
}
return hibest
}
}
func allowedMinFreeLen(t *tree, bits int) bool {
if t == nil {
return false
}
return t.minfreelen <= bits
}
func cmpMinFree(t1, t2 *tree) int {
if diff := t1.minfree() - t2.minfree(); diff != 0 {
return diff
}
return t1.prefix.Addr().Compare(t1.prefix.Addr())
}
func (t *tree) minfree() int {
if t == nil {
return 128 + 1
}
return t.minfreelen
}
func (t *tree) FindBestPrefixLen(bits int) *tree { func (t *tree) FindBestPrefixLen(bits int) *tree {
if t == nil { if t == nil {
return nil return nil
@ -103,8 +199,8 @@ func (t *tree) FindBestPrefixLen(bits int) *tree {
} }
func (t *tree) Alloc(bits int) *tree { func (t *tree) Alloc(bits int) *tree {
//log.Println("Searching space for", bits) pp := t.FindBestPrefixLenPath(bits)
best := t.FindBestPrefixLen(bits) best := pp.top()
if best == nil { if best == nil {
best = t best = t
} }
@ -112,30 +208,27 @@ func (t *tree) Alloc(bits int) *tree {
return nil return nil
} }
tofix := best
// the first insert can be into a new hi branch // the first insert can be into a new hi branch
if best.lo != nil { if best.lo != nil {
best.hi = &tree{ best.hi = &tree{
prefix: SplitPrefixHi(best.prefix), prefix: SplitPrefixHi(best.prefix),
parent: best,
minfreelen: best.prefix.Bits() + 2, minfreelen: best.prefix.Bits() + 2,
} }
best = best.hi best = best.hi
pp = pp.push(best)
} }
// the rest of the inserts are always into the lo branch // the rest of the inserts are always into the lo branch
for best.prefix.Bits() < bits { for best.prefix.Bits() < bits {
best.lo = &tree{ best.lo = &tree{
prefix: SplitPrefixLo(best.prefix), prefix: SplitPrefixLo(best.prefix),
parent: best,
minfreelen: best.prefix.Bits() + 2, minfreelen: best.prefix.Bits() + 2,
} }
best = best.lo best = best.lo
pp = pp.push(best)
} }
best.fixMinFree() fixMinFreeAll(pp)
tofix.fixMinFreeAll()
return best return best
} }
@ -159,16 +252,19 @@ func (t *tree) fixMinFree() {
t.minfreelen = minfreelen t.minfreelen = minfreelen
} }
func (t *tree) fixMinFreeAll() { func fixMinFreeAll(pp prefixpath) {
if t == nil { var root *tree
return if len(pp) > 0 {
root = pp[0]
} }
fixfree := t for len(pp) > 0 {
var t *tree
for fixfree != nil { pp, t = pp.pop()
fixfree.fixMinFree() t.fixMinFree()
fixfree = fixfree.parent }
if root.Leaf() {
root.minfreelen = root.prefix.Bits() + 1
} }
} }
@ -180,25 +276,30 @@ func (t *tree) calcfreelen(parent *tree) int {
} }
func (t *tree) Insert(p netip.Prefix) *tree { func (t *tree) Insert(p netip.Prefix) *tree {
t = t.FindBestPrefix(p) pp := t.FindBestPrefixPath(p)
if !t.haveSpace() { top := pp.top()
if top != t && (top.Full() || top.Leaf()) {
return nil
}
if top == t && top.Full() {
return nil return nil
} }
for t.prefix.Bits() < p.Bits() { for pp.top().prefix.Bits() < p.Bits() {
if lo := SplitPrefixLo(t.prefix); lo.Overlaps(p) { top := pp.top()
t.lo = &tree{prefix: lo, parent: t} if lo := SplitPrefixLo(top.prefix); lo.Overlaps(p) {
t = t.lo top.lo = &tree{prefix: lo}
} else if hi := SplitPrefixHi(t.prefix); hi.Overlaps(p) { pp = pp.push(top.lo)
t.hi = &tree{prefix: hi, parent: t} } else if hi := SplitPrefixHi(top.prefix); hi.Overlaps(p) {
t = t.hi top.hi = &tree{prefix: hi}
pp = pp.push(top.hi)
} else { } else {
return nil return nil
} }
} }
t.fixMinFreeAll() fixMinFreeAll(pp)
return t return pp.top()
} }
type ErrNotFound struct{} type ErrNotFound struct{}
@ -214,28 +315,35 @@ func (e ErrNotEmpty) Error() string {
} }
func (t *tree) Dealloc(p netip.Prefix) error { func (t *tree) Dealloc(p netip.Prefix) error {
match := t.Find(p) pp := t.FindPath(p)
if match == nil { if pp == nil {
return ErrNotFound{} return ErrNotFound{}
} }
if !match.Leaf() { if !pp.top().Leaf() {
return ErrNotEmpty{} return ErrNotEmpty{}
} }
for match != nil { child := pp.top()
if match.Leaf() && match.parent != nil { for len(pp) > 0 {
if match.parent.lo == match { var top *tree
match.parent.lo = nil pp, top = pp.pop()
if child.Leaf() {
if top.lo == child {
top.lo = nil
} }
if match.parent.hi == match { if top.hi == child {
match.parent.hi = nil top.hi = nil
} }
} }
match.fixMinFree() top.fixMinFree()
match = match.parent child = top
} }
// fixup root
if t.Leaf() {
t.minfreelen = t.prefix.Bits() + 1
}
return nil return nil
} }
@ -264,6 +372,31 @@ func (t *tree) Dot() string {
return buf.String() return buf.String()
} }
type prefixpath []*tree
func (pp prefixpath) empty() bool {
return len(pp) == 0
}
func (pp prefixpath) push(t *tree) prefixpath {
return append(pp, t)
}
func (pp prefixpath) pop() (prefixpath, *tree) {
if pp.empty() {
return nil, nil
}
lastidx := len(pp) - 1
last := pp[lastidx]
//pp[lastidx] = nil // breaks fixMinFreeAllPath
return pp[:lastidx], last
}
func (pp prefixpath) top() *tree {
if pp.empty() {
return nil
}
return pp[len(pp)-1]
}
type DB struct { type DB struct {
sync.Mutex sync.Mutex
provisions *tree provisions *tree
@ -286,7 +419,7 @@ func (db *DB) Used() []netip.Prefix {
defer db.Unlock() defer db.Unlock()
var ret []netip.Prefix var ret []netip.Prefix
db.provisions.Walk(func(t *tree) bool { db.provisions.Walk(func(t *tree) bool {
if t.Leaf() && t.parent != nil { if t.Leaf() && t != db.provisions {
ret = append(ret, t.prefix) ret = append(ret, t.prefix)
} }
return true return true
@ -299,7 +432,7 @@ func (db *DB) UsedGrouped() [][]netip.Prefix {
defer db.Unlock() defer db.Unlock()
bins := make(map[int][]netip.Prefix) bins := make(map[int][]netip.Prefix)
db.provisions.Walk(func(t *tree) bool { db.provisions.Walk(func(t *tree) bool {
if t.Leaf() && t.parent != nil { if t.Leaf() && t != db.provisions {
p := t.prefix p := t.prefix
bins[p.Bits()] = append(bins[p.Bits()], p) bins[p.Bits()] = append(bins[p.Bits()], p)
} }

View File

@ -49,6 +49,17 @@ func TestTreeAllocSmall(t *testing.T) {
for _, test := range tests { for _, test := range tests {
//t.Log("searching for", test.bits) //t.Log("searching for", test.bits)
findbest := root.FindBestPrefixLen(test.bits)
findbestpath := root.FindBestPrefixLenPath(test.bits)
if findbest != findbestpath.top() {
for _, pe := range findbestpath {
t.Log(pe.prefix)
}
fmt.Println(root.Dot())
t.Fatal(findbestpath.top().prefix, findbest.prefix)
}
best := root.Alloc(test.bits) best := root.Alloc(test.bits)
if best == nil { if best == nil {
log.Print(root.Dot()) log.Print(root.Dot())
@ -139,11 +150,13 @@ func TestTreeDealloc(t *testing.T) {
if err := root.Dealloc(first); err != nil { if err := root.Dealloc(first); err != nil {
t.Fatal("failed to remove", first) t.Fatal("failed to remove", first)
} }
if first != root.Alloc(2).prefix { if again := root.Alloc(2).prefix; again != first {
root.Walk(func(t *tree) bool { root.Walk(func(t *tree) bool {
log.Println(t.prefix, t.Leaf()) log.Println(t.prefix, t.Leaf())
return true return true
}) })
t.Log("Expected", first, "got", again)
t.Log(root.Dot())
t.Fatal(first) t.Fatal(first)
} }
} }
@ -174,6 +187,7 @@ func TestFindBestPrefix(t *testing.T) {
best := [][2]string{ best := [][2]string{
{"128.0.0.0/32", "128.0.0.0/1"}, {"128.0.0.0/32", "128.0.0.0/1"},
{"128.0.0.1/32", "128.0.0.0/1"}, {"128.0.0.1/32", "128.0.0.0/1"},
{"0.0.0.0/32", "0.0.0.0/32"},
} }
for _, test := range best { for _, test := range best {
@ -183,6 +197,16 @@ func TestFindBestPrefix(t *testing.T) {
if best.prefix != want { if best.prefix != want {
t.Fatal(best, want) t.Fatal(best, want)
} }
bestpath := root.FindBestPrefixPath(netip.MustParsePrefix(test[0]))
if bestpath.top().prefix != want {
for _, pe := range bestpath {
t.Log(pe.prefix)
}
t.Fatal(bestpath.top().prefix, want)
}
//for _, pe := range bestpath {
// t.Log(pe.prefix)
//}
} }
} }
@ -201,6 +225,8 @@ func TestInsert(t *testing.T) {
p := netip.MustParsePrefix(test) p := netip.MustParsePrefix(test)
insert := root.Insert(p) insert := root.Insert(p)
if insert == nil { if insert == nil {
t.Log("unable to find space for", p, "in", root)
t.Log(root.Dot())
t.Fatal(root, test) t.Fatal(root, test)
} }
match := root.Find(p) match := root.Find(p)
@ -249,6 +275,7 @@ func TestAllocDealloc2(t *testing.T) {
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil { if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
t.Fatalf("%s", err) t.Fatalf("%s", err)
} }
//fmt.Print(root.Dot())
expectAlloc(t, root, 32, "0.0.0.5/32") expectAlloc(t, root, 32, "0.0.0.5/32")
expectAlloc(t, root, 32, "0.0.0.0/32") expectAlloc(t, root, 32, "0.0.0.0/32")
expectAlloc(t, root, 32, "0.0.0.1/32") expectAlloc(t, root, 32, "0.0.0.1/32")