Skip to content

Commit d4f56c1

Browse files
committed
Pre-calculate heap element scores (retaining RouteGraphNode size)
`RouteGraphNode` currently recalculates scores in its `Ord` implementation, wasting time while sorting the main Dijkstra's heap. Further, some time ago, when implementing the `htlc_maximum_msat` amount reduction while walking the graph, we added `PathBuildingHop::was_processed`, looking up the source node in `dist` each time we pop'ed an element off of the binary heap. As a result, we now have a reference to our `PathBuildingHop` when processing a best-node's channels, leading to several fields in `RouteGraphNode` being entirely redundant. Here we drop those fields, but add a pre-calculated score field, as well as force a suboptimal `RouteGraphNode` layout, retaining its existing 64 byte size. Without the suboptimal layout, performance is very mixed, but with it performance is mostly improved, by around 10% in most tests.
1 parent ef7f001 commit d4f56c1

File tree

1 file changed

+40
-33
lines changed

1 file changed

+40
-33
lines changed

lightning/src/routing/router.rs

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -964,33 +964,24 @@ impl_writeable_tlv_based!(RouteHintHop, {
964964
});
965965

966966
#[derive(Eq, PartialEq)]
967+
#[repr(C)] // Force our field ordering to keep the size 64 bytes
967968
struct RouteGraphNode {
968969
node_id: NodeId,
969-
lowest_fee_to_node: u64,
970-
total_cltv_delta: u32,
970+
score: u64,
971971
// The maximum value a yet-to-be-constructed payment path might flow through this node.
972972
// This value is upper-bounded by us by:
973973
// - how much is needed for a path being constructed
974974
// - how much value can channels following this node (up to the destination) can contribute,
975975
// considering their capacity and fees
976976
value_contribution_msat: u64,
977-
/// The effective htlc_minimum_msat at this hop. If a later hop on the path had a higher HTLC
978-
/// minimum, we use it, plus the fees required at each earlier hop to meet it.
979-
path_htlc_minimum_msat: u64,
980-
/// All penalties incurred from this hop on the way to the destination, as calculated using
981-
/// channel scoring.
982-
path_penalty_msat: u64,
977+
total_cltv_delta: u32,
983978
/// The number of hops walked up to this node.
984979
path_length_to_node: u8,
985980
}
986981

987982
impl cmp::Ord for RouteGraphNode {
988983
fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering {
989-
let other_score = cmp::max(other.lowest_fee_to_node, other.path_htlc_minimum_msat)
990-
.saturating_add(other.path_penalty_msat);
991-
let self_score = cmp::max(self.lowest_fee_to_node, self.path_htlc_minimum_msat)
992-
.saturating_add(self.path_penalty_msat);
993-
other_score.cmp(&self_score).then_with(|| other.node_id.cmp(&self.node_id))
984+
other.score.cmp(&self.score).then_with(|| other.node_id.cmp(&self.node_id))
994985
}
995986
}
996987

@@ -1000,6 +991,16 @@ impl cmp::PartialOrd for RouteGraphNode {
1000991
}
1001992
}
1002993

994+
// While RouteGraphNode can be laid out as 56 bytes, performance appears to be improved
995+
// substantially when it is laid out at exactly 64 bytes.
996+
//
997+
// Thus, we use `#[repr(C)]` on the struct to force a suboptimal layout and check that it stays 64
998+
// bytes here.
999+
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
1000+
const _GRAPH_NODE_SMALL: usize = 64 - core::mem::size_of::<RouteGraphNode>();
1001+
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
1002+
const _GRAPH_NODE_FIXED_SIZE: usize = core::mem::size_of::<RouteGraphNode>() - 64;
1003+
10031004
/// A wrapper around the various hop representations.
10041005
///
10051006
/// Can be used to examine the properties of a hop,
@@ -2117,15 +2118,6 @@ where L::Target: Logger {
21172118
score_params);
21182119
let path_penalty_msat = $next_hops_path_penalty_msat
21192120
.saturating_add(channel_penalty_msat);
2120-
let new_graph_node = RouteGraphNode {
2121-
node_id: src_node_id,
2122-
lowest_fee_to_node: total_fee_msat,
2123-
total_cltv_delta: hop_total_cltv_delta,
2124-
value_contribution_msat,
2125-
path_htlc_minimum_msat,
2126-
path_penalty_msat,
2127-
path_length_to_node,
2128-
};
21292121

