Skip to content

Commit bc7e926

Browse files
committed
Store Feature flags in line rather than on the heap by default
In a running LDK instance we generally have a ton of `Feature`s objects flying around, including ones per channel/peer in the `NetworkGraph`, ones per channel/peer in `Channel`/`ChannelManager`, and ones inside of BOLT 11/12 Invoices. Thus, its useful to avoid unecessary allocations in them to reduce heap fragmentation. Luckily, Features generally don't have more than 15 bytes worth of flags, and because `Vec` has a `NonNull` pointer we can actually wrap a vec in a two-variant enum with zero additional space penalty. While this patch leaves actually deserializing `Features` without allocating as a future exercise, immediately releasing the allocation is much better than holding it, and `Features` constructed through repeated `set_*` calls benefit from avoiding the Vec entirely.
1 parent 85185d8 commit bc7e926

File tree

2 files changed

+141
-21
lines changed

2 files changed

+141
-21
lines changed

lightning-types/src/features.rs

+134-14
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,13 @@
9090
use core::borrow::Borrow;
9191
use core::hash::{Hash, Hasher};
9292
use core::marker::PhantomData;
93+
use core::ops::{Deref, DerefMut};
9394
use core::{cmp, fmt};
9495

9596
use alloc::vec::Vec;
9697

