Skip to content

Commit 1171bc1

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 8ba3e83 commit 1171bc1

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
@@ -968,33 +968,24 @@ impl_writeable_tlv_based!(RouteHintHop, {
968968
});
969969

970970
#[derive(Eq, PartialEq)]
971+
#[repr(align(64))] // Force the size to 64 bytes
971972
struct RouteGraphNode {
972973
node_id: NodeId,
973-
lowest_fee_to_node: u64,
974-
total_cltv_delta: u32,
974+
score: u64,
975975
// The maximum value a yet-to-be-constructed payment path might flow through this node.
976976
// This value is upper-bounded by us by:
977977
// - how much is needed for a path being constructed
978978
// - how much value can channels following this node (up to the destination) can contribute,
979979
// considering their capacity and fees
980980
value_contribution_msat: u64,
981-
/// The effective htlc_minimum_msat at this hop. If a later hop on the path had a higher HTLC
982-
/// minimum, we use it, plus the fees required at each earlier hop to meet it.
983-
path_htlc_minimum_msat: u64,
984-
/// All penalties incurred from this hop on the way to the destination, as calculated using
985-
/// channel scoring.
986-
path_penalty_msat: u64,
981+
total_cltv_delta: u32,
987982
/// The number of hops walked up to this node.
988983
path_length_to_node: u8,
989984
}
990985

991986
impl cmp::Ord for RouteGraphNode {
992987
fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering {
993-
let other_score = cmp::max(other.lowest_fee_to_node, other.path_htlc_minimum_msat)
994-
.saturating_add(other.path_penalty_msat);
995-
let self_score = cmp::max(self.lowest_fee_to_node, self.path_htlc_minimum_msat)
996-
.saturating_add(self.path_penalty_msat);
997-
other_score.cmp(&self_score).then_with(|| other.node_id.cmp(&self.node_id))
988+
other.score.cmp(&self.score).then_with(|| other.node_id.cmp(&self.node_id))
998989
}
999990
}
1000991

@@ -1004,6 +995,16 @@ impl cmp::PartialOrd for RouteGraphNode {
1004995
}
1005996
}
1006997

998+
// While RouteGraphNode can be laid out with fewer bytes, performance appears to be improved
999+
// substantially when it is laid out at exactly 64 bytes.
1000+
//
1001+
// Thus, we use `#[repr(C)]` on the struct to force a suboptimal layout and check that it stays 64
1002+
// bytes here.
1003+
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
1004+
const _GRAPH_NODE_SMALL: usize = 64 - core::mem::size_of::<RouteGraphNode>();
1005+
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
1006+
const _GRAPH_NODE_FIXED_SIZE: usize = core::mem::size_of::<RouteGraphNode>() - 64;
1007+
10071008
/// A wrapper around the various hop representations.
10081009
///
10091010
/// Can be used to examine the properties of a hop,
@@ -2120,15 +2121,6 @@ where L::Target: Logger {
21202121
score_params);
21212122
let path_penalty_msat = $next_hops_path_penalty_msat
21222123
.saturating_add(channel_penalty_msat);
2123-
let new_graph_node = RouteGraphNode {
2124-
node_id: src_node_id,
2125-
lowest_fee_to_node: total_fee_msat,
2126-
total_cltv_delta: hop_total_cltv_delta,
2127-
value_contribution_msat,
2128-
path_htlc_minimum_msat,
2129-
path_penalty_msat,
2130-
path_length_to_node,
2131-
};
21322124

