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
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)
}

View File

@ -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")