9798
mod sealed {
98-
use super::Features;
99-
100-
use alloc::vec::Vec;
99+
use super::{FeatureFlags, Features};
101100

102101
/// The context in which [`Features`] are applicable. Defines which features are known to the
103102
/// implementation, though specification of them as required or optional is up to the code
@@ -297,21 +296,21 @@ mod sealed {
297296

298297
/// Returns whether the feature is required by the given flags.
299298
#[inline]
300-
fn requires_feature(flags: &Vec<u8>) -> bool {
299+
fn requires_feature(flags: &[u8]) -> bool {
301300
flags.len() > Self::BYTE_OFFSET &&
302301
(flags[Self::BYTE_OFFSET] & Self::REQUIRED_MASK) != 0
303302
}
304303

305304
/// Returns whether the feature is supported by the given flags.
306305
#[inline]
307-
fn supports_feature(flags: &Vec<u8>) -> bool {
306+
fn supports_feature(flags: &[u8]) -> bool {
308307
flags.len() > Self::BYTE_OFFSET &&
309308
(flags[Self::BYTE_OFFSET] & (Self::REQUIRED_MASK | Self::OPTIONAL_MASK)) != 0
310309
}
311310

312311
/// Sets the feature's required (even) bit in the given flags.
313312
#[inline]
314-
fn set_required_bit(flags: &mut Vec<u8>) {
313+
fn set_required_bit(flags: &mut FeatureFlags) {
315314
if flags.len() <= Self::BYTE_OFFSET {
316315
flags.resize(Self::BYTE_OFFSET + 1, 0u8);
317316
}
@@ -322,7 +321,7 @@ mod sealed {
322321

323322
/// Sets the feature's optional (odd) bit in the given flags.
324323
#[inline]
325-
fn set_optional_bit(flags: &mut Vec<u8>) {
324+
fn set_optional_bit(flags: &mut FeatureFlags) {
326325
if flags.len() <= Self::BYTE_OFFSET {
327326
flags.resize(Self::BYTE_OFFSET + 1, 0u8);
328327
}
@@ -333,7 +332,7 @@ mod sealed {
333332
/// Clears the feature's required (even) and optional (odd) bits from the given
334333
/// flags.
335334
#[inline]
336-
fn clear_bits(flags: &mut Vec<u8>) {
335+
fn clear_bits(flags: &mut FeatureFlags) {
337336
if flags.len() > Self::BYTE_OFFSET {
338337
flags[Self::BYTE_OFFSET] &= !Self::REQUIRED_MASK;
339338
flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK;
@@ -661,6 +660,9 @@ mod sealed {
661660
supports_trampoline_routing,
662661
requires_trampoline_routing
663662
);
663+
// By default, allocate enough bytes to cover up to Trampoline. Update this as new features are
664+
// added which we expect to appear commonly across contexts.
665+
pub(super) const MIN_FEATURES_ALLOCATION_BYTES: usize = (57 + 7) / 8;
664666
define_feature!(
665667
259,
666668
DnsResolver,
@@ -700,14 +702,131 @@ mod sealed {
700702
const ANY_REQUIRED_FEATURES_MASK: u8 = 0b01_01_01_01;
701703
const ANY_OPTIONAL_FEATURES_MASK: u8 = 0b10_10_10_10;
702704

705+
// Vecs are always 3 pointers long, so `FeatureFlags` is never shorter than 24 bytes on 64-bit
706+
// platforms no matter what we do.
707+
//
708+
// Luckily, because `Vec` uses a `NonNull` pointer to its buffer, the two-variant enum is free
709+
// space-wise, but we only get the remaining 2 usizes in length available for our own stuff (as any
710+
// other value is interpreted as the `Heap` variant).
711+
//
712+
// Thus, as long as we never use more than 15 bytes for our Held variant `FeatureFlags` is the same
713+
// length as a `Vec` in memory.
714+
const DIRECT_ALLOC_BYTES: usize = if sealed::MIN_FEATURES_ALLOCATION_BYTES > 8 * 2 - 1 {
715+
sealed::MIN_FEATURES_ALLOCATION_BYTES
716+
} else {
717+
8 * 2 - 1
718+
};
719+
const _ASSERT: () = assert!(DIRECT_ALLOC_BYTES <= u8::MAX as usize);
720+
721+
/// The internal storage for feature flags. Public only for the [`sealed`] feature flag traits but
722+
/// generally should never be used directly.
723+
#[derive(Clone, PartialEq, Eq)]
724+
#[doc(hidden)]
725+
pub enum FeatureFlags {
726+
Held { bytes: [u8; DIRECT_ALLOC_BYTES], len: u8 },
727+
Heap(Vec<u8>),
728+
}
729+
730+
impl FeatureFlags {
731+
fn empty() -> Self {
732+
Self::Held { bytes: [0; DIRECT_ALLOC_BYTES], len: 0 }
733+
}
734+
735+
fn from(vec: Vec<u8>) -> Self {
736+
if vec.len() <= DIRECT_ALLOC_BYTES {
737+
let mut bytes = [0; DIRECT_ALLOC_BYTES];
738+
bytes[..vec.len()].copy_from_slice(&vec);
739+
Self::Held { bytes, len: vec.len() as u8 }
740+
} else {
741+
Self::Heap(vec)
742+
}
743+
}
744+
745+
fn resize(&mut self, new_len: usize, default: u8) {
746+
match self {
747+
Self::Held { bytes, len } => {
748+
let start_len = *len as usize;
749+
if new_len <= DIRECT_ALLOC_BYTES {
750+
bytes[start_len..].copy_from_slice(&[default; DIRECT_ALLOC_BYTES][start_len..]);
751+
*len = new_len as u8;
752+
} else {
753+
let mut vec = Vec::new();
754+
vec.resize(new_len, default);
755+
vec[..start_len].copy_from_slice(&bytes[..start_len]);
756+
*self = Self::Heap(vec);
757+
}
758+
},
759+
Self::Heap(vec) => {
760+
vec.resize(new_len, default);
761+
if new_len <= DIRECT_ALLOC_BYTES {
762+
let mut bytes = [0; DIRECT_ALLOC_BYTES];
763+
bytes.copy_from_slice(&vec[..new_len]);
764+
*self = Self::Held { bytes, len: new_len as u8 };
765+
}
766+
},
767+
}
768+
}
769+
770+
fn len(&self) -> usize {
771+
self.deref().len()
772+
}
773+
774+
fn iter(&self) -> (impl ExactSizeIterator<Item = &u8> + DoubleEndedIterator<Item = &u8>) {
775+
let slice = self.deref();
776+
slice.iter()
777+
}
778+
779+
fn iter_mut(
780+
&mut self,
781+
) -> (impl ExactSizeIterator<Item = &mut u8> + DoubleEndedIterator<Item = &mut u8>) {
782+
let slice = self.deref_mut();
783+
slice.iter_mut()
784+
}
785+
}
786+
787+
impl Deref for FeatureFlags {
788+
type Target = [u8];
789+
fn deref(&self) -> &[u8] {
790+
match self {
791+
FeatureFlags::Held { bytes, len } => &bytes[..*len as usize],
792+
FeatureFlags::Heap(vec) => &vec,
793+
}
794+
}
795+
}
796+
797+
impl DerefMut for FeatureFlags {
798+
fn deref_mut(&mut self) -> &mut [u8] {
799+
match self {
800+
FeatureFlags::Held { bytes, len } => &mut bytes[..*len as usize],
801+
FeatureFlags::Heap(vec) => &mut vec[..],
802+
}
803+
}
804+
}
805+
806+
impl PartialOrd for FeatureFlags {
807+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
808+
self.deref().partial_cmp(other.deref())
809+
}
810+
}
811+
impl Ord for FeatureFlags {
812+
fn cmp(&self, other: &Self) -> cmp::Ordering {
813+
self.deref().cmp(other.deref())
814+
}
815+
}
816+
impl fmt::Debug for FeatureFlags {
817+
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
818+
self.deref().fmt(fmt)
819+
}
820+
}
821+
703822
/// Tracks the set of features which a node implements, templated by the context in which it
704823
/// appears.
705824
///
706825
/// This is not exported to bindings users as we map the concrete feature types below directly instead
707826
#[derive(Eq)]
708827
pub struct Features<T: sealed::Context> {
709828
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
710-
flags: Vec<u8>,
829+
flags: FeatureFlags,
711830
mark: PhantomData<T>,
712831
}
713832

@@ -897,20 +1016,21 @@ impl ChannelTypeFeatures {
8971016
impl<T: sealed::Context> Features<T> {
8981017
/// Create a blank Features with no features set
8991018
pub fn empty() -> Self {
900-
Features { flags: Vec::new(), mark: PhantomData }
1019+
Features { flags: FeatureFlags::empty(), mark: PhantomData }
9011020
}
9021021

9031022
/// Converts `Features<T>` to `Features<C>`. Only known `T` features relevant to context `C` are
9041023
/// included in the result.
9051024
fn to_context_internal<C: sealed::Context>(&self) -> Features<C> {
9061025
let from_byte_count = T::KNOWN_FEATURE_MASK.len();
9071026
let to_byte_count = C::KNOWN_FEATURE_MASK.len();
908-
let mut flags = Vec::new();
1027+
let mut flags = FeatureFlags::empty();
1028+
flags.resize(self.flags.len(), 0);
9091029
for (i, byte) in self.flags.iter().enumerate() {
9101030
if i < from_byte_count && i < to_byte_count {
9111031
let from_known_features = T::KNOWN_FEATURE_MASK[i];
9121032
let to_known_features = C::KNOWN_FEATURE_MASK[i];
913-
flags.push(byte & from_known_features & to_known_features);
1033+
flags[i] = byte & from_known_features & to_known_features;
9141034
}
9151035
}
9161036
Features::<C> { flags, mark: PhantomData }
@@ -921,7 +1041,7 @@ impl<T: sealed::Context> Features<T> {
9211041
///
9221042
/// This is not exported to bindings users as we don't support export across multiple T
9231043
pub fn from_le_bytes(flags: Vec<u8>) -> Features<T> {
924-
Features { flags, mark: PhantomData }
1044+
Features { flags: FeatureFlags::from(flags), mark: PhantomData }
9251045
}
9261046

9271047
/// Returns the feature set as a list of bytes, in little-endian. This is in reverse byte order
@@ -936,7 +1056,7 @@ impl<T: sealed::Context> Features<T> {
9361056
/// This is not exported to bindings users as we don't support export across multiple T
9371057
pub fn from_be_bytes(mut flags: Vec<u8>) -> Features<T> {
9381058
flags.reverse(); // Swap to little-endian
939-
Self { flags, mark: PhantomData }
1059+
Self { flags: FeatureFlags::from(flags), mark: PhantomData }
9401060
}
9411061

9421062
/// Returns true if this `Features` has any optional flags set

lightning/src/routing/router.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -4284,7 +4284,7 @@ mod tests {
42844284
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
42854285
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
42864286
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
4287-
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
4287+
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &[0]); // No feature flags will meet the relevant-to-channel conversion
42884288

42894289
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
42904290
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
@@ -4330,7 +4330,7 @@ mod tests {
43304330
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
43314331
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
43324332
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); // it should also override our view of their features
4333-
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
4333+
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &vec![0]); // No feature flags will meet the relevant-to-channel conversion
43344334

43354335
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
43364336
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
@@ -4394,7 +4394,7 @@ mod tests {
43944394
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
43954395
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
43964396
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
4397-
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
4397+
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &vec![0]); // No feature flags will meet the relevant-to-channel conversion
43984398

43994399
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
44004400
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
@@ -4937,14 +4937,14 @@ mod tests {
49374937
assert_eq!(route.paths[0].hops[0].fee_msat, 0);
49384938
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
49394939
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
4940-
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); // No feature flags will meet the relevant-to-channel conversion
4940+
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &vec![0]); // No feature flags will meet the relevant-to-channel conversion
49414941

49424942
assert_eq!(route.paths[0].hops[1].pubkey, nodes[6]);
49434943
assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
49444944
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
49454945
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
49464946
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
4947-
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
4947+
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[]); // We can't learn any flags from invoices, sadly
49484948

49494949
last_hops[0].0[0].fees.base_msat = 1000;
49504950

@@ -5077,14 +5077,14 @@ mod tests {
50775077
assert_eq!(route.paths[0].hops[0].fee_msat, 1001);
50785078
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
50795079
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &[0b11]);
5080-
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
5080+
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &[0]); // We can't learn any flags from invoices, sadly
50815081

50825082
assert_eq!(route.paths[0].hops[1].pubkey, target_node_id);
50835083
assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
50845084
assert_eq!(route.paths[0].hops[1].fee_msat, 1000000);
50855085
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
50865086
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
5087-
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly
5087+
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[]); // We can't learn any flags from invoices, sadly
50885088
}
50895089

50905090
#[test]

0 commit comments

Comments
 (0)