From 94808d5142647990cb7066af52c37bc07163101f Mon Sep 17 00:00:00 2001 From: TechnoPorg Date: Sun, 5 Oct 2025 15:13:54 -0400 Subject: [PATCH] Faster conversions to doubling vectors --- Cargo.toml | 4 ++ benches/from.rs | 73 +++++++++++++++++++++++++++++++ src/growth/doubling/from.rs | 48 ++++++++++++-------- src/growth/doubling/tests/from.rs | 13 ++++++ src/growth/recursive/from.rs | 2 +- 5 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 benches/from.rs diff --git a/Cargo.toml b/Cargo.toml index 8dc4047..724c0d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,10 @@ orx-concurrent-iter = { version = "3.1.0", default-features = false } name = "serial_access" harness = false +[[bench]] +name = "from" +harness = false + [dev-dependencies] criterion = "0.7.0" rand = { version = "0.9.2", default-features = false } diff --git a/benches/from.rs b/benches/from.rs new file mode 100644 index 0000000..5df47a8 --- /dev/null +++ b/benches/from.rs @@ -0,0 +1,73 @@ +use criterion::{ + BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime, +}; +use orx_split_vec::{Doubling, Linear, Recursive, SplitVec}; +use std::hint::black_box; + +fn get_vec(len: usize) -> Vec { + let modulo = len % 3; + if modulo == 0 { + vec![len as u64; len] + } else if modulo == 1 { + vec![(len + 1) as u64; len] + } else { + vec![(len + 2) as u64; len] + } +} + +fn doubling_from_std_vec(std_vec: Vec) -> SplitVec { + SplitVec::from(std_vec) +} + +fn linear_from_std_vec(std_vec: Vec) -> SplitVec { + SplitVec::from(std_vec) +} + +fn recursive_from_std_vec( + std_vec: Vec +) -> SplitVec { + SplitVec::from(std_vec) +} + +fn test_for_type( + group: &mut BenchmarkGroup<'_, WallTime>, + num_u64s: usize, + treatments: &[usize], + value: fn(usize) -> Vec, +) { + for n in treatments { + let treatment = format!("n={},elem-type=[u64;{}]", n, num_u64s); + + group.bench_with_input( + BenchmarkId::new("doubling_from_std_vec", &treatment), + n, + |b, _| b.iter(|| doubling_from_std_vec(value(black_box(*n)))), + ); + + group.bench_with_input( + BenchmarkId::new("linear_from_std_vec", &treatment), + n, + |b, _| b.iter(|| linear_from_std_vec(value(black_box(*n)))), + ); + group.bench_with_input( + BenchmarkId::new("recursive_from_std_vec", &treatment), + n, + |b, _| b.iter(|| recursive_from_std_vec(value(black_box(*n))), + )); + } +} + +fn bench_from(c: &mut Criterion) { + let treatments = vec![1_024, 16_384, 262_144, 4_194_304]; + + let mut group = c.benchmark_group("from"); + + const N: usize = 16; + + test_for_type(&mut group, N, &treatments, get_vec); + + group.finish(); +} + +criterion_group!(benches, bench_from); +criterion_main!(benches); diff --git a/src/growth/doubling/from.rs b/src/growth/doubling/from.rs index 287e395..c92f9a8 100644 --- a/src/growth/doubling/from.rs +++ b/src/growth/doubling/from.rs @@ -1,8 +1,10 @@ +use core::{cmp::min, mem::MaybeUninit, ptr::copy_nonoverlapping}; + use super::constants::CUMULATIVE_CAPACITIES; -use crate::{Doubling, Fragment, SplitVec}; +use crate::{Doubling, Fragment, SplitVec, growth::doubling::constants::CAPACITIES}; use alloc::vec::Vec; -impl From> for SplitVec { +impl From> for SplitVec { /// Converts a `Vec` into a `SplitVec`. /// /// # Examples @@ -19,8 +21,9 @@ impl From> for SplitVec { /// assert_eq!(1, split_vec.fragments().len()); /// assert!(vec_capacity <= split_vec.capacity()); /// ``` - fn from(value: Vec) -> Self { + fn from(mut value: Vec) -> Self { let len = value.len(); + // Number of fragments to create let f = CUMULATIVE_CAPACITIES .iter() .enumerate() @@ -29,26 +32,33 @@ impl From> for SplitVec { .expect("overflow"); let mut fragments = Vec::with_capacity(f + 1); - let mut original_idx = 0; + let fragments_init = fragments.spare_capacity_mut(); let mut remaining_len = len; - let mut curr_f = 1; + let mut curr_f = f; while remaining_len > 0 { - let capacity = &CUMULATIVE_CAPACITIES[curr_f]; - let mut fragment = Fragment::new(*capacity); - - let copy_len = if capacity <= &remaining_len { - *capacity - } else { - remaining_len - }; - - fragment.extend_from_slice(&value[original_idx..(original_idx + copy_len)]); - - original_idx += copy_len; + curr_f -= 1; + let capacity = CAPACITIES[curr_f]; + // for example, if the current fragment has a capacity of 8 but there are only 5 elements to copy, + // we want the copy length to only be 1 + let copy_len = min(remaining_len - CUMULATIVE_CAPACITIES[curr_f], capacity); remaining_len -= copy_len; - fragments.push(fragment); - curr_f += 1; + + // This is adapted from Vec::split_off, with the difference that it + // reserves the full capacity first to avoid extra allocations + let mut fragment_data = Vec::with_capacity(capacity); + unsafe { + value.set_len(remaining_len); + fragment_data.set_len(copy_len); + copy_nonoverlapping( + value.as_ptr().add(remaining_len), + fragment_data.as_mut_ptr(), + copy_len, + ); + } + fragments_init[curr_f] = MaybeUninit::new(Fragment::from(fragment_data)); } + debug_assert_eq!(curr_f, 0); + unsafe { fragments.set_len(f) }; Self::from_raw_parts(len, fragments, Doubling) } diff --git a/src/growth/doubling/tests/from.rs b/src/growth/doubling/tests/from.rs index 358b086..1d65ad2 100644 --- a/src/growth/doubling/tests/from.rs +++ b/src/growth/doubling/tests/from.rs @@ -14,3 +14,16 @@ fn from_vec_medium() { validate_clone(vec, split_vec); } } + +#[test] +fn from_same_as_push() { + for len in 0..135 { + let vec: Vec<_> = (0..len).collect(); + let split_vec_from: SplitVec<_, Doubling> = vec.clone().into(); + let mut split_vec_manual = SplitVec::new(); + for item in vec { + split_vec_manual.push(item); + } + assert_eq!(split_vec_from, split_vec_manual); + } +} diff --git a/src/growth/recursive/from.rs b/src/growth/recursive/from.rs index 0161e9d..195b57e 100644 --- a/src/growth/recursive/from.rs +++ b/src/growth/recursive/from.rs @@ -55,7 +55,7 @@ impl From> for SplitVec { } } -impl From> for SplitVec { +impl From> for SplitVec { /// Converts a `Vec` into a `SplitVec`. /// /// # Examples