Skip to content

Commit ac1463b

Browse files
authored
Merge pull request #2803 from TheBlueMatt/2023-12-routing-dist-vec
Misc routing optimization
2 parents 6ed398d + f689e01 commit ac1463b

File tree

3 files changed

+139
-50
lines changed

3 files changed

+139
-50
lines changed

lightning/src/ln/features.rs

+17-9
Original file line numberDiff line numberDiff line change
@@ -801,15 +801,23 @@ impl<T: sealed::Context> Features<T> {
801801
pub fn requires_unknown_bits(&self) -> bool {
802802
// Bitwise AND-ing with all even bits set except for known features will select required
803803
// unknown features.
804-
let byte_count = T::KNOWN_FEATURE_MASK.len();
805-
self.flags.iter().enumerate().any(|(i, &byte)| {
806-
let unknown_features = if i < byte_count {
807-
!T::KNOWN_FEATURE_MASK[i]
808-
} else {
809-
0b11_11_11_11
810-
};
811-
(byte & (ANY_REQUIRED_FEATURES_MASK & unknown_features)) != 0
812-
})
804+
let mut known_chunks = T::KNOWN_FEATURE_MASK.chunks(8);
805+
for chunk in self.flags.chunks(8) {
806+
let mut flag_bytes = [0; 8];
807+
flag_bytes[..chunk.len()].copy_from_slice(&chunk);
808+
let flag_int = u64::from_le_bytes(flag_bytes);
809+
810+
let known_chunk = known_chunks.next().unwrap_or(&[0; 0]);
811+
let mut known_bytes = [0; 8];
812+
known_bytes[..known_chunk.len()].copy_from_slice(&known_chunk);
813+
let known_int = u64::from_le_bytes(known_bytes);
814+
815+
const REQ_MASK: u64 = u64::from_le_bytes([ANY_REQUIRED_FEATURES_MASK; 8]);
816+
if flag_int & (REQ_MASK & !known_int) != 0 {
817+
return true;
818+
}
819+
}
820+
false
813821
}
814822

815823
pub(crate) fn supports_unknown_bits(&self) -> bool {

lightning/src/routing/gossip.rs

+57-21
Original file line numberDiff line numberDiff line change
@@ -795,22 +795,32 @@ where
795795
}
796796
}
797797

798+
// Fetching values from this struct is very performance sensitive during routefinding. Thus, we
799+
// want to ensure that all of the fields we care about (all of them except `last_update_message`)
800+
// sit on the same cache line.
801+
//
802+
// We do this by using `repr(C)`, which forces the struct to be laid out in memory the way we write
803+
// it (ensuring `last_update_message` hangs off the end and no fields are reordered after it), and
804+
// `align(32)`, ensuring the struct starts either at the start, or in the middle, of a 64b x86-64
805+
// cache line. This ensures the beginning fields (which are 31 bytes) all sit in the same cache
806+
// line.
807+
#[repr(C, align(32))]
798808
#[derive(Clone, Debug, PartialEq, Eq)]
799809
/// Details about one direction of a channel as received within a [`ChannelUpdate`].
800810
pub struct ChannelUpdateInfo {
801-
/// When the last update to the channel direction was issued.
802-
/// Value is opaque, as set in the announcement.
803-
pub last_update: u32,
804-
/// Whether the channel can be currently used for payments (in this one direction).
805-
pub enabled: bool,
806-
/// The difference in CLTV values that you must have when routing through this channel.
807-
pub cltv_expiry_delta: u16,
808811
/// The minimum value, which must be relayed to the next hop via the channel
809812
pub htlc_minimum_msat: u64,
810813
/// The maximum value which may be relayed to the next hop via the channel.
811814
pub htlc_maximum_msat: u64,
812815
/// Fees charged when the channel is used for routing
813816
pub fees: RoutingFees,
817+
/// When the last update to the channel direction was issued.
818+
/// Value is opaque, as set in the announcement.
819+
pub last_update: u32,
820+
/// The difference in CLTV values that you must have when routing through this channel.
821+
pub cltv_expiry_delta: u16,
822+
/// Whether the channel can be currently used for payments (in this one direction).
823+
pub enabled: bool,
814824
/// Most recent update for the channel received from the network
815825
/// Mostly redundant with the data we store in fields explicitly.
816826
/// Everything else is useful only for sending out for initial routing sync.
@@ -878,22 +888,46 @@ impl Readable for ChannelUpdateInfo {
878888
}
879889
}
880890

