pools with generics

This commit is contained in:
Annika Hannig 2024-01-16 12:22:57 +01:00
parent d6fa635377
commit 85c5d19d4f
9 changed files with 357 additions and 117 deletions

View File

@ -4,6 +4,7 @@ import (
"math"
"reflect"
"sync"
"unsafe"
"github.com/alice-lg/alice-lg/pkg/api"
)
@ -12,14 +13,14 @@ import (
// This works with large and standard communities. For extended
// communities, use the ExtCommunityPool.
type CommunitiesPool struct {
root *Node
root *Node[int, api.Community]
sync.RWMutex
}
// NewCommunitiesPool creates a new pool for a single BGP community
func NewCommunitiesPool() *CommunitiesPool {
return &CommunitiesPool{
root: NewNode(api.Community{}),
root: NewNode[int, api.Community](api.Community{}),
}
}
@ -28,9 +29,9 @@ func (p *CommunitiesPool) Acquire(c api.Community) api.Community {
p.Lock()
defer p.Unlock()
if len(c) == 0 {
return p.root.ptr.(api.Community) // root
return p.root.value
}
return p.root.traverse(c, c).(api.Community)
return p.root.traverse(c, c)
}
// Read a single bgp community
@ -38,20 +39,20 @@ func (p *CommunitiesPool) Read(c api.Community) api.Community {
p.RLock()
defer p.RUnlock()
if len(c) == 0 {
return p.root.ptr.(api.Community) // root
return p.root.value // root
}
v := p.root.read(c, c)
v := p.root.read(c)
if v == nil {
return nil
}
return v.(api.Community)
return v
}
// CommunitiesSetPool is for deduplicating a list of BGP communities
// (Large and default. The ext communities representation right now
// makes problems and need to be fixed. TODO.)
type CommunitiesSetPool struct {
root *Node
root *Node[unsafe.Pointer, []api.Community]
sync.Mutex
}
@ -59,7 +60,7 @@ type CommunitiesSetPool struct {
// of BGP communities.
func NewCommunitiesSetPool() *CommunitiesSetPool {
return &CommunitiesSetPool{
root: NewNode([]api.Community{}),
root: NewNode[unsafe.Pointer, []api.Community]([]api.Community{}),
}
}
@ -69,24 +70,30 @@ func (p *CommunitiesSetPool) Acquire(communities []api.Community) []api.Communit
defer p.Unlock()
// Make identification list by using the pointer address
// of the deduplicated community as ID
ids := make([]int, len(communities))
ids := make([]unsafe.Pointer, len(communities))
set := make([]api.Community, len(communities))
for i, comm := range communities {
commPtr := Communities.Acquire(comm)
addr := reflect.ValueOf(commPtr).UnsafePointer()
ids[i] = int(uintptr(addr))
ids[i] = reflect.ValueOf(commPtr).UnsafePointer()
set[i] = commPtr
}
if len(ids) == 0 {
return p.root.ptr.([]api.Community)
return p.root.value
}
return p.root.traverse(set, ids).([]api.Community)
return p.root.traverse(set, ids)
}
// NewExtCommunitiesSetPool creates a new pool for extended communities
func NewExtCommunitiesSetPool() *CommunitiesSetPool {
return &CommunitiesSetPool{
root: NewNode([]api.ExtCommunity{}),
// ExtCommunitiesSetPool is for deduplicating a list of ext. BGP communities
type ExtCommunitiesSetPool struct {
root *Node[unsafe.Pointer, []api.ExtCommunity]
sync.Mutex
}
// NewExtCommunitiesSetPool creates a new pool for lists
// of BGP communities.
func NewExtCommunitiesSetPool() *ExtCommunitiesSetPool {
return &ExtCommunitiesSetPool{
root: NewNode[unsafe.Pointer, []api.ExtCommunity]([]api.ExtCommunity{}),
}
}
@ -99,23 +106,22 @@ func extPrefixToInt(s string) int {
}
// AcquireExt a list of ext bgp communities
func (p *CommunitiesSetPool) AcquireExt(communities []api.ExtCommunity) []api.ExtCommunity {
func (p *ExtCommunitiesSetPool) Acquire(communities []api.ExtCommunity) []api.ExtCommunity {
p.Lock()
defer p.Unlock()
// Make identification list
ids := make([]int, len(communities))
ids := make([]unsafe.Pointer, len(communities))
for i, comm := range communities {
r := extPrefixToInt(comm[0].(string))
icomm := []int{r, comm[1].(int), comm[2].(int)}
// get community identifier
commPtr := ExtCommunities.Acquire(icomm)
addr := reflect.ValueOf(commPtr).UnsafePointer()
ids[i] = int(uintptr(addr))
ids[i] = reflect.ValueOf(commPtr).UnsafePointer()
}
if len(ids) == 0 {
return p.root.ptr.([]api.ExtCommunity)
return p.root.value
}
return p.root.traverse(communities, ids).([]api.ExtCommunity)
return p.root.traverse(communities, ids)
}

View File

@ -125,14 +125,14 @@ func TestAcquireExtCommunitiesSets(t *testing.T) {
c3 := []api.ExtCommunity{
{"ro", 6, 1},
{"rt", 6, 2},
{"rt", 1, 1},
{"xyz", 1, 1},
}
p := NewExtCommunitiesSetPool()
pc1 := p.AcquireExt(c1)
pc2 := p.AcquireExt(c2)
pc3 := p.AcquireExt(c3)
pc1 := p.Acquire(c1)
pc2 := p.Acquire(c2)
pc3 := p.Acquire(c3)
if fmt.Sprintf("%p", c1) == fmt.Sprintf("%p", c2) {
t.Error("expected c1 !== c2")

View File

@ -4,77 +4,19 @@ import (
"sync"
)
// Node is a node in the tree.
type Node struct {
children map[int]*Node
counter int
ptr interface{}
}
// NewNode creates a new tree node
func NewNode(ptr interface{}) *Node {
return &Node{
children: map[int]*Node{},
ptr: ptr,
}
}
// Traverse read only; returns nil if not found
func (n *Node) read(list interface{}, tail []int) interface{} {
value := tail[0]
tail = tail[1:]
// Seek for value in children
child, ok := n.children[value]
if !ok {
return nil
}
// Set list ptr if required
if len(tail) == 0 {
return child.ptr
}
return child.read(list, tail)
}
// Internally acquire list by traversing the tree and
// creating nodes if required.
func (n *Node) traverse(list interface{}, tail []int) interface{} {
value := tail[0]
tail = tail[1:]
// Seek for value in children
child, ok := n.children[value]
if !ok {
child = NewNode(nil)
n.children[value] = child
}
// Set list ptr if required
if len(tail) == 0 {
if child.ptr == nil {
child.ptr = list
}
return child.ptr
}
return child.traverse(list, tail)
}
// A IntListPool can be used to deduplicate
// lists of integers. Like an AS path or BGP communities.
//
// A Tree datastructure is used.
type IntListPool struct {
root *Node
root *Node[int, []int]
sync.Mutex
}
// NewIntListPool creates a new int list pool
func NewIntListPool() *IntListPool {
return &IntListPool{
root: NewNode([]int{}),
root: NewNode[int, []int]([]int{}),
}
}
@ -84,16 +26,16 @@ func (p *IntListPool) Acquire(list []int) []int {
defer p.Unlock()
if len(list) == 0 {
return p.root.ptr.([]int) // root
return p.root.value // root
}
return p.root.traverse(list, list).([]int)
return p.root.traverse(list, list)
}
// A StringListPool can be used for deduplicating lists
// of strings. (This is a variant of an int list, as string
// values are converted to int.
type StringListPool struct {
root *Node
root *Node[int, []string]
values map[string]int
head int
sync.Mutex
@ -104,14 +46,14 @@ func NewStringListPool() *StringListPool {
return &StringListPool{
head: 1,
values: map[string]int{},
root: NewNode([]string{}),
root: NewNode[int, []string]([]string{}),
}
}
// Acquire the string list pointer from the pool
func (p *StringListPool) Acquire(list []string) []string {
if len(list) == 0 {
return p.root.ptr.([]string) // root
return p.root.value
}
// Make idenfier list
@ -127,5 +69,5 @@ func (p *StringListPool) Acquire(list []string) []string {
id[i] = v
}
return p.root.traverse(list, id).([]string)
return p.root.traverse(list, id)
}

View File

@ -2,9 +2,7 @@ package pools
import (
"fmt"
"reflect"
"testing"
"unsafe"
)
func TestAcquireIntList(t *testing.T) {
@ -27,23 +25,6 @@ func TestAcquireIntList(t *testing.T) {
t.Log(fmt.Sprintf("Ptr: %p %p => %p %p", a, b, r1, r2))
}
func TestPtrIntList(t *testing.T) {
a := []int{23, 42, 1337, 65535, 1}
b := []int{23, 42, 1337, 65535, 1}
pa := unsafe.Pointer(&a[0])
pb := unsafe.Pointer(&b[0])
t.Log(fmt.Sprintf("Ptr: %p %p", a, b))
t.Log(fmt.Sprintf("P: %p %p", pa, pb))
ra := reflect.ValueOf(a).UnsafePointer()
rb := reflect.ValueOf(b).UnsafePointer()
t.Log(fmt.Sprintf("P: %x %x %v", ra, rb, ra == rb))
t.Log(fmt.Sprintf("P: %x %x %v", int(uintptr(ra)), rb, ra == rb))
}
func TestAcquireStringList(t *testing.T) {
q := []string{"foo", "bar", "bgp"}
w := []string{"foo", "bar", "bgp"}

311
pkg/pools/node.go Normal file
View File

@ -0,0 +1,311 @@
package pools
import (
"unsafe"
"github.com/alice-lg/alice-lg/pkg/api"
)
// NOTE: Yes, generics could be used here.
// This also looks like a pretty good use case for them.
// However, the performance penalty is too high.
//
// With: Refreshed routes of rs1.foo (BIRD2 IPv4) in 26.905169348s
// Without: Refreshed routes of rs1.foo (BIRD2 IPv4) in 46.760695927s
//
// So yeah. Copy and paste time!
type Node[T comparable, V any] struct {
children map[T]*Node[T, V] // map of children
value V
final bool
}
// NewNode creates a new tree node
func NewNode[T comparable, V any](value V) *Node[T, V] {
return &Node[T, V]{
children: map[T]*Node[T, V]{},
value: value,
final: false,
}
}
// traverse inserts a new node into the three if required
// or returns the object if it already exists.
func (n *Node[T, V]) traverse(value V, tail []T) V {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
var zero V
child = NewNode[T, V](zero)
n.children[id] = child
}
// Set obj if required
if len(tail) == 0 {
if !child.final {
child.value = value
child.final = true
}
return child.value
}
return child.traverse(value, tail)
}
// read returns the object if it exists or nil if not.
func (n *Node[T, V]) read(tail []T) V {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
var zero V
return zero
}
// Set obj if required
if len(tail) == 0 {
return child.value
}
return child.read(tail)
}
// IntListNode is a node with an integer as key and
// a list of integers as value.
type IntListNode struct {
children map[int]*IntListNode
value []int
}
// NewIntListNode creates a new int tree node
func NewIntListNode(value []int) *IntListNode {
return &IntListNode{
children: map[int]*IntListNode{},
value: value,
}
}
// IntList traverse inserts a new node into the three if required
// or returns the object if it already exists.
func (n *IntListNode) traverse(value []int, tail []int) []int {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
child = NewIntListNode(nil)
n.children[id] = child
}
// Set obj if required
if len(tail) == 0 {
if child.value == nil {
child.value = value
}
return child.value
}
return child.traverse(value, tail)
}
// read returns the object if it exists or nil if not.
func (n *IntListNode) read(tail []int) []int {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
return nil
}
// Set obj if required
if len(tail) == 0 {
return child.value
}
return child.read(tail)
}
// StringListNode is a node with an integer as key and
// a list of integers as value.
type StringListNode struct {
children map[int]*StringListNode
value []string
}
// NewStringListNode creates a new int tree node
func NewStringListNode(value []string) *StringListNode {
return &StringListNode{
children: map[int]*StringListNode{},
value: value,
}
}
// StringList traverse inserts a new node into the three if required
// or returns the object if it already exists.
func (n *StringListNode) traverse(value []string, tail []int) []string {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
child = NewStringListNode(nil)
n.children[id] = child
}
// Set obj if required
if len(tail) == 0 {
if child.value == nil {
child.value = value
}
return child.value
}
return child.traverse(value, tail)
}
// read returns the object if it exists or nil if not.
func (n *StringListNode) read(tail []int) []string {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
return nil
}
// Set obj if required
if len(tail) == 0 {
return child.value
}
return child.read(tail)
}
// CommunityNode is a node with an integer as key
type CommunityNode struct {
children map[int]*CommunityNode
value api.Community
}
// NewCommunityNode creates a new int tree node
func NewCommunityNode(value []int) *CommunityNode {
return &CommunityNode{
children: map[int]*CommunityNode{},
value: value,
}
}
// CommunityNode: traverse inserts a new node into the three if required
// or returns the object if it already exists.
func (n *CommunityNode) traverse(value api.Community, tail []int) api.Community {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
child = NewCommunityNode(nil)
n.children[id] = child
}
// Set obj if required
if len(tail) == 0 {
if child.value == nil {
child.value = value
}
return child.value
}
return child.traverse(value, tail)
}
// read returns the object if it exists or nil if not.
func (n *CommunityNode) read(tail []int) api.Community {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
return nil
}
// Set obj if required
if len(tail) == 0 {
return child.value
}
return child.read(tail)
}
// CommunityListNode is a node with an integer as key and
// a list of integers as value.
type CommunityListNode struct {
children map[unsafe.Pointer]*CommunityListNode
value []api.Community
}
// NewCommunityListNode creates a new int tree node
func NewCommunityListNode(value []api.Community) *CommunityListNode {
return &CommunityListNode{
children: map[unsafe.Pointer]*CommunityListNode{},
value: value,
}
}
// CommunityList traverse inserts a new node into the three if required
// or returns the object if it already exists.
func (n *CommunityListNode) traverse(
value []api.Community,
tail []unsafe.Pointer,
) []api.Community {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
child = NewCommunityListNode(nil)
n.children[id] = child
}
// Set obj if required
if len(tail) == 0 {
if child.value == nil {
child.value = value
}
return child.value
}
return child.traverse(value, tail)
}
// read returns the object if it exists or nil if not.
func (n *CommunityListNode) read(tail []unsafe.Pointer) []api.Community {
id := tail[0]
tail = tail[1:]
// Seek for identifier in children
child, ok := n.children[id]
if !ok {
return nil
}
// Set obj if required
if len(tail) == 0 {
return child.value
}
return child.read(tail)
}

