Skip to content

Commit

Permalink
minor comments and more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gaissmai committed Jan 3, 2025
1 parent 4ebd569 commit 5261fc1
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 154 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ build the complete-binary-tree (CBT) of prefixes for each stride.
|:--:|
| *example from artlookup.pdf for a 4bit stride* |

The CBT is implemented as a bitvector, backtracking is just
The CBT is implemented as a bit-vector, backtracking is just
a matter of fast cache friendly bitmask operations.

The Table is implemented with popcount compressed sparse arrays
Expand Down Expand Up @@ -97,19 +97,30 @@ The API has changed in ..., v0.10.1, v0.11.0, v0.12.0, v0.12.6, v0.16.0

Please see the extensive [benchmarks](https://github.com/gaissmai/iprbench) comparing `bart` with other IP routing table implementations.

Just a teaser, LPM lookups against the full Internet routing table with random probes:
Just a teaser, Contains and Lookups against the full Internet routing table with random IP address probes:

```
goos: linux
goarch: amd64
pkg: github.com/gaissmai/bart
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkFullMatchV4/Contains 37375450 29.71 ns/op
BenchmarkFullMatchV6/Contains 41348316 26.85 ns/op
BenchmarkFullMissV4/Contains 38583682 29.66 ns/op
BenchmarkFullMissV6/Contains 83315865 12.64 ns/op
BenchmarkFullMatchV4/Contains 38003740 27.69 ns/op
BenchmarkFullMatchV6/Contains 49389355 23.00 ns/op
BenchmarkFullMissV4/Contains 42902048 26.57 ns/op
BenchmarkFullMissV6/Contains 128219610 9.375 ns/op
PASS
ok github.com/gaissmai/bart 11.248s
ok github.com/gaissmai/bart 12.195s

goos: linux
goarch: amd64
pkg: github.com/gaissmai/bart
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkFullMatchV4/Lookup 37453425 30.55 ns/op
BenchmarkFullMatchV6/Lookup 36819931 30.48 ns/op
BenchmarkFullMissV4/Lookup 37223881 30.70 ns/op
BenchmarkFullMissV6/Lookup 94333762 12.32 ns/op
PASS
ok github.com/gaissmai/bart 11.215s
```

## Compatibility Guarantees
Expand Down
6 changes: 3 additions & 3 deletions internal/sparse/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ func (s *Array[T]) Len() int {
return len(s.Items)
}

// Clone returns a copy of the Array.
// The elements are copied using assignment, so this is a shallow clone.
func (s *Array[T]) Clone() *Array[T] {
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (s *Array[T]) Copy() *Array[T] {
if s == nil {
return nil
}
Expand Down
31 changes: 31 additions & 0 deletions internal/sparse/array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,34 @@ func TestSparseArrayCompact(t *testing.T) {
t.Errorf("cap, expected 3_000, got %d", c)
}
}

func TestSparseArrayCopy(t *testing.T) {
t.Parallel()
a := new(Array[int])

for i := range 10_000 {
a.InsertAt(uint(i), i)
}

// shallow copy
b := a.Copy()

// basic values identity
for i, v := range a.Items {
if b.Items[i] != v {
t.Errorf("Clone, expect value: %v, got: %v", v, b.Items[i])
}
}

// update array a
for i := range 10_000 {
a.UpdateAt(uint(i), func(u int, _ bool) int { return u + 1 })
}

// cloned array must now differ
for i, v := range a.Items {
if b.Items[i] == v {
t.Errorf("update a after Clone, b must now differ: aValue: %v, bValue: %v", b.Items[i], v)
}
}
}
33 changes: 18 additions & 15 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ var zeroPath [16]byte
// (popcount-compressed slices) are used instead of fixed-size arrays.
//
// The array slots are also not pre-allocated (alloted) as described
// in the ART algorithm, but backtracking is used for the longest-prefix-match.
// in the ART algorithm, fast backtracking with a bitset vector is used
// to get the longest-prefix-match.
//
// The sparse child array recursively spans the trie with a branching factor of 256
// and also records path-compressed leaves in the free child slots.
// and also records path-compressed leaves in the free node slots.
type node[V any] struct {
// prefixes contains the routes as complete binary tree with payload V
prefixes sparse.Array[V]
Expand All @@ -44,17 +45,24 @@ type node[V any] struct {
children sparse.Array[interface{}]
}

// leaf is prefix and value together as path compressed child
// isEmpty returns true if node has neither prefixes nor children
func (n *node[V]) isEmpty() bool {
return n.prefixes.Len() == 0 && n.children.Len() == 0
}

// leaf is a prefix and value together, it's a path compressed child
type leaf[V any] struct {
prefix netip.Prefix
value V
}

// cloneValue, deep copy if v implements the Cloner interface.
func cloneValue[V any](v V) V {
// cloneOrCopyValue, helper function,
// deep copy if v implements the Cloner interface.
func cloneOrCopyValue[V any](v V) V {
if k, ok := any(v).(Cloner[V]); ok {
return k.Clone()
}
// just copy
return v
}

Expand All @@ -64,15 +72,10 @@ func (l *leaf[V]) cloneLeaf() *leaf[V] {
if l == nil {
return nil
}
return &leaf[V]{l.prefix, cloneValue(l.value)}
}

// isEmpty returns true if node has neither prefixes nor children
func (n *node[V]) isEmpty() bool {
return n.prefixes.Len() == 0 && n.children.Len() == 0
return &leaf[V]{l.prefix, cloneOrCopyValue(l.value)}
}

// nodeAndLeafCount
// nodeAndLeafCount for this node
func (n *node[V]) nodeAndLeafCount() (nodes int, leaves int) {
for i := range n.children.AsSlice(make([]uint, 0, maxNodeChildren)) {
switch n.children.Items[i].(type) {
Expand Down Expand Up @@ -240,15 +243,15 @@ func (n *node[V]) cloneRec() *node[V] {
}

// shallow
c.prefixes = *(n.prefixes.Clone())
c.prefixes = *(n.prefixes.Copy())

// deep copy if V implements Cloner[V]
for i, v := range c.prefixes.Items {
c.prefixes.Items[i] = cloneValue(v)
c.prefixes.Items[i] = cloneOrCopyValue(v)
}

// shallow
c.children = *(n.children.Clone())
c.children = *(n.children.Copy())

// deep copy of nodes and leaves
for i, k := range c.children.Items {
Expand Down
Loading

0 comments on commit 5261fc1

Please sign in to comment.