diff --git a/Cargo.lock b/Cargo.lock index 24aec513..ac4ae94a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,17 @@ dependencies = [ "zstd", ] +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -932,6 +943,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapopt" +version = "0.1.0" +dependencies = [ + "derive-where", + "heapless", + "more-asserts", +] + [[package]] name = "heck" version = "0.5.0" @@ -989,6 +1009,7 @@ dependencies = [ "env_logger", "flate2", "heapless", + "heapopt", "hex", "htp", "humantime", @@ -1328,6 +1349,12 @@ dependencies = [ "syn", ] +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + [[package]] name = "nonzero_ext" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 3579f529..565a202a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "crate/encstr"] +members = [".", "crate/encstr", "crate/heapopt"] [workspace.package] repository = "https://github.com/pamburus/hl" @@ -53,6 +53,7 @@ enumset-ext = { path = "./crate/enumset-ext" } env_logger = "0" flate2 = "1" heapless = "0" +heapopt = { path = "./crate/heapopt" } hex = "0" htp = { git = "https://github.com/pamburus/htp.git" } humantime = "2" diff --git a/crate/heapopt/Cargo.toml b/crate/heapopt/Cargo.toml new file mode 100644 index 00000000..abcd9b69 --- /dev/null +++ b/crate/heapopt/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "heapopt" +version = "0.1.0" +repository.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +derive-where = "1" +heapless = "0" + +[dev-dependencies] +more-asserts = "0" diff --git a/crate/heapopt/src/lib.rs b/crate/heapopt/src/lib.rs new file mode 100644 index 00000000..c6b5a7d2 --- /dev/null +++ b/crate/heapopt/src/lib.rs @@ -0,0 +1,14 @@ +//! Data structures that allow optimization for rare heap usage. +//! Optimization can be achieved by storing part of the data in a fixed size heapless part. +//! If that capacity is not enough, the rest is stored in a heap allocated part. + +pub mod vec; + +/// Vec is a re-export of the [`vec::Vec`]`. +pub type Vec = vec::Vec; + +/// VecIter is a re-export of the [`vec::Iter`]`. +pub type VecIter<'a, T> = vec::Iter<'a, T>; + +/// VecIterMut is a re-export of the [`vec::IterMut`]`. +pub type VecIterMut<'a, T> = vec::IterMut<'a, T>; diff --git a/crate/heapopt/src/vec.rs b/crate/heapopt/src/vec.rs new file mode 100644 index 00000000..4ac0ec09 --- /dev/null +++ b/crate/heapopt/src/vec.rs @@ -0,0 +1,413 @@ +// std imports +use std::{ + iter::{Chain, Extend}, + ops::{Index, IndexMut}, + slice, +}; + +// third-party imports +use derive_where::derive_where; + +// --- + +/// A vector that can store up to `N` elements on the stack. +#[derive(Clone, Debug)] +#[derive_where(Default)] +pub struct Vec { + head: heapless::Vec, + tail: std::vec::Vec, +} + +impl Vec { + /// Creates a new empty vector. + #[inline] + pub fn new() -> Self { + Self { + head: heapless::Vec::new(), + tail: std::vec::Vec::new(), + } + } + + /// Creates a new empty vector with the given capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { + head: heapless::Vec::new(), + tail: std::vec::Vec::with_capacity(capacity - N.min(capacity)), + } + } + + /// Creates a new vector from the given slice. + #[inline] + pub fn from_slice(other: &[T]) -> Self + where + T: Clone, + { + let mut v = Self::new(); + v.extend_from_slice(other); + v + } + + /// Returns the number of elements in the vector. + #[inline] + pub fn len(&self) -> usize { + self.head.len() + self.tail.len() + } + + /// Returns the total number of elements the vector can hold without reallocating. + #[inline] + pub fn capacity(&self) -> usize { + self.head.capacity() + self.tail.capacity() + } + + /// Returns the element at the given index, or `None` if the index is out of bounds. + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + if index < N { + self.head.get(index) + } else { + self.tail.get(index - N) + } + } + + /// Returns a mutable reference to the element at the given index, or `None` if the index is out of bounds. + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + if index < N { + self.head.get_mut(index) + } else { + self.tail.get_mut(index - N) + } + } + + /// Returns the first element of the vector, or `None` if it is empty. + #[inline] + pub fn first(&self) -> Option<&T> { + if self.head.is_empty() { + self.tail.first() + } else { + self.head.first() + } + } + + /// Returns a mutable reference to the first element of the vector, or `None` if it is empty. + #[inline] + pub fn first_mut(&mut self) -> Option<&mut T> { + if self.head.is_empty() { + self.tail.first_mut() + } else { + self.head.first_mut() + } + } + + /// Returns the last element of the vector, or `None` if it is empty. + #[inline] + pub fn last(&self) -> Option<&T> { + if self.tail.is_empty() { + self.head.last() + } else { + self.tail.last() + } + } + + /// Returns a mutable reference to the first element of the vector, or `None` if it is empty. + #[inline] + pub fn last_mut(&mut self) -> Option<&mut T> { + if self.tail.is_empty() { + self.head.last_mut() + } else { + self.tail.last_mut() + } + } + + /// Clears the vector, removing all elements. + #[inline] + pub fn clear(&mut self) { + self.head.clear(); + self.tail.clear(); + } + + /// Truncates the vector, keeping only the first `len` elements. + /// If `len` is greater than the length of the vector, this has no effect. + #[inline] + pub fn truncate(&mut self, len: usize) { + if len <= self.head.len() { + self.head.truncate(len); + self.tail.clear(); + } else { + self.tail.truncate(len - self.head.len()); + } + } + + /// Appends an element to the end of the vector. + #[inline] + pub fn push(&mut self, value: T) { + if let Err(value) = self.head.push(value) { + self.tail.push(value); + } + } + + /// Removes the last element from the vector and returns it, or `None` if it is empty. + #[inline] + pub fn pop(&mut self) -> Option { + if let Some(value) = self.tail.pop() { + Some(value) + } else { + self.head.pop() + } + } + + /// Returns a pair of slices containing all elements of the vector in order. + #[inline] + pub fn as_slices(&self) -> (&[T], &[T]) { + (self.head.as_slice(), self.tail.as_slice()) + } + + /// Returns a pair of mutable slices containing all elements of the vector in order. + #[inline] + pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { + (self.head.as_mut_slice(), self.tail.as_mut_slice()) + } + + /// Returns an iterator over the elements of the vector. + #[inline] + pub fn iter(&self) -> Iter { + self.into_iter() + } + + /// Returns a mutable iterator over the elements of the vector. + #[inline] + pub fn iter_mut(&mut self) -> IterMut { + self.into_iter() + } + + /// Reserves capacity for at least `additional` more elements to be inserted in the vector. + #[inline] + pub fn reserve(&mut self, additional: usize) { + let head = N - self.head.len(); + if additional > head { + self.tail.reserve(additional - head); + } + } +} + +impl Vec +where + T: Clone, +{ + /// Extends the vector with the elements from the given slice. + #[inline] + pub fn extend_from_slice(&mut self, values: &[T]) { + let n = N - self.head.len(); + if values.len() <= n { + self.head.extend_from_slice(values).ok(); + } else { + self.head.extend_from_slice(&values[..n]).ok(); + self.tail.extend_from_slice(&values[n..]); + } + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a Vec { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.head.iter().chain(self.tail.iter()) + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut Vec { + type Item = &'a mut T; + type IntoIter = IterMut<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.head.iter_mut().chain(self.tail.iter_mut()) + } +} + +impl Index for Vec { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + if index < N { + &self.head[index] + } else { + &self.tail[index - N] + } + } +} + +impl IndexMut for Vec { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index < N { + &mut self.head[index] + } else { + &mut self.tail[index - N] + } + } +} + +impl Extend for Vec +where + T: Clone, +{ + #[inline] + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + let mut iter = iter.into_iter(); + let head = N - self.head.len(); + if head > 0 { + self.head.extend(iter.by_ref().take(head)); + } + self.tail.extend(iter); + } +} + +// --- + +/// An iterator over the elements of a vector. +pub type Iter<'a, T> = Chain, slice::Iter<'a, T>>; + +/// A mutable iterator over the elements of a vector. +pub type IterMut<'a, T> = Chain, slice::IterMut<'a, T>>; + +// --- + +#[cfg(test)] +mod tests { + use super::*; + + // third-party imports + use more_asserts::*; + + #[test] + fn test_vec() { + let mut vec = Vec::::new(); + assert_eq!(vec.len(), 0); + + vec.push(1); + vec.push(2); + vec.push(3); + assert_eq!(vec.len(), 3); + + vec.push(4); + assert_eq!(vec.len(), 4); + + vec.clear(); + assert_eq!(vec.len(), 0); + + vec.push(1); + vec.push(2); + vec.push(3); + vec.truncate(2); + assert_eq!(vec.len(), 2); + + vec.push(3); + vec.push(4); + vec.push(5); + vec.truncate(4); + assert_eq!(vec.len(), 4); + + assert_eq!(vec.get(0), Some(&1)); + assert_eq!(vec.get(1), Some(&2)); + assert_eq!(vec.get(3), Some(&4)); + + assert_eq!(vec.get_mut(0), Some(&mut 1)); + assert_eq!(vec.get_mut(1), Some(&mut 2)); + assert_eq!(vec.get_mut(3), Some(&mut 4)); + + let mut vec = Vec::::from_slice(&[1, 2, 3, 4]); + assert_eq!(vec.len(), 4); + + vec.clear(); + vec.extend_from_slice(&[1, 2, 3, 4, 5]); + assert_eq!(vec.len(), 5); + + assert_eq!(vec.as_slices().0, &[1, 2, 3]); + assert_eq!(vec.as_slices().1, &[4, 5]); + assert_eq!(vec.as_mut_slices().0, &mut [1, 2, 3]); + assert_eq!(vec.as_mut_slices().1, &mut [4, 5]); + + assert_eq!(vec[0], 1); + assert_eq!(vec[1], 2); + assert_eq!(vec[2], 3); + assert_eq!(vec[3], 4); + + vec[1] = 6; + assert_eq!(vec[1], 6); + + vec[3] = 7; + assert_eq!(vec[3], 7); + + let mut iter = vec.iter(); + assert_eq!(iter.next(), Some(&1)); + assert_eq!(iter.next(), Some(&6)); + + let mut iter = vec.iter_mut(); + assert_eq!(iter.next(), Some(&mut 1)); + assert_eq!(iter.next(), Some(&mut 6)); + + assert_eq!(vec.first(), Some(&1)); + assert_eq!(vec.first_mut(), Some(&mut 1)); + assert_eq!(vec.last(), Some(&5)); + assert_eq!(vec.last_mut(), Some(&mut 5)); + + assert_eq!(vec.pop(), Some(5)); + assert_eq!(vec.pop(), Some(7)); + assert_eq!(vec.pop(), Some(3)); + assert_eq!(vec.pop(), Some(6)); + + assert_eq!(vec.first(), Some(&1)); + assert_eq!(vec.first_mut(), Some(&mut 1)); + assert_eq!(vec.last(), Some(&1)); + assert_eq!(vec.last_mut(), Some(&mut 1)); + + assert_eq!(vec.pop(), Some(1)); + assert_eq!(vec.pop(), None); + + assert_eq!(vec.first(), None); + assert_eq!(vec.first_mut(), None); + assert_eq!(vec.last(), None); + assert_eq!(vec.last_mut(), None); + + assert_eq!(Vec::::with_capacity(3).capacity(), 3); + assert_eq!(Vec::::with_capacity(1).capacity(), 2); + assert_eq!(Vec::::with_capacity(0).capacity(), 2); + + let mut vec = Vec::::new(); + + vec.reserve(2); + assert_eq!(vec.capacity(), 3); + + vec.reserve(3); + assert_eq!(vec.capacity(), 3); + + vec.reserve(4); + let cap = vec.capacity(); + assert_ge!(cap, 4); + + vec.extend([1, 2].iter().cloned()); + assert_eq!(vec.len(), 2); + assert_eq!(vec.capacity(), cap); + assert_eq!(vec.as_slices().0, &[1, 2]); + + vec.extend([3, 4].iter().cloned()); + assert_eq!(vec.len(), 4); + assert_eq!(vec.capacity(), cap); + assert_eq!(vec.as_slices().0, &[1, 2, 3]); + assert_eq!(vec.as_slices().1, &[4]); + + vec.extend([5, 6].iter().cloned()); + assert_eq!(vec.len(), 6); + assert_ge!(vec.capacity(), 6); + assert_eq!(vec.as_slices().0, &[1, 2, 3]); + assert_eq!(vec.as_slices().1, &[4, 5, 6]); + } +} diff --git a/src/fmtx.rs b/src/fmtx.rs index c168e0d3..d283f511 100644 --- a/src/fmtx.rs +++ b/src/fmtx.rs @@ -24,65 +24,7 @@ where // --- -#[derive(Default)] -pub struct OptimizedBuf { - pub head: heapless::Vec, - pub tail: Vec, -} - -impl OptimizedBuf -where - T: Clone, -{ - #[inline] - pub fn new() -> Self { - Self { - head: heapless::Vec::new(), - tail: Vec::new(), - } - } - - #[inline] - pub fn len(&self) -> usize { - self.head.len() + self.tail.len() - } - - #[inline] - pub fn clear(&mut self) { - self.head.clear(); - self.tail.clear(); - } - - #[inline] - pub fn truncate(&mut self, len: usize) { - if len <= self.head.len() { - self.head.truncate(len); - self.tail.clear(); - } else { - self.tail.truncate(len - self.head.len()); - } - } - - #[inline] - pub fn push(&mut self, value: T) { - if self.head.len() < N { - self.head.push(value).ok(); - } else { - self.tail.push(value); - } - } - - #[inline] - pub fn extend_from_slice(&mut self, values: &[T]) { - if self.head.len() + values.len() <= N { - self.head.extend_from_slice(values).ok(); - } else { - let n = N - self.head.len(); - self.head.extend_from_slice(&values[..n]).ok(); - self.tail.extend_from_slice(&values[n..]); - } - } -} +pub type OptimizedBuf = heapopt::Vec; impl Push for OptimizedBuf where @@ -495,8 +437,8 @@ mod tests { assert_eq!(buf.len(), 4); buf.push(5); assert_eq!(buf.len(), 5); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[5]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[5]); } #[test] @@ -511,8 +453,8 @@ mod tests { assert_eq!(buf.len(), 3); buf.extend_from_slice(&[4, 5, 6]); assert_eq!(buf.len(), 6); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[5, 6]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[5, 6]); } #[test] @@ -523,32 +465,32 @@ mod tests { assert_eq!(buf.len(), 7); buf.truncate(8); assert_eq!(buf.len(), 7); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[5, 6, 7]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[5, 6, 7]); buf.truncate(7); assert_eq!(buf.len(), 7); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[5, 6, 7]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[5, 6, 7]); buf.truncate(6); assert_eq!(buf.len(), 6); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[5, 6]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[5, 6]); buf.truncate(4); assert_eq!(buf.len(), 4); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.len(), 0); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1.len(), 0); buf.truncate(4); buf.extend_from_slice(&[8, 9]); assert_eq!(buf.len(), 6); - assert_eq!(buf.head.as_slice(), &[1, 2, 3, 4]); - assert_eq!(buf.tail.as_slice(), &[8, 9]); + assert_eq!(buf.as_slices().0, &[1, 2, 3, 4]); + assert_eq!(buf.as_slices().1, &[8, 9]); buf.truncate(3); assert_eq!(buf.len(), 3); - assert_eq!(buf.head.as_slice(), &[1, 2, 3]); - assert_eq!(buf.tail.len(), 0); + assert_eq!(buf.as_slices().0, &[1, 2, 3]); + assert_eq!(buf.as_slices().1.len(), 0); buf.truncate(0); assert_eq!(buf.len(), 0); - assert_eq!(buf.head.len(), 0); - assert_eq!(buf.tail.len(), 0); + assert_eq!(buf.as_slices().0.len(), 0); + assert_eq!(buf.as_slices().1.len(), 0); } } diff --git a/src/formatting.rs b/src/formatting.rs index 8a724e16..e66531cc 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -326,8 +326,8 @@ impl KeyPrefix { #[inline] fn format>(&self, buf: &mut B) { - buf.extend_from_slice(&self.value.head); - buf.extend_from_slice(&self.value.tail); + buf.extend_from_slice(&self.value.as_slices().0); + buf.extend_from_slice(&self.value.as_slices().1); } #[inline] @@ -985,10 +985,7 @@ mod tests { impl<'a> RecordExt<'a> for Record<'a> { fn from_fields(fields: &[(&'a str, RawValue<'a>)]) -> Record<'a> { Record { - fields: RecordFields { - head: heapless::Vec::from_slice(fields).unwrap(), - ..Default::default() - }, + fields: RecordFields::from_slice(fields), ..Default::default() } } @@ -1003,10 +1000,7 @@ mod tests { level: Some(Level::Debug), logger: Some("tl"), caller: Some(Caller::Text("tc")), - fields: RecordFields { - head: heapless::Vec::from_slice(&[("k_a", RawValue::from(RawObject::Json(&ka)))]).unwrap(), - ..Default::default() - }, + fields: RecordFields::from_slice(&[("k_a", RawValue::from(RawObject::Json(&ka)))]), ..Default::default() }; diff --git a/src/model.rs b/src/model.rs index cff8ebf9..e709656a 100644 --- a/src/model.rs +++ b/src/model.rs @@ -322,7 +322,7 @@ pub struct Record<'a> { impl<'a> Record<'a> { #[inline(always)] pub fn fields(&self) -> impl Iterator)> { - self.fields.head.iter().chain(self.fields.tail.iter()) + self.fields.iter() } #[inline(always)] @@ -342,24 +342,13 @@ impl<'a> Record<'a> { level: None, logger: None, caller: None, - fields: RecordFields { - head: heapless::Vec::new(), - tail: if capacity > RECORD_EXTRA_CAPACITY { - Vec::with_capacity(capacity - RECORD_EXTRA_CAPACITY) - } else { - Vec::new() - }, - }, + fields: RecordFields::with_capacity(capacity), predefined: heapless::Vec::new(), } } } -#[derive(Default)] -pub struct RecordFields<'a> { - pub(crate) head: heapless::Vec<(&'a str, RawValue<'a>), RECORD_EXTRA_CAPACITY>, - pub(crate) tail: Vec<(&'a str, RawValue<'a>)>, -} +pub type RecordFields<'a> = heapopt::Vec<(&'a str, RawValue<'a>), RECORD_EXTRA_CAPACITY>; // --- @@ -660,10 +649,7 @@ impl ParserSettingsBlock { return; } } - match to.fields.head.push((key, value)) { - Ok(_) => {} - Err(value) => to.fields.tail.push(value), - } + to.fields.push((key, value)); } #[inline(always)] @@ -899,17 +885,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for RawRecord<'a> { // --- -pub struct RawRecordFields<'a> { - head: heapless::Vec<(&'a str, RawValue<'a>), RAW_RECORD_FIELDS_CAPACITY>, - tail: Vec<(&'a str, RawValue<'a>)>, -} - -impl<'a> RawRecordFields<'a> { - #[inline] - pub fn iter(&self) -> impl Iterator)> { - self.head.iter().chain(self.tail.iter()) - } -} +pub type RawRecordFields<'a> = heapopt::Vec<(&'a str, RawValue<'a>), RAW_RECORD_FIELDS_CAPACITY>; // --- @@ -1108,23 +1084,14 @@ where } fn visit_map>(self, mut access: M) -> std::result::Result { - let mut head = heapless::Vec::new(); - let count = access.size_hint().unwrap_or(0); - let mut tail = match count > RAW_RECORD_FIELDS_CAPACITY { - false => Vec::new(), - true => Vec::with_capacity(count - RAW_RECORD_FIELDS_CAPACITY), - }; + let mut fields = heapopt::Vec::with_capacity(access.size_hint().unwrap_or(0)); + while let Some(key) = access.next_key::<&'a str>()? { let value: &RV = access.next_value()?; - match head.push((key, value.into())) { - Ok(_) => {} - Err(value) => tail.push(value), - } + fields.push((key, value.into())); } - Ok(RawRecord { - fields: RawRecordFields { head, tail }, - }) + Ok(RawRecord { fields }) } } @@ -1766,8 +1733,8 @@ mod tests { let rec = stream.next().unwrap().unwrap(); assert_eq!(rec.prefix, b""); - assert_eq!(rec.record.fields.head.len(), 0); - assert_eq!(rec.record.fields.tail.len(), 0); + assert_eq!(rec.record.fields.as_slices().0.len(), 0); + assert_eq!(rec.record.fields.as_slices().1.len(), 0); } #[test]