Skip to content

Commit

Permalink
optimize *Queue::Append
Browse files Browse the repository at this point in the history
  • Loading branch information
TimLuq committed Jan 28, 2025
1 parent b98c535 commit 1dd3954
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 13 deletions.
21 changes: 21 additions & 0 deletions src/bytedata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,27 @@ impl From<Vec<u8>> for ByteData<'_> {
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<alloc::borrow::Cow<'a, [u8]>> for ByteData<'a> {
#[inline]
fn from(data: alloc::borrow::Cow<'a, [u8]>) -> Self {
ByteData::from_cow(data)
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<alloc::borrow::Cow<'a, str>> for ByteData<'a> {
#[inline]
fn from(data: alloc::borrow::Cow<'a, str>) -> Self {
match data {
alloc::borrow::Cow::Borrowed(borr) => Self::from_borrowed(borr.as_bytes()),
alloc::borrow::Cow::Owned(ow) => Self::from(ow),
}
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<ByteData<'a>> for Vec<u8> {
Expand Down
8 changes: 6 additions & 2 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ macro_rules! concat_str_static {
#[cfg(feature = "alloc")]
#[doc(hidden)]
#[inline]
#[allow(clippy::unwrap_used)]
#[must_use]
pub fn __format_shared<'a>(args: core::fmt::Arguments<'_>) -> crate::StringData<'a> {
if let Some(args2) = args.as_str() {
return crate::StringData::from_static(args2);
Expand All @@ -68,10 +70,11 @@ macro_rules! format_shared {
};
}

#[cfg(feature = "queue")]
#[cfg(all(feature = "queue", feature = "alloc"))]
#[doc(hidden)]
#[inline]
#[allow(clippy::unwrap_used)]
#[must_use]
pub fn __format_queue<'a>(args: core::fmt::Arguments<'_>) -> crate::StringQueue<'a> {
if let Some(args2) = args.as_str() {
return crate::StringQueue::with_item(crate::StringData::from_static(args2));
Expand All @@ -84,8 +87,9 @@ pub fn __format_queue<'a>(args: core::fmt::Arguments<'_>) -> crate::StringQueue<
/// Formats a format string with arguments into an owned `StringQueue`.
///
/// There is currently no way to optimize shallow clones of `StringData` or `StringQueue` instances, so prefer to use [`StringQueue::push_back`] or [`StringQueue::append`] to build a queue of prepared strings.
#[cfg(feature = "queue")]
#[cfg(all(feature = "queue", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[cfg_attr(docsrs, doc(cfg(feature = "queue")))]
#[macro_export]
macro_rules! format_queue {
Expand Down
28 changes: 24 additions & 4 deletions src/queue/byte_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,13 +422,23 @@ impl<'a> ByteQueue<'a> {
OwnedByteIter::new(self)
}

/// Adds another `ByteQueue`'s chunks to this queue. May be optimized in the future.
/// Adds another `ByteQueue`'s chunks to this queue.
#[inline]
#[cfg(not(feature = "alloc"))]
pub fn append(&mut self, other: Self) {
// TODO: optimize by adding full regions instead of just chunks at to save on region allocations
self.extend(other.into_iter());
}

/// Adds another `ByteQueue`'s chunks to this queue.
#[inline]
#[cfg(feature = "alloc")]
pub fn append(&mut self, mut other: Self) {
self.remain += other.remain;
other.remain = 0;
self.queue
.append(core::mem::replace(&mut other.queue, LinkedRoot::new()));
}

/// Split the queue at a certain index.
/// This will return the part of the queue after the index `[at, len)` and keep everything before the position in the original queue `[0, at)`.
///
Expand Down Expand Up @@ -851,7 +861,7 @@ impl core::cmp::Ord for ByteQueue<'_> {
}
#[allow(clippy::wildcard_enum_match_arm)]
match av.cmp(bv) {
core::cmp::Ordering::Equal => continue,
core::cmp::Ordering::Equal => (),
x => return x,
}
}
Expand Down Expand Up @@ -895,7 +905,7 @@ impl PartialOrd<[u8]> for ByteQueue<'_> {
}
#[allow(clippy::wildcard_enum_match_arm)]
match av.cmp(bv) {
core::cmp::Ordering::Equal => continue,
core::cmp::Ordering::Equal => (),
xy => return Some(xy),
}
}
Expand Down Expand Up @@ -996,3 +1006,13 @@ impl Default for ByteQueue<'_> {
ByteQueue::new()
}
}

#[cfg(feature = "alloc")]
impl core::fmt::Write for crate::ByteQueue<'_> {
#[inline]
#[allow(clippy::min_ident_chars)]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.push_back(crate::ByteData::from_borrowed(s.as_bytes()).into_shared());
Ok(())
}
}
62 changes: 56 additions & 6 deletions src/queue/linked_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::ByteData;