891+
// Fetching values from this struct is very performance sensitive during routefinding. Thus, we
892+
// want to ensure that all of the fields we care about (all of them except `last_update_message`
893+
// and `announcement_received_time`) sit on the same cache line.
894+
//
895+
// Sadly, this is not possible, however we can still do okay - all of the fields before
896+
// `one_to_two` and `two_to_one` are just under 128 bytes long, so we can ensure they sit on
897+
// adjacent cache lines (which are generally fetched together in x86_64 processors).
898+
//
899+
// This leaves only the two directional channel info structs on separate cache lines.
900+
//
901+
// We accomplish this using `repr(C)`, which forces the struct to be laid out in memory the way we
902+
// write it (ensuring the fields we care about are at the start of the struct) and `align(128)`,
903+
// ensuring the struct starts at the beginning of two adjacent 64b x86-64 cache lines.
904+
#[repr(align(128), C)]
881905
#[derive(Clone, Debug, Eq)]
882906
/// Details about a channel (both directions).
883907
/// Received within a channel announcement.
884908
pub struct ChannelInfo {
885909
/// Protocol features of a channel communicated during its announcement
886910
pub features: ChannelFeatures,
911+
887912
/// Source node of the first direction of a channel
888913
pub node_one: NodeId,
889-
/// Details about the first direction of a channel
890-
pub one_to_two: Option<ChannelUpdateInfo>,
914+
891915
/// Source node of the second direction of a channel
892916
pub node_two: NodeId,
893-
/// Details about the second direction of a channel
894-
pub two_to_one: Option<ChannelUpdateInfo>,
917+
918+
/// The [`NodeInfo::node_counter`] of the node pointed to by [`Self::node_one`].
919+
pub(crate) node_one_counter: u32,
920+
/// The [`NodeInfo::node_counter`] of the node pointed to by [`Self::node_two`].
921+
pub(crate) node_two_counter: u32,
922+
895923
/// The channel capacity as seen on-chain, if chain lookup is available.
896924
pub capacity_sats: Option<u64>,
925+
926+
/// Details about the first direction of a channel
927+
pub one_to_two: Option<ChannelUpdateInfo>,
928+
/// Details about the second direction of a channel
929+
pub two_to_one: Option<ChannelUpdateInfo>,
930+
897931
/// An initial announcement of the channel
898932
/// Mostly redundant with the data we store in fields explicitly.
899933
/// Everything else is useful only for sending out for initial routing sync.
@@ -903,11 +937,6 @@ pub struct ChannelInfo {
903937
/// (which we can probably assume we are - no-std environments probably won't have a full
904938
/// network graph in memory!).
905939
announcement_received_time: u64,
906-
907-
/// The [`NodeInfo::node_counter`] of the node pointed to by [`Self::node_one`].
908-
pub(crate) node_one_counter: u32,
909-
/// The [`NodeInfo::node_counter`] of the node pointed to by [`Self::node_two`].
910-
pub(crate) node_two_counter: u32,
911940
}
912941

913942
impl PartialEq for ChannelInfo {
@@ -1053,6 +1082,8 @@ impl Readable for ChannelInfo {
10531082
pub struct DirectedChannelInfo<'a> {
10541083
channel: &'a ChannelInfo,
10551084
direction: &'a ChannelUpdateInfo,
1085+
source_counter: u32,
1086+
target_counter: u32,
10561087
/// The direction this channel is in - if set, it indicates that we're traversing the channel
10571088
/// from [`ChannelInfo::node_one`] to [`ChannelInfo::node_two`].
10581089
from_node_one: bool,
@@ -1061,7 +1092,12 @@ pub struct DirectedChannelInfo<'a> {
10611092
impl<'a> DirectedChannelInfo<'a> {
10621093
#[inline]
10631094
fn new(channel: &'a ChannelInfo, direction: &'a ChannelUpdateInfo, from_node_one: bool) -> Self {
1064-
Self { channel, direction, from_node_one }
1095+
let (source_counter, target_counter) = if from_node_one {
1096+
(channel.node_one_counter, channel.node_two_counter)
1097+
} else {
1098+
(channel.node_two_counter, channel.node_one_counter)
1099+
};
1100+
Self { channel, direction, from_node_one, source_counter, target_counter }
10651101
}
10661102

10671103
/// Returns information for the channel.
@@ -1104,12 +1140,12 @@ impl<'a> DirectedChannelInfo<'a> {
11041140
pub fn target(&self) -> &'a NodeId { if self.from_node_one { &self.channel.node_two } else { &self.channel.node_one } }
11051141

11061142
/// Returns the source node's counter
1107-
#[inline]
1108-
pub(super) fn source_counter(&self) -> u32 { if self.from_node_one { self.channel.node_one_counter } else { self.channel.node_two_counter } }
1143+
#[inline(always)]
1144+
pub(super) fn source_counter(&self) -> u32 { self.source_counter }
11091145

11101146
/// Returns the target node's counter
1111-
#[inline]
1112-
pub(super) fn target_counter(&self) -> u32 { if self.from_node_one { self.channel.node_two_counter } else { self.channel.node_one_counter } }
1147+
#[inline(always)]
1148+
pub(super) fn target_counter(&self) -> u32 { self.target_counter }
11131149
}
11141150

11151151
impl<'a> fmt::Debug for DirectedChannelInfo<'a> {

lightning/src/routing/router.rs

+65-20
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ impl<'a> CandidateRouteHop<'a> {
14371437
}
14381438
}
14391439

1440-
#[inline]
1440+
#[inline(always)]
14411441
fn src_node_counter(&self) -> u32 {
14421442
match self {
14431443
CandidateRouteHop::FirstHop(hop) => hop.payer_node_counter,
@@ -1772,6 +1772,14 @@ struct PathBuildingHop<'a> {
17721772
/// decrease as well. Thus, we have to explicitly track which nodes have been processed and
17731773
/// avoid processing them again.
17741774
was_processed: bool,
1775+
/// When processing a node as the next best-score candidate, we want to quickly check if it is
1776+
/// a direct counterparty of ours, using our local channel information immediately if we can.
1777+
///
1778+
/// In order to do so efficiently, we cache whether a node is a direct counterparty here at the
1779+
/// start of a route-finding pass. Unlike all other fields in this struct, this field is never
1780+
/// updated after being initialized - it is set at the start of a route-finding pass and only
1781+
/// read thereafter.
1782+
is_first_hop_target: bool,
17751783
/// Used to compare channels when choosing the for routing.
17761784
/// Includes paying for the use of a hop and the following hops, as well as
17771785
/// an estimated cost of reaching this hop.
@@ -1810,6 +1818,7 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
18101818
.field("source_node_id", &self.candidate.source())
18111819
.field("target_node_id", &self.candidate.target())
18121820
.field("short_channel_id", &self.candidate.short_channel_id())
1821+
.field("is_first_hop_target", &self.is_first_hop_target)
18131822
.field("total_fee_msat", &self.total_fee_msat)
18141823
.field("next_hops_fee_msat", &self.next_hops_fee_msat)
18151824
.field("hop_use_fee_msat", &self.hop_use_fee_msat)
@@ -2383,6 +2392,8 @@ where L::Target: Logger {
23832392
// if the amount being transferred over this path is lower.
23842393
// We do this for now, but this is a subject for removal.
23852394
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
2395+
let cltv_expiry_delta = $candidate.cltv_expiry_delta();
2396+
let htlc_minimum_msat = $candidate.htlc_minimum_msat();
23862397
let used_liquidity_msat = used_liquidities
23872398
.get(&$candidate.id())
23882399
.map_or(0, |used_liquidity_msat| {
@@ -2406,7 +2417,7 @@ where L::Target: Logger {
24062417
.checked_sub(2*MEDIAN_HOP_CLTV_EXPIRY_DELTA)
24072418
.unwrap_or(payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta);
24082419
let hop_total_cltv_delta = ($next_hops_cltv_delta as u32)
2409-
.saturating_add($candidate.cltv_expiry_delta());
2420+
.saturating_add(cltv_expiry_delta);
24102421
let exceeds_cltv_delta_limit = hop_total_cltv_delta > max_total_cltv_expiry_delta;
24112422

24122423
let value_contribution_msat = cmp::min(available_value_contribution_msat, $next_hops_value_contribution);
@@ -2417,13 +2428,13 @@ where L::Target: Logger {
24172428
None => unreachable!(),
24182429
};
24192430
#[allow(unused_comparisons)] // $next_hops_path_htlc_minimum_msat is 0 in some calls so rustc complains
2420-
let over_path_minimum_msat = amount_to_transfer_over_msat >= $candidate.htlc_minimum_msat() &&
2431+
let over_path_minimum_msat = amount_to_transfer_over_msat >= htlc_minimum_msat &&
24212432
amount_to_transfer_over_msat >= $next_hops_path_htlc_minimum_msat;
24222433

24232434
#[allow(unused_comparisons)] // $next_hops_path_htlc_minimum_msat is 0 in some calls so rustc complains
24242435
let may_overpay_to_meet_path_minimum_msat =
2425-
((amount_to_transfer_over_msat < $candidate.htlc_minimum_msat() &&
2426-
recommended_value_msat >= $candidate.htlc_minimum_msat()) ||
2436+
((amount_to_transfer_over_msat < htlc_minimum_msat &&
2437+
recommended_value_msat >= htlc_minimum_msat) ||
24272438
(amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat &&
24282439
recommended_value_msat >= $next_hops_path_htlc_minimum_msat));
24292440

@@ -2493,12 +2504,14 @@ where L::Target: Logger {
24932504
// payment path (upstream to the payee). To avoid that, we recompute
24942505
// path fees knowing the final path contribution after constructing it.
24952506
let curr_min = cmp::max(
2496-
$next_hops_path_htlc_minimum_msat, $candidate.htlc_minimum_msat()
2507+
$next_hops_path_htlc_minimum_msat, htlc_minimum_msat
24972508
);
2498-
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
2509+
let candidate_fees = $candidate.fees();
2510+
let src_node_counter = $candidate.src_node_counter();
2511+
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, candidate_fees)
24992512
.saturating_add(curr_min);
25002513

2501-
let dist_entry = &mut dist[$candidate.src_node_counter() as usize];
2514+
let dist_entry = &mut dist[src_node_counter as usize];
25022515
let old_entry = if let Some(hop) = dist_entry {
25032516
hop
25042517
} else {
@@ -2516,6 +2529,7 @@ where L::Target: Logger {
25162529
path_htlc_minimum_msat,
25172530
path_penalty_msat: u64::max_value(),
25182531
was_processed: false,
2532+
is_first_hop_target: false,
25192533
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
25202534
value_contribution_msat,
25212535
});
@@ -2540,7 +2554,7 @@ where L::Target: Logger {
25402554
if src_node_id != our_node_id {
25412555
// Note that `u64::max_value` means we'll always fail the
25422556
// `old_entry.total_fee_msat > total_fee_msat` check below
2543-
hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, $candidate.fees());
2557+
hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, candidate_fees);
25442558
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
25452559
}
25462560

@@ -2679,12 +2693,14 @@ where L::Target: Logger {
26792693
let fee_to_target_msat;
26802694
let next_hops_path_htlc_minimum_msat;
26812695
let next_hops_path_penalty_msat;
2696+
let is_first_hop_target;
26822697
let skip_node = if let Some(elem) = &mut dist[$node.node_counter as usize] {
26832698
let was_processed = elem.was_processed;
26842699
elem.was_processed = true;
26852700
fee_to_target_msat = elem.total_fee_msat;
26862701
next_hops_path_htlc_minimum_msat = elem.path_htlc_minimum_msat;
26872702
next_hops_path_penalty_msat = elem.path_penalty_msat;
2703+
is_first_hop_target = elem.is_first_hop_target;
26882704
was_processed
26892705
} else {
26902706
// Entries are added to dist in add_entry!() when there is a channel from a node.
@@ -2695,21 +2711,24 @@ where L::Target: Logger {
26952711
fee_to_target_msat = 0;
26962712
next_hops_path_htlc_minimum_msat = 0;
26972713
next_hops_path_penalty_msat = 0;
2714+
is_first_hop_target = false;
26982715
false
26992716
};
27002717

27012718
if !skip_node {
2702-
if let Some((first_channels, peer_node_counter)) = first_hop_targets.get(&$node_id) {
2703-
for details in first_channels {
2704-
debug_assert_eq!(*peer_node_counter, $node.node_counter);
2705-
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
2706-
details, payer_node_id: &our_node_id, payer_node_counter,
2707-
target_node_counter: $node.node_counter,
2708-
});
2709-
add_entry!(&candidate, fee_to_target_msat,
2710-
$next_hops_value_contribution,
2711-
next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
2712-
$next_hops_cltv_delta, $next_hops_path_length);
2719+
if is_first_hop_target {
2720+
if let Some((first_channels, peer_node_counter)) = first_hop_targets.get(&$node_id) {
2721+
for details in first_channels {
2722+
debug_assert_eq!(*peer_node_counter, $node.node_counter);
2723+
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
2724+
details, payer_node_id: &our_node_id, payer_node_counter,
2725+
target_node_counter: $node.node_counter,
2726+
});
2727+
add_entry!(&candidate, fee_to_target_msat,
2728+
$next_hops_value_contribution,
2729+
next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
2730+
$next_hops_cltv_delta, $next_hops_path_length);
2731+
}
27132732
}
27142733
}
27152734

@@ -2756,6 +2775,32 @@ where L::Target: Logger {
27562775
for e in dist.iter_mut() {
27572776
*e = None;
27582777
}
2778+
for (_, (chans, peer_node_counter)) in first_hop_targets.iter() {
2779+
// In order to avoid looking up whether each node is a first-hop target, we store a
2780+
// dummy entry in dist for each first-hop target, allowing us to do this lookup for
2781+
// free since we're already looking at the `was_processed` flag.
2782+
//
2783+
// Note that all the fields (except `is_first_hop_target`) will be overwritten whenever
2784+
// we find a path to the target, so are left as dummies here.
2785+
dist[*peer_node_counter as usize] = Some(PathBuildingHop {
2786+
candidate: CandidateRouteHop::FirstHop(FirstHopCandidate {
2787+
details: &chans[0],
2788+
payer_node_id: &our_node_id,
2789+
target_node_counter: u32::max_value(),
2790+
payer_node_counter: u32::max_value(),
2791+
}),
2792+
fee_msat: 0,
2793+
next_hops_fee_msat: u64::max_value(),
2794+
hop_use_fee_msat: u64::max_value(),
2795+
total_fee_msat: u64::max_value(),
2796+
path_htlc_minimum_msat: u64::max_value(),
2797+
path_penalty_msat: u64::max_value(),
2798+
was_processed: false,
2799+
is_first_hop_target: true,
2800+
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
2801+
value_contribution_msat: 0,
2802+
});
2803+
}
27592804
hit_minimum_limit = false;
27602805

27612806
// If first hop is a private channel and the only way to reach the payee, this is the only

0 commit comments

Comments
 (0)