Skip to content

Commit 370fb44

Browse files
author
TechnoPorg
committed
Faster conversions to doubling vectors
1 parent c51f12b commit 370fb44

5 files changed

Lines changed: 133 additions & 20 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ orx-concurrent-iter = { version = "3.1.0", default-features = false }
1919
name = "serial_access"
2020
harness = false
2121

22+
[[bench]]
23+
name = "from"
24+
harness = false
25+
2226
[dev-dependencies]
2327
criterion = "0.7.0"
2428
rand = { version = "0.9.2", default-features = false }

benches/from.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use criterion::{
2+
BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime,
3+
};
4+
use orx_split_vec::{Doubling, Linear, Recursive, SplitVec};
5+
use std::hint::black_box;
6+
7+
fn get_value<const N: usize>(i: usize) -> [u64; N] {
8+
let modulo = i % 3;
9+
if modulo == 0 {
10+
[i as u64; N]
11+
} else if modulo == 1 {
12+
[(i + 1) as u64; N]
13+
} else {
14+
[(i + 2) as u64; N]
15+
}
16+
}
17+
18+
fn doubling_from_std_vec<T: Clone, F: Fn(usize) -> T>(n: usize, value: F) -> SplitVec<T, Doubling> {
19+
let mut std_vec = Vec::new();
20+
for i in 0..n {
21+
std_vec.push(value(i));
22+
}
23+
SplitVec::from(std_vec)
24+
}
25+
26+
fn linear_from_std_vec<T, F: Fn(usize) -> T>(n: usize, value: F) -> SplitVec<T, Linear> {
27+
let mut std_vec = Vec::new();
28+
for i in 0..n {
29+
std_vec.push(value(i));
30+
}
31+
SplitVec::from(std_vec)
32+
}
33+
34+
fn recursive_from_std_vec<T: Clone, F: Fn(usize) -> T>(
35+
n: usize,
36+
value: F,
37+
) -> SplitVec<T, Recursive> {
38+
let mut std_vec = Vec::new();
39+
for i in 0..n {
40+
std_vec.push(value(i));
41+
}
42+
SplitVec::from(std_vec)
43+
}
44+
45+
fn test_for_type<T: Clone>(
46+
group: &mut BenchmarkGroup<'_, WallTime>,
47+
num_u64s: usize,
48+
treatments: &[usize],
49+
value: fn(usize) -> T,
50+
) {
51+
for n in treatments {
52+
let treatment = format!("n={},elem-type=[u64;{}]", n, num_u64s);
53+
54+
group.bench_with_input(
55+
BenchmarkId::new("doubling_from_std_vec", &treatment),
56+
n,
57+
|b, _| b.iter(|| doubling_from_std_vec(black_box(*n), value)),
58+
);
59+
60+
group.bench_with_input(
61+
BenchmarkId::new("linear_from_std_vec", &treatment),
62+
n,
63+
|b, _| b.iter(|| linear_from_std_vec(black_box(*n), value)),
64+
);
65+
group.bench_with_input(
66+
BenchmarkId::new("recursive_from_std_vec", &treatment),
67+
n,
68+
|b, _| b.iter(|| recursive_from_std_vec(black_box(*n), value)),
69+
);
70+
}
71+
}
72+
73+
fn bench_from(c: &mut Criterion) {
74+
let treatments = vec![1_024, 16_384, 262_144, 4_194_304];
75+
76+
let mut group = c.benchmark_group("from");
77+
78+
const N: usize = 16;
79+
80+
test_for_type(&mut group, N, &treatments, get_value::<N>);
81+
82+
group.finish();
83+
}
84+
85+
criterion_group!(benches, bench_from);
86+
criterion_main!(benches);

src/growth/doubling/from.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use core::{cmp::min, mem::MaybeUninit, ptr::copy_nonoverlapping};
2+
13
use super::constants::CUMULATIVE_CAPACITIES;
2-
use crate::{Doubling, Fragment, SplitVec};
4+
use crate::{Doubling, Fragment, SplitVec, growth::doubling::constants::CAPACITIES};
35
use alloc::vec::Vec;
46

