Skip to content

Commit e91cd1c

Browse files
authored
Merge pull request #44 from orxfun/parallelization
Parallelization
2 parents fea4d8c + 4dc144e commit e91cd1c

40 files changed

+930
-54
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ jobs:
3939
- name: Build-32bit
4040
run: cargo build --verbose --target i686-unknown-linux-musl
4141
- name: Build-wasm
42-
run: cargo build --verbose --target wasm32v1-none
42+
run: cargo build --verbose --no-default-features --target wasm32v1-none
4343

4444
- name: Test
4545
run: cargo test --verbose
4646
- name: Test-32bit
4747
run: cargo test --verbose --target i686-unknown-linux-musl
4848
- name: Check-wasm
49-
run: cargo check --verbose --target wasm32v1-none
49+
run: cargo check --verbose --no-default-features --target wasm32v1-none
5050

5151
- name: Clippy
5252
run: cargo clippy -- -D warnings --verbose
@@ -55,4 +55,4 @@ jobs:
5555
run: cargo +nightly miri test --verbose
5656

5757
- name: NoStd
58-
run: cargo +nightly no-std-check
58+
run: cargo +nightly no-std-check --no-default-features

Cargo.toml

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "orx-linked-list"
3-
version = "3.8.0"
3+
version = "3.9.0"
44
edition = "2024"
55
authors = ["orxfun <[email protected]>"]
66
description = "A linked list implementation with unique features and an extended list of constant time methods providing high performance traversals and mutations."
@@ -12,21 +12,23 @@ categories = ["data-structures", "rust-patterns", "no-std"]
1212
[dependencies]
1313
orx-iterable = { version = "1.3.0", default-features = false }
1414
orx-pseudo-default = { version = "2.1.0", default-features = false }
15-
orx-pinned-vec = "3.16.0"
16-
orx-fixed-vec = "3.16.0"
17-
orx-split-vec = "3.16.0"
18-
orx-selfref-col = "2.8.0"
19-
15+
orx-pinned-vec = { version = "3.16.0", default-features = false }
16+
orx-fixed-vec = { version = "3.17.0", default-features = false }
17+
orx-split-vec = { version = "3.17.0", default-features = false }
18+
orx-concurrent-iter = { version = "2.1.0", default-features = false }
19+
orx-selfref-col = { version = "2.9.0", default-features = false }
20+
orx-parallel = { version = "2.1.0", optional = true }
2021

2122
[dev-dependencies]
22-
clap = { version = "4.5.35", features = ["derive"] }
23-
criterion = "0.5"
24-
rand = "0.9.0"
25-
rand_chacha = "0.9.0"
23+
clap = { version = "4.5.38", features = ["derive"] }
24+
criterion = "0.5.1"
25+
rand = "0.9"
26+
rand_chacha = "0.9"
27+
rayon = { version = "1.10.0" }
2628
test-case = "3.3.1"
2729

2830
[features]
29-
default = []
31+
default = ["orx-parallel"]
3032
validation = []
3133

3234
[[bench]]

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ A linked list implementation with unique features and an extended list of consta
88

