Skip to content

Commit 5c8fdf5

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 7fca90c commit 5c8fdf5

File tree

1 file changed

+135
-14
lines changed

1 file changed

+135
-14
lines changed

lightning-types/src/features.rs

+135-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,133 @@ 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 16 bytes (15 bytes for the data and one byte for the
713+
// length) for our Held variant `FeatureFlags` is the same 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[..new_len].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(
775+
&self,
776+
) -> (impl Clone + ExactSizeIterator<Item = &u8> + DoubleEndedIterator<Item = &u8>) {
777+
let slice = self.deref();
778+
slice.iter()
779+
}
780+
781+
fn iter_mut(
782+
&mut self,
783+
) -> (impl ExactSizeIterator<Item = &mut u8> + DoubleEndedIterator<Item = &mut u8>) {
784+
let slice = self.deref_mut();
785+
slice.iter_mut()
786+
}
787+
}
788+
789+
impl Deref for FeatureFlags {
790+
type Target = [u8];
791+
fn deref(&self) -> &[u8] {
792+
match self {
793+
FeatureFlags::Held { bytes, len } => &bytes[..*len as usize],
794+
FeatureFlags::Heap(vec) => &vec,
795+
}
796+
}
797+
}
798+
799+
impl DerefMut for FeatureFlags {
800+
fn deref_mut(&mut self) -> &mut [u8] {
801+
match self {
802+
FeatureFlags::Held { bytes, len } => &mut bytes[..*len as usize],
803+
FeatureFlags::Heap(vec) => &mut vec[..],
804+
}
805+
}
806+
}
807+
808+
impl PartialOrd for FeatureFlags {
809+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
810+
self.deref().partial_cmp(other.deref())
811+
}
812+
}
813+
impl Ord for FeatureFlags {
814+
fn cmp(&self, other: &Self) -> cmp::Ordering {
815+
self.deref().cmp(other.deref())
816+
}
817+
}
818+
impl fmt::Debug for FeatureFlags {
819+
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
820+
self.deref().fmt(fmt)
821+
}
822+
}
823+
703824
/// Tracks the set of features which a node implements, templated by the context in which it
704825
/// appears.
705826
///
706827
/// This is not exported to bindings users as we map the concrete feature types below directly instead
707828
#[derive(Eq)]
708829
pub struct Features<T: sealed::Context> {
709830
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
710-
flags: Vec<u8>,
831+
flags: FeatureFlags,
711832
mark: PhantomData<T>,
712833
}
713834

@@ -897,7 +1018,7 @@ impl ChannelTypeFeatures {
8971018
impl<T: sealed::Context> Features<T> {
8981019
/// Create a blank Features with no features set
8991020
pub fn empty() -> Self {
900-
Features { flags: Vec::new(), mark: PhantomData }
1021+
Features { flags: FeatureFlags::empty(), mark: PhantomData }
9011022
}
9021023

9031024
/// Converts `Features<T>` to `Features<C>`. Only known `T` features relevant to context `C` are
@@ -910,7 +1031,7 @@ impl<T: sealed::Context> Features<T> {
9101031
None
9111032
}
9121033
});
913-
let mut flags = Vec::new();
1034+
let mut flags = FeatureFlags::empty();
9141035
flags.resize(flag_iter.clone().count(), 0);
9151036
for (i, byte) in flag_iter {
9161037
flags[i] = byte;
@@ -923,7 +1044,7 @@ impl<T: sealed::Context> Features<T> {
9231044
///
9241045
/// This is not exported to bindings users as we don't support export across multiple T
9251046
pub fn from_le_bytes(flags: Vec<u8>) -> Features<T> {
926-
Features { flags, mark: PhantomData }
1047+
Features { flags: FeatureFlags::from(flags), mark: PhantomData }
9271048
}
9281049

9291050
/// Returns the feature set as a list of bytes, in little-endian. This is in reverse byte order
@@ -938,7 +1059,7 @@ impl<T: sealed::Context> Features<T> {
9381059
/// This is not exported to bindings users as we don't support export across multiple T
9391060
pub fn from_be_bytes(mut flags: Vec<u8>) -> Features<T> {
9401061
flags.reverse(); // Swap to little-endian
941-
Self { flags, mark: PhantomData }
1062+
Self { flags: FeatureFlags::from(flags), mark: PhantomData }
9421063
}
9431064

9441065
/// Returns true if this `Features` has any optional flags set
@@ -1331,7 +1452,7 @@ mod tests {
13311452
use std::hash::{Hash, Hasher};
13321453

13331454
let mut zerod_features = InitFeatures::empty();
1334-
zerod_features.flags = vec![0];
1455+
zerod_features.flags = FeatureFlags::Heap(vec![0]);
13351456
let empty_features = InitFeatures::empty();
13361457
assert!(empty_features.flags.is_empty());
13371458

0 commit comments

Comments
 (0)