Skip to content

Commit 0958b58

Browse files
committed
Simplify the data structure
1 parent f6dc114 commit 0958b58

File tree

6 files changed

+387
-575
lines changed

6 files changed

+387
-575
lines changed

pkg/routesum/routesum.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import (
77
"strings"
88

99
"github.com/PatrickCronin/routesum/pkg/routesum/bitslice"
10-
"github.com/PatrickCronin/routesum/pkg/routesum/rstrie"
10+
"github.com/PatrickCronin/routesum/pkg/routesum/rstree"
1111
"github.com/pkg/errors"
1212
"inet.af/netaddr"
1313
)
1414

1515
// RouteSum has methods supporting route summarization of networks and hosts
1616
type RouteSum struct {
17-
ipv4, ipv6 *rstrie.RSTrie
17+
ipv4, ipv6 *rstree.RSTree
1818
}
1919

2020
// NewRouteSum returns an initialized RouteSum object
2121
func NewRouteSum() *RouteSum {
2222
rs := new(RouteSum)
23-
rs.ipv4 = rstrie.NewRSTrie()
24-
rs.ipv6 = rstrie.NewRSTrie()
23+
rs.ipv4 = rstree.NewRSTree()
24+
rs.ipv6 = rstree.NewRSTree()
2525

2626
return rs
2727
}

pkg/routesum/routesum_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ func TestSummarize(t *testing.T) { //nolint: funlen
456456
}
457457
}
458458