99
Both doubly and singly lists are provided as generic variants of the core struct `List`. It is sufficient to know the four variants:
1010
* [`DoublyList`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.DoublyList.html) and [`SinglyList`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.SinglyList.html)
11-
* [`DoublyListLazy`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.DoublyListLazy.html) and [`SinglyListLazy`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.SinglyListLazy.html)
12-
* *Lazy* suffix corresponds to lazy memory reclaim and will be explained in the indices section.
11+
* [`DoublyListLazy`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.DoublyListLazy.html) and [`SinglyListLazy`](https://docs.rs/orx-linked-list/latest/orx_linked_list/type.SinglyListLazy.html) (*Lazy* suffix corresponds to lazy memory reclaim and will be explained in the indices section)
1312

14-
Some notable features are as follows.
13+
> **no-std**: This crate supports **no-std**; however, *std* is added due to the default [**orx-parallel**](https://crates.io/crates/orx-parallel) feature. Please include with **no-default-features** for no-std use cases: `cargo add orx-linked-list --no-default-features`.
1514
1615
## Efficiency
1716

@@ -64,6 +63,24 @@ The significance of improvement can further be increased by using `DoublyList::i
6463

6564
</details>
6665

66+
## Parallelization
67+
68+
When [orx-parallel](https://crates.io/crates/orx-parallel) feature is used (by default), computations over `LinkedList` elements can be efficiently parallelized.
69+
70+
Parallel computation is defined by chained iterator methods, simply by replacing `iter_x` with `par_x`, and `into_iter_x` by `into_par_x`.
71+
72+
You may find demonstrations in [`demo_parallelization`](https://github.com/orxfun/orx-linked-list/blob/main/examples/demo_parallelization.rs)
73+
74+
Significant performance improvements can be achieved by replacing `iter_x` with `par_x`, as can be tested with the benchmark file [parallelization_ref.rs](https://github.com/orxfun/orx-linked-list/blob/main/benches/parallelization_ref.rs), or with the lightweight benchmark example [`bench_parallelization`](https://github.com/orxfun/orx-linked-list/blob/main/examples/bench_parallelization.rs):
75+
76+
```bash
77+
Sequential computation over std::collections::LinkedList : 12.56s
78+
Sequential computation over DoublyList : 11.78s
79+
Parallelized over DoublyList using orx_parallel : 2.93s
80+
```
81+
82+
*The suffix "_x" indicates that the iterators yield elements in arbitrary order, rather than from front to back. Parallelization of all iterations defined in the next section is in progress.*
83+
6784
## Iterations
6885

6986
Linked lists are all about traversal. Therefore, the linked lists defined in this crate, especially the **DoublyList**, provide various useful ways to iterate over the data:

benches/parallelization_owned.rs

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
2+
use orx_linked_list::*;
3+
#[cfg(feature = "orx-parallel")]
4+
use orx_parallel::ParIter;
5+
use orx_selfref_col::MemoryPolicy;
6+
use rand::prelude::*;
7+
use rand_chacha::ChaCha8Rng;
8+
use rayon::iter::{IntoParallelIterator, ParallelIterator};
9+
10+
#[derive(Clone)]
11+
enum Action {
12+
PushBack(String),
13+
PushFront(String),
14+
PopBack,
15+
PopFront,
16+
}
17+
18+
fn get_test_data(n: usize) -> Vec<Action> {
19+
let mut rng = ChaCha8Rng::seed_from_u64(56456);
20+
let mut vec: Vec<_> = (0..n)
21+
.map(|_| match rng.random::<f32>() {
22+
x if x < 0.5 => Action::PushBack(rng.random_range(0..n).to_string()),
23+
_ => Action::PushFront(rng.random_range(0..n).to_string()),
24+
})
25+
.collect();
26+
for _ in 0..2 * n {
27+
let action = match rng.random::<f32>() {
28+
x if x < 0.25 => Action::PushBack(rng.random_range(0..n).to_string()),
29+
x if x < 0.50 => Action::PushFront(rng.random_range(0..n).to_string()),
30+
x if x < 0.75 => Action::PopBack,
31+
_ => Action::PopFront,
32+
};
33+
vec.push(action)
34+
}
35+
vec
36+
}
37+
38+
fn fibonacci(n: i64) -> i64 {
39+
let mut a = 0;
40+
let mut b = 1;
41+
for _ in 0..n {
42+
let c = a + b;
43+
a = b;
44+
b = c;
45+
}
46+
a
47+
}
48+
49+
// variants
50+
51+
fn fill_doubly_list<M: MemoryPolicy<Doubly<String>>>(
52+
actions: &[Action],
53+
list: &mut List<Doubly<String>, M>,
54+
) {
55+
for action in actions {
56+
match action {
57+
Action::PushBack(x) => {
58+
list.push_back(x.clone());
59+
}
60+
Action::PushFront(x) => {
61+
list.push_front(x.clone());
62+
}
63+
Action::PopBack => {
64+
_ = list.pop_back();
65+
}
66+
Action::PopFront => {
67+
_ = list.pop_front();
68+
}
69+
}
70+
}
71+
}
72+
73+
fn doubly_iter<M: MemoryPolicy<Doubly<String>>>(list: List<Doubly<String>, M>) -> Vec<char> {
74+
list.into_iter()
75+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
76+
.map(|x| x.chars().last().unwrap())
77+
.collect()
78+
}
79+
80+
fn doubly_iter_x<M: MemoryPolicy<Doubly<String>>>(list: List<Doubly<String>, M>) -> Vec<char> {
81+
list.into_iter_x()
82+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
83+
.map(|x| x.chars().last().unwrap())
84+
.collect()
85+
}
86+
87+
#[cfg(feature = "orx-parallel")]
88+
fn doubly_par_x<M: MemoryPolicy<Doubly<String>>>(list: List<Doubly<String>, M>) -> Vec<char> {
89+
list.into_par_x()
90+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
91+
.map(|x| x.chars().last().unwrap())
92+
.collect()
93+
}
94+
95+
fn fill_std_linked_list(actions: &[Action], list: &mut std::collections::LinkedList<String>) {
96+
for action in actions {
97+
match action {
98+
Action::PushBack(x) => {
99+
list.push_back(x.clone());
100+
}
101+
Action::PushFront(x) => {
102+
list.push_front(x.clone());
103+
}
104+
Action::PopBack => {
105+
_ = list.pop_back();
106+
}
107+
Action::PopFront => {
108+
_ = list.pop_front();
109+
}
110+
}
111+
}
112+
}
113+
114+
fn std_linked_list(list: std::collections::LinkedList<String>) -> Vec<char> {
115+
list.into_iter()
116+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
117+
.map(|x| x.chars().last().unwrap())
118+
.collect()
119+
}
120+
121+
fn std_linked_list_rayon(list: std::collections::LinkedList<String>) -> Vec<char> {
122+
list.into_par_iter()
123+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
124+
.map(|x| x.chars().last().unwrap())
125+
.collect()
126+
}
127+
128+
fn fill_vec_deque(actions: &[Action], list: &mut std::collections::VecDeque<String>) {
129+
for action in actions {
130+
match action {
131+
Action::PushBack(x) => {
132+
list.push_back(x.clone());
133+
}
134+
Action::PushFront(x) => {
135+
list.push_front(x.clone());
136+
}
137+
Action::PopBack => {
138+
_ = list.pop_back();
139+
}
140+
Action::PopFront => {
141+
_ = list.pop_front();
142+
}
143+
}
144+
}
145+
}
146+
147+
fn std_vec_deque(list: std::collections::VecDeque<String>) -> Vec<char> {
148+
list.into_iter()
149+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
150+
.map(|x| x.chars().last().unwrap())
151+
.collect()
152+
}
153+
154+
fn std_vec_deque_rayon(list: std::collections::VecDeque<String>) -> Vec<char> {
155+
list.into_par_iter()
156+
.filter(|x| fibonacci(x.parse::<i64>().unwrap() % 1000) % 2 == 0)
157+
.map(|x| x.chars().last().unwrap())
158+
.collect()
159+
}
160+
161+
fn bench(c: &mut Criterion) {
162+
let treatments = vec![1_024 * 64 * 4];
163+
164+
let mut group = c.benchmark_group("parallelization_owned");
165+
166+
for n in &treatments {
167+
let data = get_test_data(*n);
168+
169+
let mut std_list = std::collections::LinkedList::new();
170+
fill_std_linked_list(&data, &mut std_list);
171+
let expected = std_linked_list(std_list.clone());
172+
173+
group.bench_with_input(BenchmarkId::new("LinkedList::into_iter", n), n, |b, _| {
174+
let mut std_list = std::collections::LinkedList::new();
175+
fill_std_linked_list(&data, &mut std_list);
176+
let result = std_linked_list(std_list.clone());
177+
assert_eq!(result, expected);
178+
179+
b.iter(|| std_linked_list(std_list.clone()))
180+
});
181+
182+
group.bench_with_input(
183+
BenchmarkId::new("LinkedList::into_par_iter (rayon)", n),
184+
n,
185+
|b, _| {
186+
let mut std_list = std::collections::LinkedList::new();
187+
fill_std_linked_list(&data, &mut std_list);
188+
let result = std_linked_list_rayon(std_list.clone());
189+
assert_eq!(result, expected);
190+
191+
b.iter(|| std_linked_list_rayon(std_list.clone()))
192+
},
193+
);
194+
195+
group.bench_with_input(BenchmarkId::new("VecDeque::into_iter", n), n, |b, _| {
196+
let mut list = std::collections::VecDeque::new();
197+
fill_vec_deque(&data, &mut list);
198+
let result = std_vec_deque(list.clone());
199+
assert_eq!(result, expected);
200+
201+
b.iter(|| std_vec_deque(list.clone()))
202+
});
203+
204+
group.bench_with_input(
205+
BenchmarkId::new("VecDeque::into_par_iter (rayon)", n),
206+
n,
207+
|b, _| {
208+
let mut list = std::collections::VecDeque::new();
209+
fill_vec_deque(&data, &mut list);
210+
let result = std_vec_deque_rayon(list.clone());
211+
assert_eq!(result, expected);
212+
213+
b.iter(|| std_vec_deque_rayon(list.clone()))
214+
},
215+
);
216+
217+
group.bench_with_input(BenchmarkId::new("DoublyList::into_iter", n), n, |b, _| {
218+
let mut list = DoublyList::new();
219+
fill_doubly_list(&data, &mut list);
220+
let result = doubly_iter(list.clone());
221+
assert_eq!(result, expected);
222+
223+
b.iter(|| doubly_iter(list.clone()))
224+
});
225+
226+
group.bench_with_input(BenchmarkId::new("DoublyList::into_iter_x", n), n, |b, _| {
227+
let mut list = DoublyList::new();
228+
fill_doubly_list(&data, &mut list);
229+
let result = doubly_iter_x(list.clone());
230+
assert_eq!(result, expected);
231+
232+
b.iter(|| doubly_iter_x(list.clone()))
233+
});
234+
235+
#[cfg(feature = "orx-parallel")]
236+
group.bench_with_input(
237+
BenchmarkId::new("DoublyList::into_par_x (orx-parallel)", n),
238+
n,
239+
|b, _| {
240+
let mut list = DoublyList::new();
241+
fill_doubly_list(&data, &mut list);
242+
let result = doubly_par_x(list.clone());
243+
assert_eq!(result, expected);
244+
245+
b.iter(|| doubly_par_x(list.clone()))
246+
},
247+
);
248+
}
249+
250+
group.finish();
251+
}
252+
253+
criterion_group!(benches, bench);
254+
criterion_main!(benches);

0 commit comments

Comments
 (0)