From 3b3299139dd0692c48e1a438217340be8c797145 Mon Sep 17 00:00:00 2001 From: Johannes Kimmel Date: Thu, 23 Nov 2023 21:21:26 +0100 Subject: [PATCH] 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 --- ipalloc/ipalloc.go | 221 ++++++++++++++++++++++++++++++++-------- ipalloc/ipalloc_test.go | 29 +++++- 2 files changed, 205 insertions(+), 45 deletions(-) diff --git a/ipalloc/ipalloc.go b/ipalloc/ipalloc.go index 8761a0e..31389aa 100644 --- a/ipalloc/ipalloc.go +++ b/ipalloc/ipalloc.go @@ -13,7 +13,6 @@ type tree struct { prefix netip.Prefix lo *tree hi *tree - parent *tree minfreelen int } @@ -36,9 +35,6 @@ func (t *tree) Walk(f func(*tree) bool) { } 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 { best := t.FindBestPrefix(p) @@ -65,6 +61,106 @@ func (t *tree) FindBestPrefix(p netip.Prefix) *tree { } 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 { if t == nil { return nil @@ -103,8 +199,8 @@ func (t *tree) FindBestPrefixLen(bits int) *tree { } func (t *tree) Alloc(bits int) *tree { - //log.Println("Searching space for", bits) - best := t.FindBestPrefixLen(bits) + pp := t.FindBestPrefixLenPath(bits) + best := pp.top() if best == nil { best = t } @@ -112,30 +208,27 @@ func (t *tree) Alloc(bits int) *tree { return nil } - tofix := best - // the first insert can be into a new hi branch if best.lo != nil { best.hi = &tree{ prefix: SplitPrefixHi(best.prefix), - parent: best, minfreelen: best.prefix.Bits() + 2, } best = best.hi + pp = pp.push(best) } // the rest of the inserts are always into the lo branch for best.prefix.Bits() < bits { best.lo = &tree{ prefix: SplitPrefixLo(best.prefix), - parent: best, minfreelen: best.prefix.Bits() + 2, } best = best.lo + pp = pp.push(best) } - best.fixMinFree() - tofix.fixMinFreeAll() + fixMinFreeAll(pp) return best } @@ -159,16 +252,19 @@ func (t *tree) fixMinFree() { t.minfreelen = minfreelen } -func (t *tree) fixMinFreeAll() { - if t == nil { - return +func fixMinFreeAll(pp prefixpath) { + var root *tree + if len(pp) > 0 { + root = pp[0] } - fixfree := t - - for fixfree != nil { - fixfree.fixMinFree() - fixfree = fixfree.parent + for len(pp) > 0 { + var t *tree + pp, t = pp.pop() + t.fixMinFree() + } + 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 { - 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 } - for t.prefix.Bits() < p.Bits() { - if lo := SplitPrefixLo(t.prefix); lo.Overlaps(p) { - t.lo = &tree{prefix: lo, parent: t} - t = t.lo - } else if hi := SplitPrefixHi(t.prefix); hi.Overlaps(p) { - t.hi = &tree{prefix: hi, parent: t} - t = t.hi + for pp.top().prefix.Bits() < p.Bits() { + top := pp.top() + if lo := SplitPrefixLo(top.prefix); lo.Overlaps(p) { + top.lo = &tree{prefix: lo} + pp = pp.push(top.lo) + } else if hi := SplitPrefixHi(top.prefix); hi.Overlaps(p) { + top.hi = &tree{prefix: hi} + pp = pp.push(top.hi) } else { return nil } } - t.fixMinFreeAll() - return t + fixMinFreeAll(pp) + return pp.top() } type ErrNotFound struct{} @@ -214,28 +315,35 @@ func (e ErrNotEmpty) Error() string { } func (t *tree) Dealloc(p netip.Prefix) error { - match := t.Find(p) - if match == nil { + pp := t.FindPath(p) + if pp == nil { return ErrNotFound{} } - if !match.Leaf() { + if !pp.top().Leaf() { return ErrNotEmpty{} } - for match != nil { - if match.Leaf() && match.parent != nil { - if match.parent.lo == match { - match.parent.lo = nil + child := pp.top() + for len(pp) > 0 { + var top *tree + pp, top = pp.pop() + if child.Leaf() { + if top.lo == child { + top.lo = nil } - if match.parent.hi == match { - match.parent.hi = nil + if top.hi == child { + top.hi = nil } } - match.fixMinFree() - match = match.parent + top.fixMinFree() + child = top } + // fixup root + if t.Leaf() { + t.minfreelen = t.prefix.Bits() + 1 + } return nil } @@ -264,6 +372,31 @@ func (t *tree) Dot() 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 { sync.Mutex provisions *tree @@ -286,7 +419,7 @@ func (db *DB) Used() []netip.Prefix { defer db.Unlock() var ret []netip.Prefix db.provisions.Walk(func(t *tree) bool { - if t.Leaf() && t.parent != nil { + if t.Leaf() && t != db.provisions { ret = append(ret, t.prefix) } return true @@ -299,7 +432,7 @@ func (db *DB) UsedGrouped() [][]netip.Prefix { defer db.Unlock() bins := make(map[int][]netip.Prefix) db.provisions.Walk(func(t *tree) bool { - if t.Leaf() && t.parent != nil { + if t.Leaf() && t != db.provisions { p := t.prefix bins[p.Bits()] = append(bins[p.Bits()], p) } diff --git a/ipalloc/ipalloc_test.go b/ipalloc/ipalloc_test.go index bf6a4b5..70000e9 100644 --- a/ipalloc/ipalloc_test.go +++ b/ipalloc/ipalloc_test.go @@ -49,6 +49,17 @@ func TestTreeAllocSmall(t *testing.T) { for _, test := range tests { //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) if best == nil { log.Print(root.Dot()) @@ -139,11 +150,13 @@ func TestTreeDealloc(t *testing.T) { if err := root.Dealloc(first); err != nil { 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 { log.Println(t.prefix, t.Leaf()) return true }) + t.Log("Expected", first, "got", again) + t.Log(root.Dot()) t.Fatal(first) } } @@ -174,6 +187,7 @@ func TestFindBestPrefix(t *testing.T) { best := [][2]string{ {"128.0.0.0/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 { @@ -183,6 +197,16 @@ func TestFindBestPrefix(t *testing.T) { if best.prefix != 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) insert := root.Insert(p) if insert == nil { + t.Log("unable to find space for", p, "in", root) + t.Log(root.Dot()) t.Fatal(root, test) } 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 { t.Fatalf("%s", err) } + //fmt.Print(root.Dot()) 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.1/32")