#[cfg(feature = "alloc")]
pub(super) struct LinkedRoot<'a> {
/// The first chunk in the queue, available for optimized use.
pub(super) chamber: ByteData<'a>,
pub(super) first: *mut super::linked_node_leaf::LinkedNodeLeaf<'a>,
pub(super) last: *mut super::linked_node_leaf::LinkedNodeLeaf<'a>,
/// The number of chunks in the queue.
pub(super) count: usize,
}

Expand Down Expand Up @@ -246,6 +248,60 @@ impl<'a> LinkedRoot<'a> {
// SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`.
LinkedIter::new(chamber, unsafe { self.first.as_ref() })
}

pub(super) fn append(&mut self, mut other: Self) {
if other.count == 0 {
return;
}
if self.count == 0 {
*self = other;
return;
}
if self.count == 1 && !self.chamber.is_empty() {
other.push_front(core::mem::replace(
&mut self.chamber,
ByteData::from_chunk(&[0]),
));
*self = other;
return;
}

// attempt to move the chambered item without allocating
if !other.chamber.is_empty() {
let chamber = core::mem::replace(&mut other.chamber, ByteData::empty());
other.count -= 1;
// SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`.
if let Some(fst) = unsafe { other.first.as_mut() } {
if let Err(val) = fst.data.push_front(chamber) {
self.push_back(val);
}
} else {
self.push_back(chamber);
}
if other.count == 0 {
return;
}
}

// SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`.
if let Some(last) = unsafe { self.last.as_mut() } {
// SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`.
if let Some(first) = unsafe { other.first.as_mut() } {
last.next = first;
first.prev = last;
self.last = other.last;
} else {
unreachable!("invalid state at append");
}
} else {
self.first = other.first;
self.last = other.last;
}
self.count += other.count;
other.first = core::ptr::null_mut();
other.last = core::ptr::null_mut();
other.count = 0;
}
}

impl<'a> LinkedRoot<'a> {
Expand Down Expand Up @@ -285,12 +341,6 @@ impl<'a> LinkedRoot<'a> {
return;
}
#[cfg(feature = "alloc")]
if self.count == 0 {
self.chamber = data;
self.count = 1;
return;
}
#[cfg(feature = "alloc")]
{
core::mem::swap(&mut self.chamber, &mut data);
if data.is_empty() {
Expand Down
2 changes: 2 additions & 0 deletions src/queue/string_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ impl core::fmt::Debug for crate::StringQueue<'_> {
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl core::fmt::Write for crate::StringQueue<'_> {
#[inline]
#[allow(clippy::min_ident_chars)]
Expand Down
12 changes: 12 additions & 0 deletions src/stringdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,18 @@ impl From<String> for StringData<'_> {
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<alloc::borrow::Cow<'a, str>> for StringData<'a> {
#[inline]
fn from(data: alloc::borrow::Cow<'a, str>) -> Self {
match data {
alloc::borrow::Cow::Borrowed(borr) => Self::from_borrowed(borr),
alloc::borrow::Cow::Owned(ow) => Self::from_owned(ow),
}
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> From<StringData<'a>> for String {
Expand Down
2 changes: 1 addition & 1 deletion src/test/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn test_macros_format_shared() {
assert!(hw.ends_with(choice));
}

#[cfg(feature = "queue")]
#[cfg(all(feature = "alloc", feature = "queue"))]
#[test]
fn test_macros_format_queue() {
static CHOICES: &[&str] = &[" you", " world", "... hello... hello", "oooooooo!"];
Expand Down
42 changes: 42 additions & 0 deletions src/test/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,45 @@ fn byte_queue_test() {
panic!("queue is not the same as the input data (from end)\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}");
}
}

#[test]
#[allow(clippy::panic)]
fn byte_queue_append_test() {
static A_DATA: &[&[u8]] = &[
b"a0", b"a1", b"a2", b"a3", b"a4", b"a5", b"a6", b"a7", b"a8", b"a9", b"aA", b"aB", b"aC",
b"aD", b"aE", b"aF",
];
static B_DATA: &[&[u8]] = &[
b"b0", b"b1", b"b2", b"b3", b"b4", b"b5", b"b6", b"b7", b"b8", b"b9", b"bA", b"bB", b"bC",
b"bD", b"bE", b"bF", b"bG", b"bH", b"bI",
];

let mut a_queue = crate::ByteQueue::new();
for data in A_DATA {
a_queue.push_back(*data);
}
for (chunk, data) in a_queue.chunks().zip(A_DATA) {
assert_eq!(chunk.as_slice(), *data);
}

let mut b_queue = crate::ByteQueue::new();
for data in B_DATA {
b_queue.push_back(*data);
}
for (chunk, data) in b_queue.chunks().zip(B_DATA) {
assert_eq!(chunk.as_slice(), *data);
}

a_queue.append(b_queue);

let mut i = 0;
while let Some(chunk) = a_queue.pop_front() {
if i < A_DATA.len() {
assert_eq!(chunk.as_slice(), A_DATA[i]);
} else {
assert_eq!(chunk.as_slice(), B_DATA[i - A_DATA.len()]);
}
i += 1;
}
assert_eq!(i, A_DATA.len() + B_DATA.len());
}

0 comments on commit 1dd3954

Please sign in to comment.