21332125
// Update the way of reaching $candidate.source()
21342126
// with the given short_channel_id (from $candidate.target()),
@@ -2153,6 +2145,13 @@ where L::Target: Logger {
21532145
.saturating_add(path_penalty_msat);
21542146

21552147
if !old_entry.was_processed && new_cost < old_cost {
2148+
let new_graph_node = RouteGraphNode {
2149+
node_id: src_node_id,
2150+
score: cmp::max(total_fee_msat, path_htlc_minimum_msat).saturating_add(path_penalty_msat),
2151+
total_cltv_delta: hop_total_cltv_delta,
2152+
value_contribution_msat,
2153+
path_length_to_node,
2154+
};
21562155
targets.push(new_graph_node);
21572156
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
21582157
old_entry.hop_use_fee_msat = hop_use_fee_msat;
@@ -2226,18 +2225,26 @@ where L::Target: Logger {
22262225
// meaning how much will be paid in fees after this node (to the best of our knowledge).
22272226
// This data can later be helpful to optimize routing (pay lower fees).
22282227
macro_rules! add_entries_to_cheapest_to_target_node {
2229-
( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr,
2230-
$next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr,
2228+
( $node: expr, $node_id: expr, $next_hops_value_contribution: expr,
22312229
$next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => {
2230+
let fee_to_target_msat;
2231+
let next_hops_path_htlc_minimum_msat;
2232+
let next_hops_path_penalty_msat;
22322233
let skip_node = if let Some(elem) = dist.get_mut(&$node_id) {
22332234
let was_processed = elem.was_processed;
22342235
elem.was_processed = true;
2236+
fee_to_target_msat = elem.total_fee_msat;
2237+
next_hops_path_htlc_minimum_msat = elem.path_htlc_minimum_msat;
2238+
next_hops_path_penalty_msat = elem.path_penalty_msat;
22352239
was_processed
22362240
} else {
22372241
// Entries are added to dist in add_entry!() when there is a channel from a node.
22382242
// Because there are no channels from payee, it will not have a dist entry at this point.
22392243
// If we're processing any other node, it is always be the result of a channel from it.
22402244
debug_assert_eq!($node_id, maybe_dummy_payee_node_id);
2245+
fee_to_target_msat = 0;
2246+
next_hops_path_htlc_minimum_msat = 0;
2247+
next_hops_path_penalty_msat = 0;
22412248
false
22422249
};
22432250

@@ -2247,9 +2254,9 @@ where L::Target: Logger {
22472254
let candidate = CandidateRouteHop::FirstHop {
22482255
details, payer_node_id: &our_node_id,
22492256
};
2250-
add_entry!(&candidate, $fee_to_target_msat,
2257+
add_entry!(&candidate, fee_to_target_msat,
22512258
$next_hops_value_contribution,
2252-
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
2259+
next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
22532260
$next_hops_cltv_delta, $next_hops_path_length);
22542261
}
22552262
}
@@ -2272,10 +2279,10 @@ where L::Target: Logger {
22722279
short_channel_id: *chan_id,
22732280
};
22742281
add_entry!(&candidate,
2275-
$fee_to_target_msat,
2282+
fee_to_target_msat,
22762283
$next_hops_value_contribution,
2277-
$next_hops_path_htlc_minimum_msat,
2278-
$next_hops_path_penalty_msat,
2284+
next_hops_path_htlc_minimum_msat,
2285+
next_hops_path_penalty_msat,
22792286
$next_hops_cltv_delta, $next_hops_path_length);
22802287
}
22812288
}
@@ -2320,7 +2327,7 @@ where L::Target: Logger {
23202327
// If not, targets.pop() will not even let us enter the loop in step 2.
23212328
None => {},
23222329
Some(node) => {
2323-
add_entries_to_cheapest_to_target_node!(node, payee, 0, path_value_msat, 0, 0u64, 0, 0);
2330+
add_entries_to_cheapest_to_target_node!(node, payee, path_value_msat, 0, 0);
23242331
},
23252332
});
23262333

@@ -2527,7 +2534,7 @@ where L::Target: Logger {
25272534
// Both these cases (and other cases except reaching recommended_value_msat) mean that
25282535
// paths_collection will be stopped because found_new_path==false.
25292536
// This is not necessarily a routing failure.
2530-
'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() {
2537+
'path_construction: while let Some(RouteGraphNode { node_id, total_cltv_delta, mut value_contribution_msat, path_length_to_node, .. }) = targets.pop() {
25312538

25322539
// Since we're going payee-to-payer, hitting our node as a target means we should stop
25332540
// traversing the graph and arrange the path out of what we found.
@@ -2662,8 +2669,8 @@ where L::Target: Logger {
26622669
match network_nodes.get(&node_id) {
26632670
None => {},
26642671
Some(node) => {
2665-
add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node,
2666-
value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat,
2672+
add_entries_to_cheapest_to_target_node!(node, node_id,
2673+
value_contribution_msat,
26672674
total_cltv_delta, path_length_to_node);
26682675
},
26692676
}

0 commit comments

Comments
 (0)