routing: optimize path finding structures
distance map now holds the edge the current path is coming from, removing the need for next map. Both distance map and distanceHeap now hold pointers instead of the full struct to reduce allocations and copies. Both these changes reduced path finding time by ~5% and memory usage by ~2mb.
This commit is contained in:
parent
fc36df0e60
commit
df70095ad0
@ -3,6 +3,7 @@ package routing
|
|||||||
import (
|
import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
)
|
)
|
||||||
@ -35,12 +36,15 @@ type nodeWithDist struct {
|
|||||||
// Includes the routing fees and a virtual cost factor to account for
|
// Includes the routing fees and a virtual cost factor to account for
|
||||||
// time locks.
|
// time locks.
|
||||||
weight int64
|
weight int64
|
||||||
|
|
||||||
|
// nextHop is the edge this route comes from.
|
||||||
|
nextHop *channeldb.ChannelEdgePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
// distanceHeap is a min-distance heap that's used within our path finding
|
// distanceHeap is a min-distance heap that's used within our path finding
|
||||||
// algorithm to keep track of the "closest" node to our source node.
|
// algorithm to keep track of the "closest" node to our source node.
|
||||||
type distanceHeap struct {
|
type distanceHeap struct {
|
||||||
nodes []nodeWithDist
|
nodes []*nodeWithDist
|
||||||
|
|
||||||
// pubkeyIndices maps public keys of nodes to their respective index in
|
// pubkeyIndices maps public keys of nodes to their respective index in
|
||||||
// the heap. This is used as a way to avoid db lookups by using heap.Fix
|
// the heap. This is used as a way to avoid db lookups by using heap.Fix
|
||||||
@ -53,7 +57,7 @@ type distanceHeap struct {
|
|||||||
func newDistanceHeap(numNodes int) distanceHeap {
|
func newDistanceHeap(numNodes int) distanceHeap {
|
||||||
distHeap := distanceHeap{
|
distHeap := distanceHeap{
|
||||||
pubkeyIndices: make(map[route.Vertex]int, numNodes),
|
pubkeyIndices: make(map[route.Vertex]int, numNodes),
|
||||||
nodes: make([]nodeWithDist, 0, numNodes),
|
nodes: make([]*nodeWithDist, 0, numNodes),
|
||||||
}
|
}
|
||||||
|
|
||||||
return distHeap
|
return distHeap
|
||||||
@ -85,7 +89,7 @@ func (d *distanceHeap) Swap(i, j int) {
|
|||||||
//
|
//
|
||||||
// NOTE: This is part of the heap.Interface implementation.
|
// NOTE: This is part of the heap.Interface implementation.
|
||||||
func (d *distanceHeap) Push(x interface{}) {
|
func (d *distanceHeap) Push(x interface{}) {
|
||||||
n := x.(nodeWithDist)
|
n := x.(*nodeWithDist)
|
||||||
d.nodes = append(d.nodes, n)
|
d.nodes = append(d.nodes, n)
|
||||||
d.pubkeyIndices[n.node] = len(d.nodes) - 1
|
d.pubkeyIndices[n.node] = len(d.nodes) - 1
|
||||||
}
|
}
|
||||||
@ -97,6 +101,7 @@ func (d *distanceHeap) Push(x interface{}) {
|
|||||||
func (d *distanceHeap) Pop() interface{} {
|
func (d *distanceHeap) Pop() interface{} {
|
||||||
n := len(d.nodes)
|
n := len(d.nodes)
|
||||||
x := d.nodes[n-1]
|
x := d.nodes[n-1]
|
||||||
|
d.nodes[n-1] = nil
|
||||||
d.nodes = d.nodes[0 : n-1]
|
d.nodes = d.nodes[0 : n-1]
|
||||||
delete(d.pubkeyIndices, x.node)
|
delete(d.pubkeyIndices, x.node)
|
||||||
return x
|
return x
|
||||||
@ -107,7 +112,7 @@ func (d *distanceHeap) Pop() interface{} {
|
|||||||
// modify its position and reorder the heap. If the vertex does not already
|
// modify its position and reorder the heap. If the vertex does not already
|
||||||
// exist in the heap, then it is pushed onto the heap. Otherwise, we will end
|
// exist in the heap, then it is pushed onto the heap. Otherwise, we will end
|
||||||
// up performing more db lookups on the same node in the pathfinding algorithm.
|
// up performing more db lookups on the same node in the pathfinding algorithm.
|
||||||
func (d *distanceHeap) PushOrFix(dist nodeWithDist) {
|
func (d *distanceHeap) PushOrFix(dist *nodeWithDist) {
|
||||||
index, ok := d.pubkeyIndices[dist.node]
|
index, ok := d.pubkeyIndices[dist.node]
|
||||||
if !ok {
|
if !ok {
|
||||||
heap.Push(d, dist)
|
heap.Push(d, dist)
|
||||||
|
@ -24,12 +24,12 @@ func TestHeapOrdering(t *testing.T) {
|
|||||||
// Create 100 random entries adding them to the heap created above, but
|
// Create 100 random entries adding them to the heap created above, but
|
||||||
// also a list that we'll sort with the entries.
|
// also a list that we'll sort with the entries.
|
||||||
const numEntries = 100
|
const numEntries = 100
|
||||||
sortedEntries := make([]nodeWithDist, 0, numEntries)
|
sortedEntries := make([]*nodeWithDist, 0, numEntries)
|
||||||
for i := 0; i < numEntries; i++ {
|
for i := 0; i < numEntries; i++ {
|
||||||
var pubKey [33]byte
|
var pubKey [33]byte
|
||||||
prand.Read(pubKey[:])
|
prand.Read(pubKey[:])
|
||||||
|
|
||||||
entry := nodeWithDist{
|
entry := &nodeWithDist{
|
||||||
node: route.Vertex(pubKey),
|
node: route.Vertex(pubKey),
|
||||||
dist: prand.Int63(),
|
dist: prand.Int63(),
|
||||||
}
|
}
|
||||||
@ -55,9 +55,9 @@ func TestHeapOrdering(t *testing.T) {
|
|||||||
|
|
||||||
// One by one, pop of all the entries from the heap, they should come
|
// One by one, pop of all the entries from the heap, they should come
|
||||||
// out in sorted order.
|
// out in sorted order.
|
||||||
var poppedEntries []nodeWithDist
|
var poppedEntries []*nodeWithDist
|
||||||
for nodeHeap.Len() != 0 {
|
for nodeHeap.Len() != 0 {
|
||||||
e := heap.Pop(&nodeHeap).(nodeWithDist)
|
e := heap.Pop(&nodeHeap).(*nodeWithDist)
|
||||||
poppedEntries = append(poppedEntries, e)
|
poppedEntries = append(poppedEntries, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,12 +355,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
nodeHeap := newDistanceHeap(estimatedNodeCount)
|
nodeHeap := newDistanceHeap(estimatedNodeCount)
|
||||||
|
|
||||||
// Holds the current best distance for a given node.
|
// Holds the current best distance for a given node.
|
||||||
distance := make(map[route.Vertex]nodeWithDist, estimatedNodeCount)
|
distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
|
||||||
|
|
||||||
// We'll use this map as a series of "next" hop pointers. So to get
|
|
||||||
// from `Vertex` to the target node, we'll take the edge that it's
|
|
||||||
// mapped to within `next`.
|
|
||||||
next := make(map[route.Vertex]*channeldb.ChannelEdgePolicy, estimatedNodeCount)
|
|
||||||
|
|
||||||
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
|
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
|
||||||
for vertex, outgoingEdgePolicies := range g.additionalEdges {
|
for vertex, outgoingEdgePolicies := range g.additionalEdges {
|
||||||
@ -386,7 +381,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
// is the starting point of the graph traversal. We are searching
|
// is the starting point of the graph traversal. We are searching
|
||||||
// backwards to get the fees first time right and correctly match
|
// backwards to get the fees first time right and correctly match
|
||||||
// channel bandwidth.
|
// channel bandwidth.
|
||||||
distance[target] = nodeWithDist{
|
distance[target] = &nodeWithDist{
|
||||||
dist: 0,
|
dist: 0,
|
||||||
weight: 0,
|
weight: 0,
|
||||||
node: target,
|
node: target,
|
||||||
@ -552,18 +547,17 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
// better than the current best known distance to this node.
|
// better than the current best known distance to this node.
|
||||||
// The new better distance is recorded, and also our "next hop"
|
// The new better distance is recorded, and also our "next hop"
|
||||||
// map is populated with this edge.
|
// map is populated with this edge.
|
||||||
withDist := nodeWithDist{
|
withDist := &nodeWithDist{
|
||||||
dist: tempDist,
|
dist: tempDist,
|
||||||
weight: tempWeight,
|
weight: tempWeight,
|
||||||
node: fromVertex,
|
node: fromVertex,
|
||||||
amountToReceive: amountToReceive,
|
amountToReceive: amountToReceive,
|
||||||
incomingCltv: incomingCltv,
|
incomingCltv: incomingCltv,
|
||||||
probability: probability,
|
probability: probability,
|
||||||
|
nextHop: edge,
|
||||||
}
|
}
|
||||||
distance[fromVertex] = withDist
|
distance[fromVertex] = withDist
|
||||||
|
|
||||||
next[fromVertex] = edge
|
|
||||||
|
|
||||||
// Either push withDist onto the heap if the node
|
// Either push withDist onto the heap if the node
|
||||||
// represented by fromVertex is not already on the heap OR adjust
|
// represented by fromVertex is not already on the heap OR adjust
|
||||||
// its position within the heap via heap.Fix.
|
// its position within the heap via heap.Fix.
|
||||||
@ -582,7 +576,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
|
|
||||||
// Fetch the node within the smallest distance from our source
|
// Fetch the node within the smallest distance from our source
|
||||||
// from the heap.
|
// from the heap.
|
||||||
partialPath := heap.Pop(&nodeHeap).(nodeWithDist)
|
partialPath := heap.Pop(&nodeHeap).(*nodeWithDist)
|
||||||
pivot := partialPath.node
|
pivot := partialPath.node
|
||||||
|
|
||||||
// If we've reached our source (or we don't have any incoming
|
// If we've reached our source (or we don't have any incoming
|
||||||
@ -632,7 +626,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
|
|
||||||
// Check if this candidate node is better than what we
|
// Check if this candidate node is better than what we
|
||||||
// already have.
|
// already have.
|
||||||
processEdge(route.Vertex(chanSource), edgeBandwidth, inEdge, pivot)
|
processEdge(chanSource, edgeBandwidth, inEdge, pivot)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,26 +650,24 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the source node isn't found in the next hop map, then a path
|
// Use the distance map to unravel the forward path from source to
|
||||||
// doesn't exist, so we terminate in an error.
|
|
||||||
if _, ok := next[source]; !ok {
|
|
||||||
return nil, newErrf(ErrNoPathFound, "unable to find a path to "+
|
|
||||||
"destination")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the nextHop map to unravel the forward path from source to
|
|
||||||
// target.
|
// target.
|
||||||
var pathEdges []*channeldb.ChannelEdgePolicy
|
var pathEdges []*channeldb.ChannelEdgePolicy
|
||||||
currentNode := source
|
currentNode := source
|
||||||
for currentNode != target { // TODO(roasbeef): assumes no cycles
|
for currentNode != target { // TODO(roasbeef): assumes no cycles
|
||||||
// Determine the next hop forward using the next map.
|
// Determine the next hop forward using the next map.
|
||||||
nextNode := next[currentNode]
|
currentNodeWithDist, ok := distance[currentNode]
|
||||||
|
if !ok {
|
||||||
|
// If the node doesnt have a next hop it means we didn't find a path.
|
||||||
|
return nil, newErrf(ErrNoPathFound, "unable to find a "+
|
||||||
|
"path to destination")
|
||||||
|
}
|
||||||
|
|
||||||
// Add the next hop to the list of path edges.
|
// Add the next hop to the list of path edges.
|
||||||
pathEdges = append(pathEdges, nextNode)
|
pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
|
||||||
|
|
||||||
// Advance current node.
|
// Advance current node.
|
||||||
currentNode = route.Vertex(nextNode.Node.PubKeyBytes)
|
currentNode = currentNodeWithDist.nextHop.Node.PubKeyBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// The route is invalid if it spans more than 20 hops. The current
|
// The route is invalid if it spans more than 20 hops. The current
|
||||||
|
Loading…
Reference in New Issue
Block a user