21302122
// Update the way of reaching $candidate.source()
21312123
// with the given short_channel_id (from $candidate.target()),
@@ -2150,6 +2142,13 @@ where L::Target: Logger {
21502142
.saturating_add(path_penalty_msat);
21512143

21522144
if !old_entry.was_processed && new_cost < old_cost {
2145+
let new_graph_node = RouteGraphNode {
2146+
node_id: src_node_id,
2147+
score: cmp::max(total_fee_msat, path_htlc_minimum_msat).saturating_add(path_penalty_msat),
2148+
total_cltv_delta: hop_total_cltv_delta,
2149+
value_contribution_msat,
2150+
path_length_to_node,
2151+
};
21532152
targets.push(new_graph_node);
21542153
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
21552154
old_entry.hop_use_fee_msat = hop_use_fee_msat;
@@ -2223,18 +2222,26 @@ where L::Target: Logger {
22232222
// meaning how much will be paid in fees after this node (to the best of our knowledge).
22242223
// This data can later be helpful to optimize routing (pay lower fees).
22252224
macro_rules! add_entries_to_cheapest_to_target_node {
2226-
( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr,
2227-
$next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr,
2225+
( $node: expr, $node_id: expr, $next_hops_value_contribution: expr,
22282226
$next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => {
2227+
let fee_to_target_msat;
2228+
let next_hops_path_htlc_minimum_msat;
2229+
let next_hops_path_penalty_msat;
22292230
let skip_node = if let Some(elem) = dist.get_mut(&$node_id) {
22302231
let was_processed = elem.was_processed;
22312232
elem.was_processed = true;
2233+
fee_to_target_msat = elem.total_fee_msat;
2234+
next_hops_path_htlc_minimum_msat = elem.path_htlc_minimum_msat;
2235+
next_hops_path_penalty_msat = elem.path_penalty_msat;
22322236
was_processed
22332237
} else {
22342238
// Entries are added to dist in add_entry!() when there is a channel from a node.
22352239
// Because there are no channels from payee, it will not have a dist entry at this point.
22362240
// If we're processing any other node, it is always be the result of a channel from it.
22372241
debug_assert_eq!($node_id, maybe_dummy_payee_node_id);
2242+
fee_to_target_msat = 0;
2243+
next_hops_path_htlc_minimum_msat = 0;
2244+
next_hops_path_penalty_msat = 0;
22382245
false
22392246
};
22402247

@@ -2244,9 +2251,9 @@ where L::Target: Logger {
22442251
let candidate = CandidateRouteHop::FirstHop {
22452252
details, payer_node_id: &our_node_id,
22462253
};
2247-
add_entry!(&candidate, $fee_to_target_msat,
2254+
add_entry!(&candidate, fee_to_target_msat,
22482255
$next_hops_value_contribution,
2249-
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
2256+
next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
22502257
$next_hops_cltv_delta, $next_hops_path_length);
22512258
}
22522259
}
@@ -2269,10 +2276,10 @@ where L::Target: Logger {
22692276
short_channel_id: *chan_id,
22702277
};
22712278
add_entry!(&candidate,
2272-
$fee_to_target_msat,
2279+
fee_to_target_msat,
22732280
$next_hops_value_contribution,
2274-
$next_hops_path_htlc_minimum_msat,
2275-
$next_hops_path_penalty_msat,
2281+
next_hops_path_htlc_minimum_msat,
2282+
next_hops_path_penalty_msat,
22762283
$next_hops_cltv_delta, $next_hops_path_length);
22772284
}
22782285
}
@@ -2317,7 +2324,7 @@ where L::Target: Logger {
23172324
// If not, targets.pop() will not even let us enter the loop in step 2.
23182325
None => {},
23192326
Some(node) => {
2320-
add_entries_to_cheapest_to_target_node!(node, payee, 0, path_value_msat, 0, 0u64, 0, 0);
2327+
add_entries_to_cheapest_to_target_node!(node, payee, path_value_msat, 0, 0);
23212328
},
23222329
});
23232330

@@ -2524,7 +2531,7 @@ where L::Target: Logger {
25242531
// Both these cases (and other cases except reaching recommended_value_msat) mean that
25252532
// paths_collection will be stopped because found_new_path==false.
25262533
// This is not necessarily a routing failure.
2527-
'path_construction: while let Some(RouteGraphNode { node_id, lowest_fee_to_node, total_cltv_delta, mut value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, path_length_to_node, .. }) = targets.pop() {
2534+
'path_construction: while let Some(RouteGraphNode { node_id, total_cltv_delta, mut value_contribution_msat, path_length_to_node, .. }) = targets.pop() {
25282535

25292536
// Since we're going payee-to-payer, hitting our node as a target means we should stop
25302537
// traversing the graph and arrange the path out of what we found.
@@ -2659,8 +2666,8 @@ where L::Target: Logger {
26592666
match network_nodes.get(&node_id) {
26602667
None => {},
26612668
Some(node) => {
2662-
add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node,
2663-
value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat,
2669+
add_entries_to_cheapest_to_target_node!(node, node_id,
2670+
value_contribution_msat,
26642671
total_cltv_delta, path_length_to_node);
26652672
},
26662673
}

0 commit comments

Comments
 (0)