459-
func TestRSTrieMemUsage(t *testing.T) { //nolint: funlen
459+
func TestMemUsage(t *testing.T) { //nolint: funlen
460460
tests := []struct {
461461
name string
462462
entries []string
@@ -473,7 +473,7 @@ func TestRSTrieMemUsage(t *testing.T) { //nolint: funlen
473473
entries: []string{
474474
"192.0.2.1",
475475
},
476-
expectedNumInternalNodes: 0,
476+
expectedNumInternalNodes: 32,
477477
expectedNumLeafNodes: 1,
478478
},
479479
{
@@ -482,7 +482,7 @@ func TestRSTrieMemUsage(t *testing.T) { //nolint: funlen
482482
"192.0.2.1",
483483
"192.0.2.0",
484484
},
485-
expectedNumInternalNodes: 0,
485+
expectedNumInternalNodes: 31,
486486
expectedNumLeafNodes: 1,
487487
},
488488
{
@@ -491,15 +491,15 @@ func TestRSTrieMemUsage(t *testing.T) { //nolint: funlen
491491
"192.0.2.1",
492492
"192.0.2.2",
493493
},
494-
expectedNumInternalNodes: 1,
494+
expectedNumInternalNodes: 33,
495495
expectedNumLeafNodes: 2,
496496
},
497497
{
498498
name: "one item, IPv6",
499499
entries: []string{
500500
"2001:db8::1",
501501
},
502-
expectedNumInternalNodes: 0,
502+
expectedNumInternalNodes: 128,
503503
expectedNumLeafNodes: 1,
504504
},
505505
{
@@ -508,7 +508,7 @@ func TestRSTrieMemUsage(t *testing.T) { //nolint: funlen
508508
"192.0.2.0",
509509
"2001:db8::1",
510510
},
511-
expectedNumInternalNodes: 0,
511+
expectedNumInternalNodes: 160,
512512
expectedNumLeafNodes: 2,
513513
},
514514
}

pkg/routesum/rstree/rstree.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Package rstree provides a datatype that supports building a space-efficient summary of networks
2+
// and IPs.
3+
package rstree
4+
5+
import (
6+
"container/list"
7+
"unsafe"
8+
9+
"github.com/PatrickCronin/routesum/pkg/routesum/bitslice"
10+
)
11+
12+
type node struct {
13+
children *[2]*node
14+
}
15+
16+
func (n *node) isLeaf() bool {
17+
return n.children == nil
18+
}
19+
20+
func (n *node) childrenAreCompleteSubtree() bool {
21+
if n.children[0] == nil || n.children[1] == nil ||
22+
!n.children[0].isLeaf() || !n.children[1].isLeaf() {
23+
return false
24+
}
25+
26+
return true
27+
}
28+
29+
// RSTree is a binary tree that supports the storage and retrieval of networks and IPs for the
30+
// purpose of route summarization.
31+
type RSTree struct {
32+
root *node
33+
}
34+
35+
// NewRSTree returns an initialized RSTree for use
36+
func NewRSTree() *RSTree {
37+
return &RSTree{
38+
root: nil,
39+
}
40+
}
41+
42+
// InsertRoute inserts a new BitSlice into the tree. Each insert results in a space-optimized tree
43+
// structure. If a route being inserted is already covered by an existing route, it's simply
44+
// ignored. If a route being inserted covers one or more routes already stored, those routes are
45+
// replaced.
46+
func (t *RSTree) InsertRoute(routeBits bitslice.BitSlice) {
47+
// If the tree has no root node, create one.
48+
if t.root == nil {
49+
t.root = &node{children: nil}
50+
51+
if len(routeBits) > 0 {
52+
t.root.children = new([2]*node)
53+
}
54+
}
55+
56+
// Perform a non-recursive search of the tree's nodes for the best place to insert the route,
57+
// and do so.
58+
visited := []*node{}
59+
curNode := t.root
60+
for i := range routeBits {
61+
if curNode.isLeaf() {
62+
// Does the current node cover the requested route? If so, we're done.
63+
return
64+
}
65+
66+
if curNode.children[routeBits[i]] == nil {
67+
curNode.children[routeBits[i]] = new(node)
68+
if i < len(routeBits)-1 {
69+
curNode.children[routeBits[i]].children = new([2]*node)
70+
}
71+
}
72+
73+
visited = append(visited, curNode)
74+
curNode = curNode.children[routeBits[i]]
75+
}
76+
77+
if !curNode.isLeaf() {
78+
// Does the requested route cover the current node? If so, update the current node.
79+
curNode.children = nil
80+
}
81+
82+
simplifyVisitedSubtrees(visited)
83+
}
84+
85+
// A completed subtree is a node in the tree whose children when taken together represent the
86+
// complete subtree below the node. For example, if the root node had a leaf node child for 0 and a
87+
// leaf-node child for 1, the node would be representing the "0" and 1" routes. But that's the same
88+
// as representing every possible route, so we'd simplify this to replace the root node with a leaf
89+
// node.
90+
// simplifyVisitedSubtrees takes a stack of visited nodes and simplifies completed subtrees as far
91+
// up the stack as possible. If at any point in the stack we find a node representing an incomplete
92+
// subtree, we stop.
93+
func simplifyVisitedSubtrees(visited []*node) {
94+
for i := len(visited) - 1; i >= 0; i-- {
95+
if !visited[i].childrenAreCompleteSubtree() {
96+
return
97+
}
98+
99+
visited[i].children = nil
100+
}
101+
}
102+
103+
type traversalStep struct {
104+
n *node
105+
precedingRouteBits bitslice.BitSlice
106+
}
107+
108+
// Contents returns the BitSlices contained in the RSTree.
109+
func (t *RSTree) Contents() []bitslice.BitSlice {
110+
// If the tree is empty
111+
if t.root == nil {
112+
return []bitslice.BitSlice{}
113+
}
114+
115+
// Otherwise
116+
remainingSteps := list.New()
117+
remainingSteps.PushFront(traversalStep{
118+
n: t.root,
119+
precedingRouteBits: bitslice.BitSlice{},
120+
})
121+
122+
contents := []bitslice.BitSlice{}
123+
for remainingSteps.Len() > 0 {
124+
step := remainingSteps.Remove(remainingSteps.Front()).(traversalStep)
125+
126+
if step.n.isLeaf() {
127+
contents = append(contents, step.precedingRouteBits)
128+
} else {
129+
lenPrecedingRouteBits := len(step.precedingRouteBits)
130+
131+
if step.n.children[1] != nil {
132+
highChildBits := make([]byte, lenPrecedingRouteBits+1)
133+
copy(highChildBits, step.precedingRouteBits)
134+
highChildBits[lenPrecedingRouteBits] = 1
135+
remainingSteps.PushFront(traversalStep{
136+
n: step.n.children[1],
137+
precedingRouteBits: highChildBits,
138+
})
139+
}
140+
141+
if step.n.children[0] != nil {
142+
lowChildBits := make([]byte, lenPrecedingRouteBits+1)
143+
copy(lowChildBits, step.precedingRouteBits)
144+
lowChildBits[lenPrecedingRouteBits] = 0
145+
remainingSteps.PushFront(traversalStep{
146+
n: step.n.children[0],
147+
precedingRouteBits: lowChildBits,
148+
})
149+
}
150+
}
151+
}
152+
153+
return contents
154+
}
155+
156+
func (t *RSTree) visitAll(cb func(*node)) {
157+
// If the trie is empty
158+
if t.root == nil {
159+
return
160+
}
161+
162+
// Otherwise
163+
remainingSteps := list.New()
164+
remainingSteps.PushFront(traversalStep{
165+
n: t.root,
166+
precedingRouteBits: bitslice.BitSlice{},
167+
})
168+
169+
for remainingSteps.Len() > 0 {
170+
curNode := remainingSteps.Remove(remainingSteps.Front()).(traversalStep)
171+
172+
// Act on this node
173+
cb(curNode.n)
174+
175+
// Traverse the remainder of the nodes
176+
if !curNode.n.isLeaf() {
177+
lenPrecedingRouteBits := len(curNode.precedingRouteBits)
178+
179+
if curNode.n.children[1] != nil {
180+
highChildBits := make([]byte, lenPrecedingRouteBits+1)
181+
copy(highChildBits, curNode.precedingRouteBits)
182+
highChildBits[lenPrecedingRouteBits] = 1
183+
remainingSteps.PushFront(traversalStep{
184+
n: curNode.n.children[1],
185+
precedingRouteBits: highChildBits,
186+
})
187+
}
188+
189+
if curNode.n.children[0] != nil {
190+
lowChildBits := make([]byte, lenPrecedingRouteBits+1)
191+
copy(lowChildBits, curNode.precedingRouteBits)
192+
lowChildBits[lenPrecedingRouteBits] = 0
193+
remainingSteps.PushFront(traversalStep{
194+
n: curNode.n.children[0],
195+
precedingRouteBits: lowChildBits,
196+
})
197+
}
198+
}
199+
}
200+
}
201+
202+
// MemUsage returns information about an RSTrie's current size in memory.
203+
func (t *RSTree) MemUsage() (uint, uint, uintptr, uintptr) {
204+
var numInternalNodes, numLeafNodes uint
205+
var internalNodesTotalSize, leafNodesTotalSize uintptr
206+
207+
tallyNode := func(n *node) {
208+
baseNodeSize := unsafe.Sizeof(node{}) //nolint: exhaustruct, gosec
209+
if n.isLeaf() {
210+
numLeafNodes++
211+
leafNodesTotalSize += baseNodeSize
212+
return
213+
}
214+
215+
numInternalNodes++
216+
internalNodesTotalSize += baseNodeSize + unsafe.Sizeof([2]*node{}) //nolint: gosec
217+
}
218+
t.visitAll(tallyNode)
219+
220+
return numInternalNodes, numLeafNodes, internalNodesTotalSize, leafNodesTotalSize
221+
}

0 commit comments

Comments
 (0)