Skip to content

Commit 3d7b9b7

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 3d7b9b7

File tree

1 file changed

+133
-14
lines changed

1 file changed

+133
-14
lines changed

lightning-types/src/features.rs

Lines changed: 133 additions & 14 deletions
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,130 @@ 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+
}
757+
},
758+
Self::Heap(vec) => {
759+
vec.resize(new_len, default);
760+
if new_len <= DIRECT_ALLOC_BYTES {
761+
let mut bytes = [0; DIRECT_ALLOC_BYTES];
762+
bytes.copy_from_slice(&vec);
763+
*self = Self::Held { bytes, len: new_len as u8 };
764+
}
765+
},
766+
}
767+
}
768+
769+
fn len(&self) -> usize {
770+
self.deref().len()
771+
}
772+
773+
fn iter(&self) -> (impl ExactSizeIterator<Item = &u8> + DoubleEndedIterator<Item = &u8>) {
774+
let slice = self.deref();
775+
slice.iter()
776+
}
777+
778+
fn iter_mut(
779+
&mut self,
780+
) -> (impl ExactSizeIterator<Item = &mut u8> + DoubleEndedIterator<Item = &mut u8>) {
781+
let slice = self.deref_mut();
782+
slice.iter_mut()
783+
}
784+
}
785+
786+
impl Deref for FeatureFlags {
787+
type Target = [u8];
788+
fn deref(&self) -> &[u8] {
789+
match self {
790+
FeatureFlags::Held { bytes, len } => &bytes[..*len as usize],
791+
FeatureFlags::Heap(vec) => &vec,
792+
}
793+
}
794+
}
795+
796+
impl DerefMut for FeatureFlags {
797+
fn deref_mut(&mut self) -> &mut [u8] {
798+
match self {
799+
FeatureFlags::Held { bytes, len } => &mut bytes[..*len as usize],
800+
FeatureFlags::Heap(vec) => &mut vec[..],
801+
}
802+
}
803+
}
804+
805+
impl PartialOrd for FeatureFlags {
806+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
807+
self.deref().partial_cmp(other.deref())
808+
}
809+
}
810+
impl Ord for FeatureFlags {
811+
fn cmp(&self, other: &Self) -> cmp::Ordering {
812+
self.deref().cmp(other.deref())
813+
}
814+
}
815+
impl fmt::Debug for FeatureFlags {
816+
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
817+
self.deref().fmt(fmt)
818+
}
819+
}
820+
703821
/// Tracks the set of features which a node implements, templated by the context in which it
704822
/// appears.
705823
///
706824
/// This is not exported to bindings users as we map the concrete feature types below directly instead
707825
#[derive(Eq)]
708826
pub struct Features<T: sealed::Context> {
709827
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
710-
flags: Vec<u8>,
828+
flags: FeatureFlags,
711829
mark: PhantomData<T>,
712830
}
713831

@@ -897,20 +1015,21 @@ impl ChannelTypeFeatures {
8971015
impl<T: sealed::Context> Features<T> {
8981016
/// Create a blank Features with no features set
8991017
pub fn empty() -> Self {
900-
Features { flags: Vec::new(), mark: PhantomData }
1018+
Features { flags: FeatureFlags::empty(), mark: PhantomData }
9011019
}
9021020

9031021
/// Converts `Features<T>` to `Features<C>`. Only known `T` features relevant to context `C` are
9041022
/// included in the result.
9051023
fn to_context_internal<C: sealed::Context>(&self) -> Features<C> {
9061024
let from_byte_count = T::KNOWN_FEATURE_MASK.len();
9071025
let to_byte_count = C::KNOWN_FEATURE_MASK.len();
908-
let mut flags = Vec::new();
1026+
let mut flags = FeatureFlags::empty();
1027+
flags.resize(self.flags.len(), 0);
9091028
for (i, byte) in self.flags.iter().enumerate() {
9101029
if i < from_byte_count && i < to_byte_count {
9111030
let from_known_features = T::KNOWN_FEATURE_MASK[i];
9121031
let to_known_features = C::KNOWN_FEATURE_MASK[i];
913-
flags.push(byte & from_known_features & to_known_features);
1032+
flags[i] = byte & from_known_features & to_known_features;
9141033
}
9151034
}
9161035
Features::<C> { flags, mark: PhantomData }
@@ -921,7 +1040,7 @@ impl<T: sealed::Context> Features<T> {
9211040
///
9221041
/// This is not exported to bindings users as we don't support export across multiple T
9231042
pub fn from_le_bytes(flags: Vec<u8>) -> Features<T> {
924-
Features { flags, mark: PhantomData }
1043+
Features { flags: FeatureFlags::from(flags), mark: PhantomData }
9251044
}
9261045

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

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

0 commit comments

Comments
 (0)