5-
impl<T: Clone> From<Vec<T>> for SplitVec<T, Doubling> {
7+
impl<T> From<Vec<T>> for SplitVec<T, Doubling> {
68
/// Converts a `Vec` into a `SplitVec`.
79
///
810
/// # Examples
@@ -19,8 +21,9 @@ impl<T: Clone> From<Vec<T>> for SplitVec<T, Doubling> {
1921
/// assert_eq!(1, split_vec.fragments().len());
2022
/// assert!(vec_capacity <= split_vec.capacity());
2123
/// ```
22-
fn from(value: Vec<T>) -> Self {
24+
fn from(mut value: Vec<T>) -> Self {
2325
let len = value.len();
26+
// Number of fragments to create
2427
let f = CUMULATIVE_CAPACITIES
2528
.iter()
2629
.enumerate()
@@ -29,26 +32,33 @@ impl<T: Clone> From<Vec<T>> for SplitVec<T, Doubling> {
2932
.expect("overflow");
3033

3134
let mut fragments = Vec::with_capacity(f + 1);
32-
let mut original_idx = 0;
35+
let fragments_init = fragments.spare_capacity_mut();
3336
let mut remaining_len = len;
34-
let mut curr_f = 1;
37+
let mut curr_f = f;
3538
while remaining_len > 0 {
36-
let capacity = &CUMULATIVE_CAPACITIES[curr_f];
37-
let mut fragment = Fragment::new(*capacity);
38-
39-
let copy_len = if capacity <= &remaining_len {
40-
*capacity
41-
} else {
42-
remaining_len
43-
};
44-
45-
fragment.extend_from_slice(&value[original_idx..(original_idx + copy_len)]);
46-
47-
original_idx += copy_len;
39+
curr_f -= 1;
40+
let capacity = CAPACITIES[curr_f];
41+
// for example, if the current fragment has a capacity of 8 but there are only 5 elements to copy,
42+
// we want the copy length to only be 1
43+
let copy_len = min(remaining_len - CUMULATIVE_CAPACITIES[curr_f], capacity);
4844
remaining_len -= copy_len;
49-
fragments.push(fragment);
50-
curr_f += 1;
45+
46+
// This is adapted from Vec::split_off, with the difference that it
47+
// reserves the full capacity first to avoid extra allocations
48+
let mut fragment_data = Vec::with_capacity(capacity);
49+
unsafe {
50+
value.set_len(remaining_len);
51+
fragment_data.set_len(copy_len);
52+
copy_nonoverlapping(
53+
value.as_ptr().add(remaining_len),
54+
fragment_data.as_mut_ptr(),
55+
copy_len,
56+
);
57+
}
58+
fragments_init[curr_f] = MaybeUninit::new(Fragment::from(fragment_data));
5159
}
60+
debug_assert_eq!(curr_f, 0);
61+
unsafe { fragments.set_len(f) };
5262

5363
Self::from_raw_parts(len, fragments, Doubling)
5464
}

src/growth/doubling/tests/from.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,16 @@ fn from_vec_medium() {
1414
validate_clone(vec, split_vec);
1515
}
1616
}
17+
18+
#[test]
19+
fn from_same_as_push() {
20+
for len in 0..135 {
21+
let vec: Vec<_> = (0..len).collect();
22+
let split_vec_from: SplitVec<_, Doubling> = vec.clone().into();
23+
let mut split_vec_manual = SplitVec::new();
24+
for item in vec {
25+
split_vec_manual.push(item);
26+
}
27+
assert_eq!(split_vec_from, split_vec_manual);
28+
}
29+
}

src/growth/recursive/from.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl<T> From<SplitVec<T, Linear>> for SplitVec<T, Recursive> {
5555
}
5656
}
5757

58-
impl<T: Clone> From<Vec<T>> for SplitVec<T, Recursive> {
58+
impl<T> From<Vec<T>> for SplitVec<T, Recursive> {
5959
/// Converts a `Vec` into a `SplitVec`.
6060
///
6161
/// # Examples

0 commit comments

Comments
 (0)