Skip to content

Commit 6c0d040

Browse files
authored
Merge pull request #82 from orxfun/concurrent-iterator
Concurrent and Parallel Iterator over References
2 parents acf31d7 + ebaff31 commit 6c0d040

33 files changed

Lines changed: 2208 additions & 14 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
run: cargo clippy -- -D warnings --verbose
5353

5454
- name: Miri
55-
run: cargo +nightly miri test --verbose
55+
run: cargo +nightly miri test --lib --bins --tests --verbose
5656

5757
- name: NoStd
5858
run: cargo +nightly no-std-check

Cargo.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "orx-split-vec"
3-
version = "3.16.0"
3+
version = "3.17.0"
44
edition = "2024"
55
authors = ["orxfun <orx.ugur.arikan@gmail.com>"]
66
description = "An efficient dynamic capacity vector with pinned element guarantees."
@@ -13,13 +13,18 @@ categories = ["data-structures", "rust-patterns", "no-std"]
1313
orx-iterable = { version = "1.3.0", default-features = false }
1414
orx-pseudo-default = { version = "2.1.0", default-features = false }
1515
orx-pinned-vec = { version = "3.16.0", default-features = false }
16+
orx-concurrent-iter = { version = "2.1.0", default-features = false }
1617

1718
[[bench]]
18-
name = "serial_access"
19+
name = "par_collect_map_filter_ref"
1920
harness = false
2021

2122
[dev-dependencies]
22-
criterion = { version = "0.5", default-features = false }
23+
clap = { version = "4.5.38", features = ["derive"] }
24+
criterion = "0.6.0"
2325
rand = { version = "0.9", default-features = false }
2426
rand_chacha = { version = "0.9", default-features = false }
2527
test-case = "3.3.1"
28+
orx-concurrent-bag = "2.12.0"
29+
orx-parallel = { version = "2.0.1", default-features = false }
30+
rayon = { version = "1.10.0", default-features = false }

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ Recursive strategy is a specialized variant of the Doubling, which works identic
6363

6464
It is equivalent to Doubling strategy in terms of sequential access performance. However, due to the additional flexibility, it cannot implement `GrowthWithConstantTimeAccess`. Its random access time complexity is **O(f)** where **f** is the number of fragments in the split vector.
6565

