improve allocation performance by tracking available space in subtrees
This commit is contained in:
parent
822a8039b1
commit
4d8620ebd7
|
@ -5,14 +5,16 @@ import (
|
|||
"log"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type tree struct {
|
||||
prefix netip.Prefix
|
||||
lo *tree
|
||||
hi *tree
|
||||
parent *tree
|
||||
prefix netip.Prefix
|
||||
lo *tree
|
||||
hi *tree
|
||||
parent *tree
|
||||
minfreelen int
|
||||
}
|
||||
|
||||
func (t *tree) Prefix() netip.Prefix {
|
||||
|
@ -71,33 +73,32 @@ func (t *tree) FindBestPrefixLen(bits int) *tree {
|
|||
// already taken
|
||||
return nil
|
||||
}
|
||||
//log.Println("testing", t.prefix)
|
||||
if t.minfreelen > bits {
|
||||
return nil
|
||||
}
|
||||
if t.prefix.Bits() >= bits {
|
||||
//log.Println("too long", t.prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
var best *tree
|
||||
if !t.Full() {
|
||||
|
||||
lobest := t.lo.FindBestPrefixLen(bits)
|
||||
hibest := t.hi.FindBestPrefixLen(bits)
|
||||
switch {
|
||||
case lobest == nil:
|
||||
best = hibest
|
||||
case hibest == nil:
|
||||
best = lobest
|
||||
case lobest.minfreelen >= hibest.minfreelen:
|
||||
best = lobest
|
||||
default:
|
||||
best = hibest
|
||||
}
|
||||
|
||||
if best == nil {
|
||||
best = t
|
||||
}
|
||||
|
||||
better := func(t1, t2 *tree) *tree {
|
||||
switch {
|
||||
case t1 == nil:
|
||||
return t2
|
||||
case t2 == nil:
|
||||
return t1
|
||||
case t1.prefix.Bits() >= t2.prefix.Bits():
|
||||
return t1
|
||||
default:
|
||||
return t2
|
||||
}
|
||||
}
|
||||
|
||||
best = better(best, t.lo.FindBestPrefixLen(bits))
|
||||
best = better(best, t.hi.FindBestPrefixLen(bits))
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
|
@ -114,8 +115,9 @@ func (t *tree) Alloc(bits int) *tree {
|
|||
// the first insert can be into a new hi branch
|
||||
if best.lo != nil {
|
||||
best.hi = &tree{
|
||||
prefix: SplitPrefixHi(best.prefix),
|
||||
parent: best,
|
||||
prefix: SplitPrefixHi(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 1,
|
||||
}
|
||||
best = best.hi
|
||||
}
|
||||
|
@ -123,15 +125,57 @@ func (t *tree) Alloc(bits int) *tree {
|
|||
// 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,
|
||||
prefix: SplitPrefixLo(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 1,
|
||||
}
|
||||
best = best.lo
|
||||
}
|
||||
|
||||
best.fixMinFreeAll()
|
||||
return best
|
||||
}
|
||||
|
||||
func (t *tree) fixMinFree() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if t.Leaf() {
|
||||
t.minfreelen = t.prefix.Addr().BitLen() + 1
|
||||
}
|
||||
lofree := t.lo.calcfreelen(t)
|
||||
hifree := t.hi.calcfreelen(t)
|
||||
minfreelen := max(min(lofree, hifree), t.prefix.Bits()+1)
|
||||
|
||||
if t.minfreelen != minfreelen {
|
||||
//log.Printf("lo:%v, hi:%v\n", fixfree.lo == nil, fixfree.hi == nil)
|
||||
//log.Printf("Fixing space for %q, lo: %d, hi: %d, old: %d, new: %d", fixfree.prefix, lofree, hifree, fixfree.minfreelen, minfreelen)
|
||||
t.minfreelen = minfreelen
|
||||
}
|
||||
}
|
||||
func (t *tree) fixMinFreeAll() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if t.Leaf() {
|
||||
t.minfreelen = t.prefix.Addr().BitLen() + 1
|
||||
}
|
||||
|
||||
fixfree := t.parent
|
||||
|
||||
for fixfree != nil {
|
||||
fixfree.fixMinFree()
|
||||
fixfree = fixfree.parent
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tree) calcfreelen(parent *tree) int {
|
||||
if t == nil {
|
||||
return parent.prefix.Bits() + 1
|
||||
}
|
||||
return t.minfreelen
|
||||
}
|
||||
|
||||
func (t *tree) Insert(p netip.Prefix) *tree {
|
||||
t = t.FindBestPrefix(p)
|
||||
|
||||
|
@ -150,6 +194,7 @@ func (t *tree) Insert(p netip.Prefix) *tree {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
t.fixMinFreeAll()
|
||||
return t
|
||||
}
|
||||
|
||||
|
@ -184,12 +229,38 @@ func (t *tree) Dealloc(p netip.Prefix) error {
|
|||
}
|
||||
}
|
||||
|
||||
match.fixMinFree()
|
||||
match = match.parent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tree) Dot() string {
|
||||
var buf strings.Builder
|
||||
|
||||
fmt.Fprintln(&buf, "strict digraph {")
|
||||
|
||||
t.Walk(func(t *tree) bool {
|
||||
printEdge := func(parent, child *tree) {
|
||||
if child == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(&buf, "\t%q -> %q;\n", parent.prefix, child.prefix)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, "\t%q [shape=none label=<<table border=\"0\" cellspacing=\"0\" cellborder=\"1\"><tr><td>prefix</td><td>%s</td></tr><tr><td>minfreelen</td><td>%d</td></tr></table>>];\n", t.prefix, t.prefix, t.minfreelen)
|
||||
|
||||
printEdge(t, t.lo)
|
||||
printEdge(t, t.hi)
|
||||
|
||||
return true
|
||||
})
|
||||
fmt.Fprintln(&buf, "}")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
sync.Mutex
|
||||
provisions *tree
|
||||
|
|
|
@ -22,6 +22,32 @@ func TestSplitPrefix(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTreeAllocSmall(t *testing.T) {
|
||||
tests := []struct {
|
||||
bits int
|
||||
best string
|
||||
}{
|
||||
{32, "0.0.0.0/32"},
|
||||
{32, "0.0.0.1/32"},
|
||||
{31, "0.0.0.2/31"},
|
||||
{32, "0.0.0.4/32"},
|
||||
}
|
||||
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/28")}
|
||||
|
||||
for _, test := range tests {
|
||||
//t.Log("searching for", test.bits)
|
||||
best := root.Alloc(test.bits)
|
||||
if best == nil {
|
||||
log.Print(root.Dot())
|
||||
t.Fatal(root, test.bits)
|
||||
}
|
||||
//t.Log(test, "->", best.prefix)
|
||||
if best.prefix != netip.MustParsePrefix(test.best) {
|
||||
log.Print(root.Dot())
|
||||
t.Fatal(root, test.bits, best.prefix.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestTreeAlloc(t *testing.T) {
|
||||
tests := []struct {
|
||||
bits int
|
||||
|
@ -42,10 +68,10 @@ func TestTreeAlloc(t *testing.T) {
|
|||
t.Fatal(root, test.bits)
|
||||
}
|
||||
if best.prefix != netip.MustParsePrefix(test.best) {
|
||||
log.Print(root.Dot())
|
||||
t.Fatal(root, test.bits, best.prefix.String())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTreeDealloc(t *testing.T) {
|
||||
|
@ -177,8 +203,28 @@ func TestInsert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func expectAlloc(t *testing.T, root *tree, bits int, expect string) {
|
||||
if alloc := root.Alloc(bits); alloc.prefix != netip.MustParsePrefix(expect) {
|
||||
t.Logf("allocating %d bits failed: got %s, want %s", bits, alloc.prefix, expect)
|
||||
t.Log("graph:\n", root.Dot())
|
||||
t.Fatal()
|
||||
|
||||
}
|
||||
}
|
||||
func TestAllocDealloc(t *testing.T) {
|
||||
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/30")}
|
||||
expectAlloc(t, root, 31, "0.0.0.0/31")
|
||||
expectAlloc(t, root, 32, "0.0.0.2/32")
|
||||
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
expectAlloc(t, root, 32, "0.0.0.3/32")
|
||||
expectAlloc(t, root, 32, "0.0.0.0/32")
|
||||
expectAlloc(t, root, 32, "0.0.0.1/32")
|
||||
}
|
||||
|
||||
func BenchmarkFill(b *testing.B) {
|
||||
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/20")}
|
||||
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/16")}
|
||||
for {
|
||||
if alloc := root.Alloc(32); alloc == nil {
|
||||
break
|
||||
|
|
Loading…
Reference in New Issue