Skip to content

Commit

Permalink
batched extend implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
jdonszelmann committed Sep 16, 2023
1 parent 90675b6 commit cf0437d
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 24 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ criterion = "0.4.0"
compiletest_rs = "0.10.0"

[features]
default = ["alloc"]
default = ["alloc", "batched_extend"]
# disable the alloc based ringbuffer, to make RingBuffers work in no_alloc environments
alloc = []

batched_extend = []

[[bench]]
name = "bench"
harness = false
Expand Down
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ fn extend_too_few(b: &mut Bencher) {

fn extend_after_one(b: &mut Bencher) {
let mut rb = ConstGenericRingBuffer::new::<8192>();
rb.push(0);
rb.push(&0);
let input = (0..4096).collect::<Vec<_>>();

b.iter_batched(
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,8 +1356,8 @@ mod tests {
}

test_fill(AllocRingBuffer::new(4));
// test_fill(GrowableAllocRingBuffer::with_capacity(4));
// test_fill(ConstGenericRingBuffer::<i32, 4>::new());
test_fill(GrowableAllocRingBuffer::with_capacity(4));
test_fill(ConstGenericRingBuffer::<i32, 4>::new());
}

mod test_dropping {
Expand Down
54 changes: 34 additions & 20 deletions src/with_const_generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ impl<T, const CAP: usize> ConstGenericRingBuffer<T, CAP> {
/// # Safety
/// Only safe when old != new
#[inline]
#[cfg(feature = "batched_extend")]
unsafe fn split_pointer_move(
&mut self,
old: usize,
Expand All @@ -273,6 +274,7 @@ impl<T, const CAP: usize> ConstGenericRingBuffer<T, CAP> {
/// # Safety
/// Only safe when `CAP` >= `BATCH_SIZE`
#[inline]
#[cfg(feature = "batched_extend")]
unsafe fn extend_from_arr_batch<const BATCH_SIZE: usize>(&mut self, data: [T; BATCH_SIZE]) {
debug_assert!(CAP >= BATCH_SIZE);

Expand Down Expand Up @@ -374,6 +376,7 @@ impl<T, const CAP: usize> ConstGenericRingBuffer<T, CAP> {
}

#[inline]
#[cfg(feature = "batched_extend")]
fn fill_batch<const BATCH_SIZE: usize>(
batch: &mut [MaybeUninit<T>; BATCH_SIZE],
iter: &mut impl Iterator<Item = T>,
Expand All @@ -390,6 +393,7 @@ impl<T, const CAP: usize> ConstGenericRingBuffer<T, CAP> {
}

#[inline]
#[cfg(feature = "batched_extend")]
fn extend_batched<const BATCH_SIZE: usize>(&mut self, mut other: impl Iterator<Item = T>) {
// SAFETY: if CAP < Self::BATCH_SIZE we can't run extend_from_arr_batch so we catch that here
if CAP < BATCH_SIZE {
Expand Down Expand Up @@ -431,6 +435,7 @@ impl<T, const CAP: usize> ConstGenericRingBuffer<T, CAP> {

/// # Safety
/// ONLY USE WHEN WORKING ON A CLEARED RINGBUFFER
#[cfg(feature = "batched_extend")]
#[inline]
unsafe fn finish_iter<const BATCH_SIZE: usize>(&mut self, mut iter: impl Iterator<Item = T>) {
let mut index = 0;
Expand Down Expand Up @@ -458,33 +463,42 @@ impl<T, const CAP: usize> Extend<T> for ConstGenericRingBuffer<T, CAP> {
/// NOTE: correctness (but not soundness) of extend depends on `size_hint` on iter being correct.
#[inline]
fn extend<A: IntoIterator<Item = T>>(&mut self, iter: A) {
const BATCH_SIZE: usize = 1;
// const BATCH_SIZE: usize = 1024;
#[cfg(not(feature = "batched_extend"))]
{
for i in iter {
self.push(i);
}
}

let iter = iter.into_iter();
#[cfg(feature = "batched_extend")]
{
const BATCH_SIZE: usize = 30;

let (lower, _) = iter.size_hint();
let iter = iter.into_iter();

if lower >= CAP {
// if there are more elements in our iterator than we have size in the ringbuffer
// drain the ringbuffer
self.clear();
let (lower, _) = iter.size_hint();

// we need exactly CAP elements.
// so we need to drop until the number of elements in the iterator is exactly CAP
let num_we_can_drop = lower - CAP;
if lower >= CAP {
// if there are more elements in our iterator than we have size in the ringbuffer
// drain the ringbuffer
self.clear();

let iter = iter.skip(num_we_can_drop);
// we need exactly CAP elements.
// so we need to drop until the number of elements in the iterator is exactly CAP
let num_we_can_drop = lower - CAP;

// Safety: clear above
unsafe { self.finish_iter::<BATCH_SIZE>(iter) };
} else if self.is_empty() {
self.clear();
let iter = iter.skip(num_we_can_drop);

// Safety: clear above
unsafe { self.finish_iter::<BATCH_SIZE>(iter) };
} else {
self.extend_batched::<BATCH_SIZE>(iter);
// Safety: clear above
unsafe { self.finish_iter::<BATCH_SIZE>(iter) };
} else if self.is_empty() {
self.clear();

// Safety: clear above
unsafe { self.finish_iter::<BATCH_SIZE>(iter) };
} else {
self.extend_batched::<BATCH_SIZE>(iter);
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions tests/compile-fail/test_const_generic_array_zero_length.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extern crate ringbuffer;

use ringbuffer::ConstGenericRingBuffer;

fn main() {
let _ = ConstGenericRingBuffer::<i32, 0>::new();
//~^ note: the above error was encountered while instantiating `fn ringbuffer::ConstGenericRingBuffer::<i32, 0>::new::<0>`
// ringbuffer can't be zero length
}
10 changes: 10 additions & 0 deletions tests/compile-fail/test_const_generic_array_zero_length_new.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extern crate ringbuffer;

use ringbuffer::{ConstGenericRingBuffer, RingBuffer};

fn main() {
let mut buf = ConstGenericRingBuffer::new::<0>();
//~^ note: the above error was encountered while instantiating `fn ringbuffer::ConstGenericRingBuffer::<i32, 0>::new::<0>`
// ringbuffer can't be zero length
buf.push(5);
}
23 changes: 23 additions & 0 deletions tests/compiletests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
extern crate compiletest_rs as compiletest;

use std::path::PathBuf;

#[cfg(test)]
mod conversions;

fn run_mode(mode: &'static str) {
let mut config = compiletest::Config::default();

config.mode = mode.parse().expect("Invalid mode");
config.src_base = PathBuf::from(format!("tests/{}", mode));
config.link_deps(); // Populate config.target_rustcflags with dependencies on the path
config.clean_rmeta(); // If your tests import the parent crate, this helps with E0464

compiletest::run_tests(&config);
}

#[test]
#[cfg_attr(miri, ignore)]
fn compile_test() {
run_mode("compile-fail");
}
135 changes: 135 additions & 0 deletions tests/conversions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
extern crate alloc;

use alloc::collections::{LinkedList, VecDeque};
use alloc::string::ToString;
use core::ops::Deref;
use ringbuffer::RingBuffer;
use ringbuffer::{AllocRingBuffer, ConstGenericRingBuffer, GrowableAllocRingBuffer};
use std::vec;

macro_rules! convert_test {
($name: ident: $from: expr => $to: ty) => {
#[test]
fn $name() {
let a = $from;

let mut b: $to = a.into();
assert_eq!(b.to_vec(), vec!['1', '2']);
b.push('3');
assert_eq!(b, b);
}
};
}

macro_rules! convert_tests {
(
[$($name: ident: $from: expr),* $(,)?]
=> $to: ty
) => {
$(
convert_test!($name: $from => $to);
)*
};
}

convert_tests!(
[
alloc_from_vec: vec!['1', '2'],
alloc_from_ll: {let mut l = LinkedList::new(); l.push_back('1'); l.push_back('2'); l},
alloc_from_vd: {let mut l = VecDeque::new(); l.push_back('1'); l.push_back('2'); l},
alloc_from_str: "12".to_string(),
alloc_from_str_slice: "12",
alloc_from_slice: {let a: &[char] = &['1', '2']; a},
alloc_from_const_slice: {let a: &[char; 2] = &['1', '2']; a},
alloc_from_arr: {let a: [char; 2] = ['1', '2']; a},

alloc_from_cgrb: {let a = ConstGenericRingBuffer::from(['1', '2']); a},
alloc_from_garb: {let a = GrowableAllocRingBuffer::from(['1', '2']); a},
] => AllocRingBuffer::<_>
);

convert_tests!(
[
growable_alloc_from_vec: vec!['1', '2'],
growable_alloc_from_ll: {let mut l = LinkedList::new(); l.push_back('1'); l.push_back('2'); l},
growable_alloc_from_vd: {let mut l = VecDeque::new(); l.push_back('1'); l.push_back('2'); l},
growable_alloc_from_str: "12".to_string(),
growable_alloc_from_str_slice: "12",
growable_alloc_from_slice: {let a: &[char] = &['1', '2']; a},
growable_alloc_from_const_slice: {let a: &[char; 2] = &['1', '2']; a},
growable_alloc_from_arr: {let a: [char; 2] = ['1', '2']; a},

growable_alloc_from_cgrb: {let a = ConstGenericRingBuffer::from(['1', '2']); a},
growable_alloc_from_arb: {let a = AllocRingBuffer::from(['1', '2']); a},
] => GrowableAllocRingBuffer::<_>
);

convert_tests!(
[
const_from_vec: vec!['1', '2'],
const_from_ll: {let mut l = LinkedList::new(); l.push_back('1'); l.push_back('2'); l},
const_from_vd: {let mut l = VecDeque::new(); l.push_back('1'); l.push_back('2'); l},
const_from_str: "12".to_string(),
const_from_str_slice: "12",
const_from_slice: {let a: &[char] = &['1', '2']; a},
const_from_const_slice: {let a: &[char; 2] = &['1', '2']; a},
const_from_arr: {let a: [char; 2] = ['1', '2']; a},

const_from_garb: {let a = GrowableAllocRingBuffer::from(['1', '2']); a},
const_from_arb: {let a = AllocRingBuffer::from(['1', '2']); a},
] => ConstGenericRingBuffer::<_, 2>
);

#[test]
fn test_extra_conversions_growable() {
let a: &mut [i32; 2] = &mut [1, 2];
let a = GrowableAllocRingBuffer::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);

let a: &mut [i32] = &mut [1, 2];
let a = GrowableAllocRingBuffer::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);

let mut b = VecDeque::<i32>::new();
b.push_back(1);
b.push_back(2);
assert_eq!(a.deref(), &b);
assert_eq!(a.as_ref(), &b);
}

#[test]
fn test_extra_conversions_alloc() {
let a: &mut [i32; 2] = &mut [1, 2];
let a = AllocRingBuffer::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);

let a: &mut [i32] = &mut [1, 2];
let a = AllocRingBuffer::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);
}

#[test]
fn test_extra_conversions_const() {
let a: &mut [i32; 2] = &mut [1, 2];
let a = ConstGenericRingBuffer::<_, 2>::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);

let a: &mut [i32] = &mut [1, 2];
let a = ConstGenericRingBuffer::<_, 2>::from(a);
assert_eq!(a.to_vec(), vec![1, 2]);
}

#[test]
fn test_const_generic_new_parameter() {
// Can we specify size only on the method?
let mut a = ConstGenericRingBuffer::new::<2>();
a.push(5);

// Can we specify size in both positions?
let mut a = ConstGenericRingBuffer::<i32, 50>::new::<50>();
a.push(5);

// Can we specify size only on the struct?
let mut a = ConstGenericRingBuffer::<i32, 50>::new();
a.push(5);
}

1 comment on commit cf0437d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.