View File

@ -47,7 +47,7 @@ var ExtCommunities *CommunitiesPool
var CommunitiesSets *CommunitiesSetPool
// ExtCommunitiesSets stores a list of extended communities
var ExtCommunitiesSets *CommunitiesSetPool
var ExtCommunitiesSets *ExtCommunitiesSetPool
// LargeCommunitiesSets store a list of large BGP communities
var LargeCommunitiesSets *CommunitiesSetPool

View File

@ -259,7 +259,7 @@ func parseRouteBgpInfo(data interface{}) *api.BGPInfo {
LocalPref: localPref,
Med: med,
Communities: pools.CommunitiesSets.Acquire(communities),
ExtCommunities: pools.ExtCommunitiesSets.AcquireExt(extCommunities),
ExtCommunities: pools.ExtCommunitiesSets.Acquire(extCommunities),
LargeCommunities: pools.LargeCommunitiesSets.Acquire(largeCommunities),
}
return bgp

View File

@ -159,7 +159,7 @@ func (gobgp *GoBGP) parsePathIntoRoute(
route.BGP.AsPath = pools.ASPaths.Acquire(route.BGP.AsPath)
route.BGP.Communities = pools.CommunitiesSets.Acquire(route.BGP.Communities)
route.BGP.ExtCommunities = pools.ExtCommunitiesSets.AcquireExt(route.BGP.ExtCommunities)
route.BGP.ExtCommunities = pools.ExtCommunitiesSets.Acquire(route.BGP.ExtCommunities)
route.BGP.LargeCommunities = pools.LargeCommunitiesSets.Acquire(route.BGP.LargeCommunities)
route.Metric = (route.BGP.LocalPref + route.BGP.Med)

View File

@ -184,7 +184,7 @@ func decodeRoute(details map[string]interface{}) (*api.Route, error) {
AsPath: pools.ASPaths.Acquire(asPath),
NextHop: pools.Gateways4.Acquire(trueNextHop),
Communities: pools.CommunitiesSets.Acquire(communities),
ExtCommunities: pools.ExtCommunitiesSets.AcquireExt(extendedCommunities),
ExtCommunities: pools.ExtCommunitiesSets.Acquire(extendedCommunities),
LargeCommunities: pools.LargeCommunitiesSets.Acquire(largeCommunities),
LocalPref: localPref,
}