66+
## Parallelization
67+
68+
`SplitVec` implements [`ConcurrentCollection`](https://docs.rs/orx-concurrent-iter/latest/orx_concurrent_iter/trait.ConcurrentCollection.html) for all above-mentioned growth strategies.
69+
70+
Therefore, when [orx_parallel](https://crates.io/crates/orx-parallel) crate is included, `SplitVec` also automatically implements [`ParallelizableCollection`](https://docs.rs/orx-parallel/latest/orx_parallel/trait.ParallelizableCollection.html).
71+
72+
This means that computations over the split vector can be efficiently parallelized:
73+
74+
* `split_vec.par()` returns a parallel iterator over references to its elements, and
75+
* `split_vec.into_par()` consumes the vector and returns a parallel iterator of the owned elements.
76+
77+
You may find demonstrations in [`demo_parallelization`](https://github.com/orxfun/orx-split-vec/blob/main/examples/demo_parallelization.rs) and [`bench_parallelization`](https://github.com/orxfun/orx-split-vec/blob/main/examples/bench_parallelization.rs) examples.
78+
79+
6680
## Examples
6781

6882
SplitVec api resembles and aims to cover as much as possible the standard vector's api.

benches/append.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use criterion::{BatchSize, BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
1+
use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main};
22
use orx_split_vec::*;
33
use rand::prelude::*;
44
use rand_chacha::ChaCha8Rng;
5+
use std::hint::black_box;
56

67
const NUM_APPEND_OPS: usize = 32;
78

benches/grow.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use criterion::{
2-
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId,
3-
Criterion,
2+
BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime,
43
};
54
use orx_split_vec::*;
5+
use std::hint::black_box;
66

77
fn get_value<const N: usize>(i: usize) -> [u64; N] {
88
let modulo = i % 3;
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2+
use orx_parallel::*;
3+
use orx_split_vec::*;
4+
use rand::prelude::*;
5+
use rand_chacha::ChaCha8Rng;
6+
use std::hint::black_box;
7+
8+
const TEST_LARGE_OUTPUT: bool = false;
9+
10+
const LARGE_OUTPUT_LEN: usize = match TEST_LARGE_OUTPUT {
11+
true => 64,
12+
false => 0,
13+
};
14+
const SEED: u64 = 5426;
15+
const FIB_UPPER_BOUND: u32 = 201;
16+
17+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
18+
struct Output {
19+
name: String,
20+
numbers: [i64; LARGE_OUTPUT_LEN],
21+
}
22+
23+
fn map(idx: usize) -> Output {
24+
let prefix = match idx % 7 {
25+
0 => "zero-",
26+
3 => "three-",
27+
_ => "sth-",
28+
};
29+
let fib = fibonacci(&(idx as u32));
30+
let name = format!("{}-fib-{}", prefix, fib);
31+
32+
let mut numbers = [0i64; LARGE_OUTPUT_LEN];
33+
for (i, x) in numbers.iter_mut().enumerate() {
34+
*x = match (idx * 7 + i) % 3 {
35+
0 => idx as i64 + i as i64,
36+
_ => idx as i64 - i as i64,
37+
};
38+
}
39+
40+
Output { name, numbers }
41+
}
42+
43+
fn filter(output: &Output) -> bool {
44+
let last_char = output.name.chars().last().unwrap();
45+
let last_digit: u32 = last_char.to_string().parse().unwrap();
46+
last_digit < 4
47+
}
48+
49+
fn fibonacci(n: &u32) -> u32 {
50+
let mut a = 0;
51+
let mut b = 1;
52+
for _ in 0..*n {
53+
let c = a + b;
54+
a = b;
55+
b = c;
56+
}
57+
a
58+
}
59+
60+
fn input(len: usize) -> impl Iterator<Item = usize> {
61+
let mut rng = ChaCha8Rng::seed_from_u64(SEED);
62+
(0..len).map(move |_| rng.random_range(0..FIB_UPPER_BOUND) as usize)
63+
}
64+
65+
fn seq(inputs: Vec<usize>) -> Vec<Output> {
66+
inputs.into_iter().map(map).filter(filter).collect()
67+
}
68+
69+
fn par_over_vec(inputs: Vec<usize>) -> Vec<Output> {
70+
inputs.into_par().map(map).filter(filter).collect()
71+
}
72+
73+
fn par_over_split_vec<G: ParGrowth>(inputs: SplitVec<usize, G>) -> Vec<Output> {
74+
inputs.into_par().map(map).filter(filter).collect()
75+
}
76+
77+
fn run(c: &mut Criterion) {
78+
let treatments = [65_536, 65_536 * 4];
79+
80+
#[allow(unused_mut)]
81+
let mut group = c.benchmark_group("par_collect_map_filter_owned");
82+
83+
for n in &treatments {
84+
let expected = seq(input(*n).collect());
85+
86+
let input_doubling = || input(*n).collect::<SplitVec<_, Doubling>>();
87+
88+
let input_recursive = || input(*n).collect::<SplitVec<_, Recursive>>();
89+
90+
let input_linear = || {
91+
let mut input_linear = SplitVec::with_linear_growth(10);
92+
input_linear.extend(input(*n));
93+
input_linear
94+
};
95+
96+
group.bench_with_input(BenchmarkId::new("seq", n), n, |b, _| {
97+
assert_eq!(&expected, &seq(input(*n).collect()));
98+
b.iter(|| seq(black_box(input(*n).collect())))
99+
});
100+
101+
group.bench_with_input(BenchmarkId::new("par_over_vec", n), n, |b, _| {
102+
assert_eq!(&expected, &par_over_vec(input(*n).collect()));
103+
b.iter(|| par_over_vec(black_box(input(*n).collect())))
104+
});
105+
106+
group.bench_with_input(
107+
BenchmarkId::new("par_over_split_vec_doubling", n),
108+
n,
109+
|b, _| {
110+
assert_eq!(&expected, &par_over_split_vec(input_doubling()));
111+
b.iter(|| par_over_split_vec(black_box(input_doubling())))
112+
},
113+
);
114+
115+
group.bench_with_input(
116+
BenchmarkId::new("par_over_split_vec_linear", n),
117+
n,
118+
|b, _| {
119+
assert_eq!(&expected, &par_over_split_vec(input_linear()));
120+
b.iter(|| par_over_split_vec(black_box(input_linear())))
121+
},
122+
);
123+
124+
group.bench_with_input(
125+
BenchmarkId::new("par_over_split_vec_recursive", n),
126+
n,
127+
|b, _| {
128+
assert_eq!(&expected, &par_over_split_vec(input_recursive()));
129+
b.iter(|| par_over_split_vec(black_box(input_recursive())))
130+
},
131+
);
132+
}
133+
134+
group.finish();
135+
}
136+
137+
criterion_group!(benches, run);
138+
criterion_main!(benches);
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2+
use orx_parallel::*;
3+
use orx_split_vec::*;
4+
use rand::prelude::*;
5+
use rand_chacha::ChaCha8Rng;
6+
use std::hint::black_box;
7+
8+
const TEST_LARGE_OUTPUT: bool = false;
9+
10+
const LARGE_OUTPUT_LEN: usize = match TEST_LARGE_OUTPUT {
11+
true => 64,
12+
false => 0,
13+
};
14+
const SEED: u64 = 5426;
15+
const FIB_UPPER_BOUND: u32 = 201;
16+
17+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
18+
struct Output {
19+
name: String,
20+
numbers: [i64; LARGE_OUTPUT_LEN],
21+
}
22+
23+
fn map(idx: &usize) -> Output {
24+
let idx = *idx;
25+
let prefix = match idx % 7 {
26+
0 => "zero-",
27+
3 => "three-",
28+
_ => "sth-",
29+
};
30+
let fib = fibonacci(&(idx as u32));
31+
let name = format!("{}-fib-{}", prefix, fib);
32+
33+
let mut numbers = [0i64; LARGE_OUTPUT_LEN];
34+
for (i, x) in numbers.iter_mut().enumerate() {
35+
*x = match (idx * 7 + i) % 3 {
36+
0 => idx as i64 + i as i64,
37+
_ => idx as i64 - i as i64,
38+
};
39+
}
40+
41+
Output { name, numbers }
42+
}
43+
44+
fn filter(output: &Output) -> bool {
45+
let last_char = output.name.chars().last().unwrap();
46+
let last_digit: u32 = last_char.to_string().parse().unwrap();
47+
last_digit < 4
48+
}
49+
50+
fn fibonacci(n: &u32) -> u32 {
51+
let mut a = 0;
52+
let mut b = 1;
53+
for _ in 0..*n {
54+
let c = a + b;
55+
a = b;
56+
b = c;
57+
}
58+
a
59+
}
60+
61+
fn get_input(len: usize) -> impl Iterator<Item = usize> {
62+
let mut rng = ChaCha8Rng::seed_from_u64(SEED);
63+
(0..len).map(move |_| rng.random_range(0..FIB_UPPER_BOUND) as usize)
64+
}
65+
66+
fn seq(inputs: &[usize]) -> Vec<Output> {
67+
inputs.iter().map(map).filter(filter).collect()
68+
}
69+
70+
fn par_over_vec(inputs: &[usize]) -> Vec<Output> {
71+
inputs.par().map(map).filter(filter).collect()
72+
}
73+
74+
fn par_over_split_vec<G: ParGrowth>(inputs: &SplitVec<usize, G>) -> Vec<Output> {
75+
inputs.par().map(map).filter(filter).collect()
76+
}
77+
78+
fn run(c: &mut Criterion) {
79+
let treatments = [65_536, 65_536 * 4];
80+
81+
#[allow(unused_mut)]
82+
let mut group = c.benchmark_group("par_collect_map_filter_ref");
83+
84+
for n in &treatments {
85+
let input: Vec<_> = get_input(*n).collect();
86+
let expected = seq(&input);
87+
88+
let input_doubling = get_input(*n).collect::<SplitVec<_, Doubling>>();
89+
90+
let input_recursive = get_input(*n).collect::<SplitVec<_, Recursive>>();
91+
92+
let input_linear = {
93+
let mut input_linear = SplitVec::with_linear_growth(10);
94+
input_linear.extend_from_slice(&input);
95+
input_linear
96+
};
97+
98+
group.bench_with_input(BenchmarkId::new("seq", n), n, |b, _| {
99+
assert_eq!(&expected, &seq(&input));
100+
b.iter(|| seq(black_box(&input)))
101+
});
102+
103+
group.bench_with_input(BenchmarkId::new("par_over_vec", n), n, |b, _| {
104+
assert_eq!(&expected, &par_over_vec(&input));
105+
b.iter(|| par_over_vec(black_box(&input)))
106+
});
107+
108+
group.bench_with_input(
109+
BenchmarkId::new("par_over_split_vec_doubling", n),
110+
n,
111+
|b, _| {
112+
assert_eq!(&expected, &par_over_split_vec(&input_doubling));
113+
b.iter(|| par_over_split_vec(black_box(&input_doubling)))
114+
},
115+
);
116+
117+
group.bench_with_input(
118+
BenchmarkId::new("par_over_split_vec_linear", n),
119+
n,
120+
|b, _| {
121+
assert_eq!(&expected, &par_over_split_vec(&input_linear));
122+
b.iter(|| par_over_split_vec(black_box(&input_linear)))
123+
},
124+
);
125+
126+
group.bench_with_input(
127+
BenchmarkId::new("par_over_split_vec_recursive", n),
128+
n,
129+
|b, _| {
130+
assert_eq!(&expected, &par_over_split_vec(&input_recursive));
131+
b.iter(|| par_over_split_vec(black_box(&input_recursive)))
132+
},
133+
);
134+
}
135+
136+
group.finish();
137+
}
138+
139+
criterion_group!(benches, run);
140+
criterion_main!(benches);

benches/random_access.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use criterion::{
2-
BenchmarkGroup, BenchmarkId, Criterion, black_box, criterion_group, criterion_main,
3-
measurement::WallTime,
2+
BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime,
43
};
54
use orx_split_vec::*;
65
use rand::prelude::*;
76
use rand_chacha::ChaCha8Rng;
7+
use std::hint::black_box;
88

99
fn get_indices(n: usize) -> Vec<usize> {
1010
let mut rng = ChaCha8Rng::seed_from_u64(7541);

benches/serial_access.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use criterion::{
2-
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId,
3-
Criterion,
2+
BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime,
43
};
54
use orx_split_vec::*;
5+
use std::hint::black_box;
66

77
fn get_value<const N: usize>(i: usize) -> [u64; N] {
88
let modulo = i % 3;

0 commit comments

Comments
 (0)