routing: replace linear vertex scan with distanceHeap usage

In this commit we now utilize the node distance heap that was added in
a prior commit into our core path finding logic. With this new data
structure, we no longer linearly scan the distance of all vertexes from
the source node when deciding which one to greedily explore.

Instead, we now start with the source added to our distance heap, then
new vertexes are progressively added to our heap as their edges are
explored. With this change, we move the computational complexity of our
path finding algorithm closer to the theoretical limit.
This commit is contained in:
Olaoluwa Osuntokun 2017-03-19 14:15:58 -07:00
parent 20aba8060f
commit b6199f27da
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -3,6 +3,8 @@ package routing
import ( import (
"math" "math"
"container/heap"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/chaincfg/chainhash"
@ -239,11 +241,11 @@ func edgeWeight(e *channeldb.ChannelEdgePolicy) float64 {
func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey, func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
amt btcutil.Amount) (*Route, error) { amt btcutil.Amount) (*Route, error) {
// First initialize empty list of all the node that we've yet to
// visited. // First we'll initilaze an empty heap which'll help us to quickly
// TODO(roasbeef): make into incremental fibonacci heap rather than // locate the next edge we should visit next during our graph
// loading all into memory. // traversal.
var unvisited []*channeldb.LightningNode var nodeHeap distanceHeap
// For each node/vertex the graph we create an entry in the distance // For each node/vertex the graph we create an entry in the distance
// map for the node set with a distance of "infinity". We also mark // map for the node set with a distance of "infinity". We also mark
@ -256,8 +258,6 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
dist: infinity, dist: infinity,
node: node, node: node,
} }
unvisited = append(unvisited, node)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
@ -276,39 +276,23 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
node: sourceNode, node: sourceNode,
} }
// To start, our source node will hte the sole item within our distance
// heap.
heap.Push(&nodeHeap, distance[sourceVertex])
// We'll use this map as a series of "previous" hop pointers. So to get // We'll use this map as a series of "previous" hop pointers. So to get
// to `vertex` we'll take the edge that it's mapped to within `prev`. // to `vertex` we'll take the edge that it's mapped to within `prev`.
prev := make(map[vertex]edgeWithPrev) prev := make(map[vertex]edgeWithPrev)
for len(unvisited) != 0 { for nodeHeap.Len() != 0 {
var bestNode *channeldb.LightningNode // Fetch the node within the smallest distance from our source
smallestDist := infinity // from the heap.
bestNode := heap.Pop(&nodeHeap).(nodeWithDist).node
// First we examine our list of unvisited nodes, for the most
// optimal vertex to examine next.
for i, node := range unvisited {
// The "best" node to visit next is node with the
// smallest distance from the source of all the
// unvisited nodes.
v := newVertex(node.PubKey)
if nodeInfo := distance[v]; nodeInfo.dist < smallestDist {
smallestDist = nodeInfo.dist
bestNode = nodeInfo.node
// Since we're going to visit this node, we can
// remove it from the set of unvisited nodes.
copy(unvisited[i:], unvisited[i+1:])
unvisited[len(unvisited)-1] = nil // Avoid GC leak.
unvisited = unvisited[:len(unvisited)-1]
break
}
}
// If we've reached our target (or we don't have any outgoing // If we've reached our target (or we don't have any outgoing
// edges), then we're done here and can exit the graph // edges), then we're done here and can exit the graph
// traversal early. // traversal early.
if bestNode == nil || bestNode.PubKey.IsEqual(target) { if bestNode.PubKey.IsEqual(target) {
break break
} }
@ -327,9 +311,9 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
// If this new tentative distance is better than the // If this new tentative distance is better than the
// current best known distance to this node, then we // current best known distance to this node, then we
// record the new better distance, and also populate // record the new better distance, and also populate
// our "next hop" map with this edge. // our "next hop" map with this edge. We'll also shave
// TODO(roasbeef): add capacity to relaxation criteria? // off irrelevant edges by adding the sufficient
// * also add min payment? // capacity of an edge to our relaxation condition.
v := newVertex(edge.Node.PubKey) v := newVertex(edge.Node.PubKey)
if tempDist < distance[v].dist && if tempDist < distance[v].dist &&
edgeInfo.Capacity >= amt { edgeInfo.Capacity >= amt {
@ -345,6 +329,10 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
}, },
prevNode: bestNode.PubKey, prevNode: bestNode.PubKey,
} }
// Add this new node to our heap as we'd like
// to further explore down this edge.
heap.Push(&nodeHeap, distance[v])
} }
return nil return nil
}) })