diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fbd6a7..2db7166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,14 +39,14 @@ jobs: - name: Build-32bit run: cargo build --verbose --target i686-unknown-linux-musl - name: Build-wasm - run: cargo build --verbose --target wasm32v1-none + run: cargo build --verbose --no-default-features --target wasm32v1-none - name: Test run: cargo test --verbose - name: Test-32bit run: cargo test --verbose --target i686-unknown-linux-musl - name: Check-wasm - run: cargo check --verbose --target wasm32v1-none + run: cargo check --verbose --no-default-features --target wasm32v1-none - name: Clippy run: cargo clippy -- -D warnings --verbose @@ -55,4 +55,4 @@ jobs: run: cargo +nightly miri test --verbose - name: NoStd - run: cargo +nightly no-std-check + run: cargo +nightly no-std-check --no-default-features diff --git a/Cargo.toml b/Cargo.toml index bbfa0a7..b4a11a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orx-linked-list" -version = "3.8.0" +version = "3.9.0" edition = "2024" authors = ["orxfun "] 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"] [dependencies] orx-iterable = { version = "1.3.0", default-features = false } orx-pseudo-default = { version = "2.1.0", default-features = false } -orx-pinned-vec = "3.16.0" -orx-fixed-vec = "3.16.0" -orx-split-vec = "3.16.0" -orx-selfref-col = "2.8.0" - +orx-pinned-vec = { version = "3.16.0", default-features = false } +orx-fixed-vec = { version = "3.17.0", default-features = false } +orx-split-vec = { version = "3.17.0", default-features = false } +orx-concurrent-iter = { version = "2.1.0", default-features = false } +orx-selfref-col = { version = "2.9.0", default-features = false } +orx-parallel = { version = "2.1.0", optional = true } [dev-dependencies] -clap = { version = "4.5.35", features = ["derive"] } -criterion = "0.5" -rand = "0.9.0" -rand_chacha = "0.9.0" +clap = { version = "4.5.38", features = ["derive"] } +criterion = "0.5.1" +rand = "0.9" +rand_chacha = "0.9" +rayon = { version = "1.10.0" } test-case = "3.3.1" [features] -default = [] +default = ["orx-parallel"] validation = [] [[bench]] diff --git a/README.md b/README.md index e8d2683..260c5c4 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ A linked list implementation with unique features and an extended list of consta Both doubly and singly lists are provided as generic variants of the core struct `List`. It is sufficient to know the four variants: * [`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) -* [`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. +* [`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) -Some notable features are as follows. +> **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`. ## Efficiency @@ -64,6 +63,24 @@ The significance of improvement can further be increased by using `DoublyList::i +## Parallelization + +When [orx-parallel](https://crates.io/crates/orx-parallel) feature is used (by default), computations over `LinkedList` elements can be efficiently parallelized. + +Parallel computation is defined by chained iterator methods, simply by replacing `iter_x` with `par_x`, and `into_iter_x` by `into_par_x`. + +You may find demonstrations in [`demo_parallelization`](https://github.com/orxfun/orx-linked-list/blob/main/examples/demo_parallelization.rs) + +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): + +```bash +Sequential computation over std::collections::LinkedList : 12.56s +Sequential computation over DoublyList : 11.78s +Parallelized over DoublyList using orx_parallel : 2.93s +``` + +*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.* + ## Iterations 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: diff --git a/benches/parallelization_owned.rs b/benches/parallelization_owned.rs new file mode 100644 index 0000000..b018b9c --- /dev/null +++ b/benches/parallelization_owned.rs @@ -0,0 +1,254 @@ +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use orx_linked_list::*; +#[cfg(feature = "orx-parallel")] +use orx_parallel::ParIter; +use orx_selfref_col::MemoryPolicy; +use rand::prelude::*; +use rand_chacha::ChaCha8Rng; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +#[derive(Clone)] +enum Action { + PushBack(String), + PushFront(String), + PopBack, + PopFront, +} + +fn get_test_data(n: usize) -> Vec { + let mut rng = ChaCha8Rng::seed_from_u64(56456); + let mut vec: Vec<_> = (0..n) + .map(|_| match rng.random::() { + x if x < 0.5 => Action::PushBack(rng.random_range(0..n).to_string()), + _ => Action::PushFront(rng.random_range(0..n).to_string()), + }) + .collect(); + for _ in 0..2 * n { + let action = match rng.random::() { + x if x < 0.25 => Action::PushBack(rng.random_range(0..n).to_string()), + x if x < 0.50 => Action::PushFront(rng.random_range(0..n).to_string()), + x if x < 0.75 => Action::PopBack, + _ => Action::PopFront, + }; + vec.push(action) + } + vec +} + +fn fibonacci(n: i64) -> i64 { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a +} + +// variants + +fn fill_doubly_list>>( + actions: &[Action], + list: &mut List, M>, +) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn doubly_iter>>(list: List, M>) -> Vec { + list.into_iter() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn doubly_iter_x>>(list: List, M>) -> Vec { + list.into_iter_x() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +#[cfg(feature = "orx-parallel")] +fn doubly_par_x>>(list: List, M>) -> Vec { + list.into_par_x() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn fill_std_linked_list(actions: &[Action], list: &mut std::collections::LinkedList) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn std_linked_list(list: std::collections::LinkedList) -> Vec { + list.into_iter() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn std_linked_list_rayon(list: std::collections::LinkedList) -> Vec { + list.into_par_iter() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn fill_vec_deque(actions: &[Action], list: &mut std::collections::VecDeque) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn std_vec_deque(list: std::collections::VecDeque) -> Vec { + list.into_iter() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn std_vec_deque_rayon(list: std::collections::VecDeque) -> Vec { + list.into_par_iter() + .filter(|x| fibonacci(x.parse::().unwrap() % 1000) % 2 == 0) + .map(|x| x.chars().last().unwrap()) + .collect() +} + +fn bench(c: &mut Criterion) { + let treatments = vec![1_024 * 64 * 4]; + + let mut group = c.benchmark_group("parallelization_owned"); + + for n in &treatments { + let data = get_test_data(*n); + + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let expected = std_linked_list(std_list.clone()); + + group.bench_with_input(BenchmarkId::new("LinkedList::into_iter", n), n, |b, _| { + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let result = std_linked_list(std_list.clone()); + assert_eq!(result, expected); + + b.iter(|| std_linked_list(std_list.clone())) + }); + + group.bench_with_input( + BenchmarkId::new("LinkedList::into_par_iter (rayon)", n), + n, + |b, _| { + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let result = std_linked_list_rayon(std_list.clone()); + assert_eq!(result, expected); + + b.iter(|| std_linked_list_rayon(std_list.clone())) + }, + ); + + group.bench_with_input(BenchmarkId::new("VecDeque::into_iter", n), n, |b, _| { + let mut list = std::collections::VecDeque::new(); + fill_vec_deque(&data, &mut list); + let result = std_vec_deque(list.clone()); + assert_eq!(result, expected); + + b.iter(|| std_vec_deque(list.clone())) + }); + + group.bench_with_input( + BenchmarkId::new("VecDeque::into_par_iter (rayon)", n), + n, + |b, _| { + let mut list = std::collections::VecDeque::new(); + fill_vec_deque(&data, &mut list); + let result = std_vec_deque_rayon(list.clone()); + assert_eq!(result, expected); + + b.iter(|| std_vec_deque_rayon(list.clone())) + }, + ); + + group.bench_with_input(BenchmarkId::new("DoublyList::into_iter", n), n, |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_iter(list.clone()); + assert_eq!(result, expected); + + b.iter(|| doubly_iter(list.clone())) + }); + + group.bench_with_input(BenchmarkId::new("DoublyList::into_iter_x", n), n, |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_iter_x(list.clone()); + assert_eq!(result, expected); + + b.iter(|| doubly_iter_x(list.clone())) + }); + + #[cfg(feature = "orx-parallel")] + group.bench_with_input( + BenchmarkId::new("DoublyList::into_par_x (orx-parallel)", n), + n, + |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_par_x(list.clone()); + assert_eq!(result, expected); + + b.iter(|| doubly_par_x(list.clone())) + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/benches/parallelization_ref.rs b/benches/parallelization_ref.rs new file mode 100644 index 0000000..660ea66 --- /dev/null +++ b/benches/parallelization_ref.rs @@ -0,0 +1,276 @@ +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use orx_linked_list::*; +use orx_selfref_col::MemoryPolicy; +use rand::prelude::*; +use rand_chacha::ChaCha8Rng; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + +#[cfg(feature = "orx-parallel")] +use orx_parallel::ParIter; + +#[derive(Clone)] +enum Action { + PushBack(String), + PushFront(String), + PopBack, + PopFront, +} + +fn get_test_data(n: usize) -> Vec { + let mut rng = ChaCha8Rng::seed_from_u64(56456); + let mut vec: Vec<_> = (0..n) + .map(|_| match rng.random::() { + x if x < 0.5 => Action::PushBack(rng.random_range(0..n).to_string()), + _ => Action::PushFront(rng.random_range(0..n).to_string()), + }) + .collect(); + for _ in 0..2 * n { + let action = match rng.random::() { + x if x < 0.25 => Action::PushBack(rng.random_range(0..n).to_string()), + x if x < 0.50 => Action::PushFront(rng.random_range(0..n).to_string()), + x if x < 0.75 => Action::PopBack, + _ => Action::PopFront, + }; + vec.push(action) + } + vec +} + +fn fibonacci(n: i64) -> i64 { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a +} + +// variants + +fn fill_doubly_list>>( + actions: &[Action], + list: &mut List, M>, +) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn doubly_iter>>(list: &List, M>) -> i64 { + list.iter() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn doubly_iter_x>>(list: &List, M>) -> i64 { + list.iter_x() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +#[cfg(feature = "orx-parallel")] +fn doubly_par_x>>(list: &List, M>) -> i64 { + list.par_x() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn fill_std_linked_list(actions: &[Action], list: &mut std::collections::LinkedList) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn std_linked_list(list: &std::collections::LinkedList) -> i64 { + list.iter() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn std_linked_list_rayon(list: &std::collections::LinkedList) -> i64 { + list.par_iter() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn fill_vec_deque(actions: &[Action], list: &mut std::collections::VecDeque) { + for action in actions { + match action { + Action::PushBack(x) => { + list.push_back(x.clone()); + } + Action::PushFront(x) => { + list.push_front(x.clone()); + } + Action::PopBack => { + _ = list.pop_back(); + } + Action::PopFront => { + _ = list.pop_front(); + } + } + } +} + +fn std_vec_deque(list: &std::collections::VecDeque) -> i64 { + list.iter() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn std_vec_deque_rayon(list: &std::collections::VecDeque) -> i64 { + list.par_iter() + .filter_map(|x| x.parse::().ok()) + .map(|x| match x % 2 { + 0 => fibonacci(x as i64 % 1000), + _ => -(x as i64), + }) + .sum() +} + +fn bench(c: &mut Criterion) { + let treatments = vec![1_024 * 64 * 4]; + + let mut group = c.benchmark_group("parallelization_ref"); + + for n in &treatments { + let data = get_test_data(*n); + + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let expected = std_linked_list(&std_list); + + group.bench_with_input(BenchmarkId::new("LinkedList::iter", n), n, |b, _| { + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let result = std_linked_list(&std_list); + assert_eq!(result, expected); + + b.iter(|| std_linked_list(&std_list)) + }); + + group.bench_with_input( + BenchmarkId::new("LinkedList::par_iter (rayon)", n), + n, + |b, _| { + let mut std_list = std::collections::LinkedList::new(); + fill_std_linked_list(&data, &mut std_list); + let result = std_linked_list_rayon(&std_list); + assert_eq!(result, expected); + + b.iter(|| std_linked_list_rayon(&std_list)) + }, + ); + + group.bench_with_input(BenchmarkId::new("VecDeque::iter", n), n, |b, _| { + let mut list = std::collections::VecDeque::new(); + fill_vec_deque(&data, &mut list); + let result = std_vec_deque(&list); + assert_eq!(result, expected); + + b.iter(|| std_vec_deque(&list)) + }); + + group.bench_with_input( + BenchmarkId::new("VecDeque::par_iter (rayon)", n), + n, + |b, _| { + let mut list = std::collections::VecDeque::new(); + fill_vec_deque(&data, &mut list); + let result = std_vec_deque_rayon(&list); + assert_eq!(result, expected); + + b.iter(|| std_vec_deque_rayon(&list)) + }, + ); + + group.bench_with_input(BenchmarkId::new("DoublyList::iter", n), n, |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_iter(&list); + assert_eq!(result, expected); + + b.iter(|| doubly_iter(&list)) + }); + + group.bench_with_input(BenchmarkId::new("DoublyList::iter_x", n), n, |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_iter_x(&list); + assert_eq!(result, expected); + + b.iter(|| doubly_iter_x(&list)) + }); + + #[cfg(feature = "orx-parallel")] + group.bench_with_input( + BenchmarkId::new("DoublyList::par_x (orx-parallel)", n), + n, + |b, _| { + let mut list = DoublyList::new(); + fill_doubly_list(&data, &mut list); + let result = doubly_par_x(&list); + assert_eq!(result, expected); + + b.iter(|| doubly_par_x(&list)) + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/examples/bench_parallelization.rs b/examples/bench_parallelization.rs new file mode 100644 index 0000000..c802d0e --- /dev/null +++ b/examples/bench_parallelization.rs @@ -0,0 +1,94 @@ +// cargo run --release --features orx-parallel --example bench_parallelization +// cargo run --release --features orx-parallel --example bench_parallelization -- --help +// cargo run --release --features orx-parallel --example bench_parallelization -- --len 50000 --num-repetitions 20 + +mod utils; + +use clap::Parser; +use orx_linked_list::*; +use utils::timed_collect_all; + +#[derive(Parser, Debug)] +struct Args { + /// Number of items in the input iterator. + #[arg(long, default_value_t = 1_000_000)] + len: usize, + /// Number of repetitions to measure time; total time will be reported. + #[arg(long, default_value_t = 100)] + num_repetitions: usize, +} + +fn fibonacci(n: usize) -> usize { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a +} + +fn main() { + let args = Args::parse(); + + let expected_output = { + let list: DoublyList<_> = (0..args.len as usize).collect(); + + list.iter() + .filter(|x| *x % 3 != 0) + .map(|x| x + fibonacci(x % 1000)) + .filter_map(|x| (x % 2 == 0).then(|| x.to_string())) + .collect::>() + }; + + let computations: Vec<(&str, Box Vec>)> = vec![ + ( + "Sequential computation over std::collections::LinkedList", + Box::new(move || { + let list: std::collections::LinkedList<_> = (0..args.len as usize).collect(); + + list.iter() + .filter(|x| *x % 3 != 0) + .map(|x| x + fibonacci(x % 1000)) + .filter_map(|x| (x % 2 == 0).then(|| x.to_string())) + .collect::>() + }), + ), + #[cfg(feature = "orx-parallel")] + ( + "Sequential computation over DoublyList", + Box::new(move || { + let list: DoublyList<_> = (0..args.len as usize).collect(); + + list.iter_x() + .filter(|x| *x % 3 != 0) + .map(|x| x + fibonacci(x % 1000)) + .filter_map(|x| (x % 2 == 0).then(|| x.to_string())) + .collect::>() + }), + ), + #[cfg(feature = "orx-parallel")] + ( + "Parallelized over DoublyList using orx_parallel", + Box::new(move || { + let imp_vec: DoublyList<_> = (0..args.len as usize).collect(); + + imp_vec + .into_par_x() // replace iter (into_iter_x) with par (into_par_x) to parallelize ! + .filter(|x| *x % 3 != 0) + .map(|x| x + fibonacci(x % 1000)) + .filter(|x| x % 2 == 0) + .map(|x| x.to_string()) + .collect::>() + }), + ), + ]; + + timed_collect_all( + "benchmark_parallelization", + args.num_repetitions, + &expected_output, + &computations, + ); +} diff --git a/examples/demo_parallelization.rs b/examples/demo_parallelization.rs new file mode 100644 index 0000000..65bb086 --- /dev/null +++ b/examples/demo_parallelization.rs @@ -0,0 +1,35 @@ +// cargo run --release --features orx-parallel --example demo_parallelization + +use orx_linked_list::*; + +fn main() { + let n = 12345; + let input: DoublyList<_> = (0..n).map(|x| x.to_string()).collect(); + let expected_num_characters = 50615; + + // computation using iterators + + let total_num_characters: usize = input.iter_x().map(|x| x.len()).sum(); + assert_eq!(total_num_characters, expected_num_characters); + + #[cfg(feature = "orx-parallel")] + { + // computation using parallel iterator: replace `iter_x()` with `par_x()` + + let total_num_characters: usize = input.par_x().map(|x| x.len()).sum(); + assert_eq!(total_num_characters, expected_num_characters); + + // configure parallel computation + let total_num_characters: usize = input + .par_x() + .num_threads(2) + .chunk_size(64) + .map(|x| x.len()) + .sum(); + assert_eq!(total_num_characters, expected_num_characters); + + // consuming parallel iterator: replace `into_iter_x` with `into_par_x` + let total_num_characters: usize = input.into_par_x().map(|x| x.len()).sum(); + assert_eq!(total_num_characters, expected_num_characters); + } +} diff --git a/examples/utils/benchmark_utils.rs b/examples/utils/benchmark_utils.rs new file mode 100644 index 0000000..4ab3241 --- /dev/null +++ b/examples/utils/benchmark_utils.rs @@ -0,0 +1,94 @@ +#![allow(dead_code)] + +use std::{ + fmt::Debug, + hint::black_box, + time::{Duration, SystemTime}, +}; + +// reduce + +fn timed_reduce(num_repetitions: usize, expected_output: &Option, fun: F) -> Duration +where + F: Fn() -> O, + O: PartialEq + Debug, +{ + if let Some(expected_output) = expected_output.as_ref() { + let result = fun(); + assert_eq!(&result, expected_output); + } + + // warm up + for _ in 0..10 { + let _ = black_box(fun()); + } + + // measurement + + let now = SystemTime::now(); + for _ in 0..num_repetitions { + let result = black_box(fun()); + if let Some(expected_output) = expected_output.as_ref() { + assert_eq!(&result, expected_output); + } + } + now.elapsed().unwrap() +} + +pub fn timed_reduce_all( + benchmark_name: &str, + num_repetitions: usize, + expected_output: Option, + computations: &[(&str, Box O>)], +) where + O: PartialEq + Debug + Clone, +{ + println!("\n{} {} {}", "#".repeat(10), benchmark_name, "#".repeat(10)); + for (name, fun) in computations { + let duration = timed_reduce(num_repetitions, &expected_output, fun); + println!("{:>10} : {:?}", name, duration); + } + println!("{}\n", "#".repeat(10 + 10 + 2 + benchmark_name.len())); +} + +// collect + +fn timed_collect(num_repetitions: usize, expected_output: &[O], fun: F) -> Duration +where + F: Fn() -> Out, + Out: IntoIterator, + O: PartialEq + Debug, +{ + let result = fun(); + assert_eq!(result.into_iter().collect::>(), expected_output); + + // warm up + for _ in 0..10 { + let _ = black_box(fun()); + } + + // measurement + + let now = SystemTime::now(); + for _ in 0..num_repetitions { + let _ = black_box(fun()); + } + now.elapsed().unwrap() +} + +pub fn timed_collect_all( + benchmark_name: &str, + num_repetitions: usize, + expected_output: &[O], + computations: &[(&str, Box Out>)], +) where + Out: IntoIterator, + O: PartialEq + Debug, +{ + println!("\n{} {} {}", "#".repeat(10), benchmark_name, "#".repeat(10)); + for (name, fun) in computations { + let duration = timed_collect(num_repetitions, expected_output, fun); + println!("{:>10} : {:?}", name, duration); + } + println!("{}\n", "#".repeat(10 + 10 + 2 + benchmark_name.len())); +} diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs new file mode 100644 index 0000000..c2042e0 --- /dev/null +++ b/examples/utils/mod.rs @@ -0,0 +1,5 @@ +#![allow(unused_imports)] + +mod benchmark_utils; + +pub use benchmark_utils::*; diff --git a/src/iter/doubly_link_iter.rs b/src/iter/doubly_link_iter.rs index 1cdc929..6864611 100644 --- a/src/iter/doubly_link_iter.rs +++ b/src/iter/doubly_link_iter.rs @@ -1,4 +1,4 @@ -use super::{doubly_link_iter_ptr::PairPtr, DoublyLinkIterPtr}; +use super::{DoublyLinkIterPtr, doubly_link_iter_ptr::PairPtr}; use crate::Doubly; use core::iter::FusedIterator; use orx_pinned_vec::PinnedVec; diff --git a/src/lib.rs b/src/lib.rs index 2d71150..d73ca75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,3 +41,6 @@ pub use type_aliases::{ SinglyListSliceMutLazy, SinglyListThreshold, }; pub use variant::{Doubly, Singly}; + +#[cfg(feature = "orx-parallel")] +pub use orx_parallel::*; diff --git a/src/list.rs b/src/list.rs index 8b44208..74294e5 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,7 +1,7 @@ use crate::{ + Doubly, Singly, type_aliases::{DefaultMemory, DefaultPinVec}, variant::ListVariant, - Doubly, Singly, }; use helper_traits::{ HasCol, HasColMut, HasDoublyEnds, HasDoublyEndsMut, HasSinglyEnds, HasSinglyEndsMut, diff --git a/src/list/common_traits/clone.rs b/src/list/common_traits/clone.rs index cb1e21e..343f2a6 100644 --- a/src/list/common_traits/clone.rs +++ b/src/list/common_traits/clone.rs @@ -1,4 +1,4 @@ -use crate::{variant::Doubly, DoublyIterable, List, Singly, SinglyIterable}; +use crate::{DoublyIterable, List, Singly, SinglyIterable, variant::Doubly}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node}; diff --git a/src/list/common_traits/debug.rs b/src/list/common_traits/debug.rs index e1e0240..8cb773f 100644 --- a/src/list/common_traits/debug.rs +++ b/src/list/common_traits/debug.rs @@ -1,4 +1,4 @@ -use crate::{variant::Doubly, DoublyIterable, List, Singly, SinglyIterable}; +use crate::{DoublyIterable, List, Singly, SinglyIterable, variant::Doubly}; use core::fmt::Debug; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node}; diff --git a/src/list/common_traits/extend.rs b/src/list/common_traits/extend.rs index bd46038..c47b432 100644 --- a/src/list/common_traits/extend.rs +++ b/src/list/common_traits/extend.rs @@ -1,4 +1,4 @@ -use crate::{variant::Doubly, List}; +use crate::{List, variant::Doubly}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node}; diff --git a/src/list/common_traits/from_iter.rs b/src/list/common_traits/from_iter.rs index 31ac02e..9773dee 100644 --- a/src/list/common_traits/from_iter.rs +++ b/src/list/common_traits/from_iter.rs @@ -1,7 +1,7 @@ use crate::{ + List, Singly, type_aliases::{BACK_IDX, FRONT_IDX}, variant::Doubly, - List, Singly, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodePtr, SelfRefCol}; diff --git a/src/list/common_traits/index.rs b/src/list/common_traits/index.rs index e08040a..4bd246e 100644 --- a/src/list/common_traits/index.rs +++ b/src/list/common_traits/index.rs @@ -1,6 +1,6 @@ use crate::{ - type_aliases::OOB, variant::Doubly, DoublyEnds, DoublyEndsMut, DoublyIdx, List, ListSlice, - ListSliceMut, Singly, SinglyEnds, SinglyEndsMut, SinglyIdx, + DoublyEnds, DoublyEndsMut, DoublyIdx, List, ListSlice, ListSliceMut, Singly, SinglyEnds, + SinglyEndsMut, SinglyIdx, type_aliases::OOB, variant::Doubly, }; use core::ops::{Index, IndexMut}; use orx_pinned_vec::PinnedVec; diff --git a/src/list/common_traits/into.rs b/src/list/common_traits/into.rs index 225f3b1..9cfe844 100644 --- a/src/list/common_traits/into.rs +++ b/src/list/common_traits/into.rs @@ -1,6 +1,6 @@ use crate::{ - variant::ListVariant, Doubly, DoublyList, DoublyListLazy, DoublyListThreshold, List, Singly, - SinglyList, SinglyListLazy, SinglyListThreshold, + Doubly, DoublyList, DoublyListLazy, DoublyListThreshold, List, Singly, SinglyList, + SinglyListLazy, SinglyListThreshold, variant::ListVariant, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryReclaimNever, MemoryReclaimOnThreshold, MemoryReclaimer, Node}; diff --git a/src/list/common_traits/into_iter.rs b/src/list/common_traits/into_iter.rs index 5fe6388..576ecc7 100644 --- a/src/list/common_traits/into_iter.rs +++ b/src/list/common_traits/into_iter.rs @@ -1,7 +1,7 @@ use crate::{ + List, Singly, iter::{DoublyIterOwned, SinglyIterOwned}, variant::Doubly, - List, Singly, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node}; diff --git a/src/list/consuming.rs b/src/list/consuming.rs index 3e2c952..e33b65e 100644 --- a/src/list/consuming.rs +++ b/src/list/consuming.rs @@ -1,11 +1,13 @@ use super::List; use crate::variant::ListVariant; -use orx_selfref_col::MemoryPolicy; +use orx_pinned_vec::PinnedVec; +use orx_selfref_col::{MemoryPolicy, Node}; -impl List +impl List where V: ListVariant, M: MemoryPolicy, + P: PinnedVec>, { /// Returns an arbitrary order consuming iterator of owned elements of the list. /// @@ -39,4 +41,51 @@ where let (nodes, _, _) = self.0.into_inner().0.into_inner(); nodes.into_iter().filter_map(|x| x.into_data()) } + + /// Consumes the linked list and creates a parallel iterator over owned elements in **arbitrary order**. + /// + /// Note that `into_par_x` is parallel counterpart of [`into_iter_x`]. + /// + /// Please see [`ParIter`] for details of the parallel computation. + /// In brief, computation is defined as chain of iterator transformations and parallelization + /// is handled by the underlying parallel executor. + /// + /// Required **orx-parallel** feature. + /// + /// [`ParIter`]: orx_parallel::ParIter + /// [`into_iter_x`]: crate::List::into_iter_x + /// + /// # Examples + /// + /// ``` + /// use orx_linked_list::*; + /// + /// let new_list = || DoublyList::from_iter(0..1024); + /// + /// let expected: usize = new_list().iter_x().sum(); + /// + /// let sum = new_list().into_par_x().sum(); // parallelized computation + /// assert_eq!(expected, sum); + /// + /// let sum = new_list().into_par_x().num_threads(4).sum(); // using at most 4 threads + /// assert_eq!(expected, sum); + /// + /// let sum_doubles = new_list().into_par_x().map(|x| x * 2).sum(); + /// assert_eq!(2 * expected, sum_doubles); + /// + /// let expected: usize = new_list().into_iter_x().filter(|x| x % 2 == 0).sum(); + /// let sum_evens = new_list().into_par_x().filter(|x| x % 2 == 0).sum(); + /// std::dbg!(sum_evens, expected); + /// ``` + #[cfg(feature = "orx-parallel")] + pub fn into_par_x(self) -> impl orx_parallel::ParIter + where + V::Item: Send + Sync + Clone, + Node: Send + Sync, + P: orx_concurrent_iter::IntoConcurrentIter>, + { + use orx_parallel::*; + let (pinned, _, _) = self.0.into_inner().0.into_inner(); + pinned.into_par().filter_map(|x| x.into_data()) + } } diff --git a/src/list/ends_traits/doubly_ends.rs b/src/list/ends_traits/doubly_ends.rs index d283142..cb0ba4e 100644 --- a/src/list/ends_traits/doubly_ends.rs +++ b/src/list/ends_traits/doubly_ends.rs @@ -1,7 +1,7 @@ use crate::{ + Doubly, DoublyIdx, list::helper_traits::HasDoublyEnds, type_aliases::{BACK_IDX, FRONT_IDX, IDX_ERR}, - Doubly, DoublyIdx, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdxError}; diff --git a/src/list/ends_traits/doubly_ends_mut.rs b/src/list/ends_traits/doubly_ends_mut.rs index 8b6fb4a..8fdfbd1 100644 --- a/src/list/ends_traits/doubly_ends_mut.rs +++ b/src/list/ends_traits/doubly_ends_mut.rs @@ -1,7 +1,7 @@ use crate::{ + Doubly, DoublyEnds, DoublyIdx, list::helper_traits::HasDoublyEndsMut, type_aliases::{BACK_IDX, FRONT_IDX, IDX_ERR, OOB}, - Doubly, DoublyEnds, DoublyIdx, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdx, NodeIdxError}; diff --git a/src/list/ends_traits/singly_ends.rs b/src/list/ends_traits/singly_ends.rs index 393f0a4..de82607 100644 --- a/src/list/ends_traits/singly_ends.rs +++ b/src/list/ends_traits/singly_ends.rs @@ -1,4 +1,4 @@ -use crate::{list::helper_traits::HasSinglyEnds, type_aliases::IDX_ERR, Singly, SinglyIdx}; +use crate::{Singly, SinglyIdx, list::helper_traits::HasSinglyEnds, type_aliases::IDX_ERR}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdxError}; diff --git a/src/list/ends_traits/singly_ends_mut.rs b/src/list/ends_traits/singly_ends_mut.rs index b03a288..c51713c 100644 --- a/src/list/ends_traits/singly_ends_mut.rs +++ b/src/list/ends_traits/singly_ends_mut.rs @@ -1,5 +1,5 @@ use super::SinglyEnds; -use crate::{list::helper_traits::HasSinglyEndsMut, Singly, SinglyIdx}; +use crate::{Singly, SinglyIdx, list::helper_traits::HasSinglyEndsMut}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdxError}; diff --git a/src/list/get.rs b/src/list/get.rs index 2375716..bef27e7 100644 --- a/src/list/get.rs +++ b/src/list/get.rs @@ -104,4 +104,51 @@ where pub fn iter_x(&self) -> impl Iterator { self.0.nodes().iter().filter_map(|x| x.data()) } + + /// Creates a parallel iterator over references to the elements of the linked list in **arbitrary order**. + /// + /// Note that `par_x` is parallel counterpart of [`iter_x`]. + /// + /// Please see [`ParIter`] for details of the parallel computation. + /// In brief, computation is defined as chain of iterator transformations and parallelization + /// is handled by the underlying parallel executor. + /// + /// Required **orx-parallel** feature. + /// + /// [`ParIter`]: orx_parallel::ParIter + /// [`iter_x`]: crate::List::iter_x + /// + /// # Examples + /// + /// ``` + /// use orx_linked_list::*; + /// + /// let list: DoublyList<_> = (0..1024).collect(); + /// + /// let expected: usize = list.iter_x().sum(); + /// + /// let sum = list.par_x().sum(); // parallelized computation + /// assert_eq!(expected, sum); + /// + /// let sum = list.par_x().num_threads(4).sum(); // using at most 4 threads + /// assert_eq!(expected, sum); + /// + /// let sum_doubles = list.par_x().map(|x| x * 2).sum(); + /// assert_eq!(2 * expected, sum_doubles); + /// + /// let expected: usize = list.iter_x().filter(|x| *x % 2 == 0).sum(); + /// let sum_evens = list.par_x().filter(|x| *x % 2 == 0).sum(); + /// std::dbg!(sum_evens, expected); + /// ``` + #[cfg(feature = "orx-parallel")] + pub fn par_x(&self) -> impl orx_parallel::ParIter + where + V::Item: Send + Sync, + Node: Send + Sync, + for<'a> &'a P: orx_concurrent_iter::IntoConcurrentIter>, + { + use orx_parallel::*; + let pinned = self.0.nodes(); + pinned.par().filter_map(|x| x.data()) + } } diff --git a/src/list/get_doubly.rs b/src/list/get_doubly.rs index d1eba53..e6a53a0 100644 --- a/src/list/get_doubly.rs +++ b/src/list/get_doubly.rs @@ -1,5 +1,5 @@ -use super::{helper_traits::HasDoublyEnds, slice::ListSlice, List}; -use crate::{variant::Doubly, DoublyIdx}; +use super::{List, helper_traits::HasDoublyEnds, slice::ListSlice}; +use crate::{DoublyIdx, variant::Doubly}; use core::ops::RangeBounds; use orx_selfref_col::MemoryPolicy; diff --git a/src/list/helper_traits/doubly_ends.rs b/src/list/helper_traits/doubly_ends.rs index a632488..e3003e2 100644 --- a/src/list/helper_traits/doubly_ends.rs +++ b/src/list/helper_traits/doubly_ends.rs @@ -1,7 +1,7 @@ use super::{HasCol, HasColMut}; use crate::{ - type_aliases::{BACK_IDX, FRONT_IDX}, Doubly, DoublyIdx, + type_aliases::{BACK_IDX, FRONT_IDX}, }; use core::ops::RangeBounds; use orx_pinned_vec::PinnedVec; diff --git a/src/list/idx_doubly.rs b/src/list/idx_doubly.rs index 602bb1a..b8c94c8 100644 --- a/src/list/idx_doubly.rs +++ b/src/list/idx_doubly.rs @@ -1,7 +1,7 @@ use crate::{ + DoublyIdx, List, type_aliases::{BACK_IDX, FRONT_IDX, IDX_ERR}, variant::Doubly, - DoublyIdx, List, }; use orx_selfref_col::{MemoryPolicy, NodeIdx, NodeIdxError}; diff --git a/src/list/iter_traits/doubly_iterable_mut.rs b/src/list/iter_traits/doubly_iterable_mut.rs index 9efca24..f03695a 100644 --- a/src/list/iter_traits/doubly_iterable_mut.rs +++ b/src/list/iter_traits/doubly_iterable_mut.rs @@ -1,8 +1,8 @@ use crate::{ + Doubly, DoublyIdx, iter::{DoublyIterMut, DoublyIterMutChain}, list::helper_traits::HasDoublyEndsMut, type_aliases::{BACK_IDX, FRONT_IDX, OOB}, - Doubly, DoublyIdx, }; use core::iter::Rev; use orx_pinned_vec::PinnedVec; diff --git a/src/list/iter_traits/singly_iterable.rs b/src/list/iter_traits/singly_iterable.rs index 590f6f9..671ba95 100644 --- a/src/list/iter_traits/singly_iterable.rs +++ b/src/list/iter_traits/singly_iterable.rs @@ -1,9 +1,9 @@ use crate::{ + Singly, SinglyIdx, iter::{SinglyIter, SinglyIterPtr}, list::helper_traits::HasSinglyEnds, pointers::SinglyPtr, type_aliases::OOB, - Singly, SinglyIdx, }; use orx_selfref_col::{MemoryPolicy, Node}; use orx_split_vec::PinnedVec; diff --git a/src/list/iter_traits/singly_iterable_mut.rs b/src/list/iter_traits/singly_iterable_mut.rs index d2476e0..7f02c7a 100644 --- a/src/list/iter_traits/singly_iterable_mut.rs +++ b/src/list/iter_traits/singly_iterable_mut.rs @@ -1,6 +1,6 @@ use crate::{ - iter::SinglyIterMut, list::helper_traits::HasSinglyEndsMut, type_aliases::OOB, Singly, - SinglyIdx, + Singly, SinglyIdx, iter::SinglyIterMut, list::helper_traits::HasSinglyEndsMut, + type_aliases::OOB, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node}; diff --git a/src/list/linear.rs b/src/list/linear.rs index 46dc159..6d14cbe 100644 --- a/src/list/linear.rs +++ b/src/list/linear.rs @@ -1,4 +1,4 @@ -use crate::{type_aliases::OOB, Doubly, DoublyIterable, List, Singly, SinglyIterable}; +use crate::{Doubly, DoublyIterable, List, Singly, SinglyIterable, type_aliases::OOB}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdx}; diff --git a/src/list/mut_doubly.rs b/src/list/mut_doubly.rs index e858151..f34f164 100644 --- a/src/list/mut_doubly.rs +++ b/src/list/mut_doubly.rs @@ -1,8 +1,8 @@ -use super::{helper_traits::HasDoublyEnds, List}; +use super::{List, helper_traits::HasDoublyEnds}; use crate::{ - type_aliases::{DoublyIdx, BACK_IDX, FRONT_IDX}, - variant::Doubly, ListSliceMut, + type_aliases::{BACK_IDX, DoublyIdx, FRONT_IDX}, + variant::Doubly, }; use core::ops::RangeBounds; use orx_pinned_vec::PinnedVec; diff --git a/src/list/mut_singly.rs b/src/list/mut_singly.rs index 7790672..933b3c8 100644 --- a/src/list/mut_singly.rs +++ b/src/list/mut_singly.rs @@ -1,5 +1,5 @@ use super::List; -use crate::{iter::SinglyIterMut, variant::Singly, SinglyIdx}; +use crate::{SinglyIdx, iter::SinglyIterMut, variant::Singly}; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodeIdx, Refs}; diff --git a/src/list/new.rs b/src/list/new.rs index 278554c..f5115f0 100644 --- a/src/list/new.rs +++ b/src/list/new.rs @@ -1,6 +1,6 @@ use crate::{ - list::List, variant::ListVariant, DoublyList, DoublyListLazy, DoublyListThreshold, SinglyList, - SinglyListLazy, SinglyListThreshold, + DoublyList, DoublyListLazy, DoublyListThreshold, SinglyList, SinglyListLazy, + SinglyListThreshold, list::List, variant::ListVariant, }; use orx_fixed_vec::FixedVec; use orx_pinned_vec::PinnedVec; diff --git a/src/list/slice/list_slice.rs b/src/list/slice/list_slice.rs index d1957dd..30d4ea9 100644 --- a/src/list/slice/list_slice.rs +++ b/src/list/slice/list_slice.rs @@ -1,8 +1,8 @@ use crate::{ + Doubly, Singly, list::helper_traits::{HasCol, HasDoublyEnds, HasSinglyEnds}, type_aliases::{DefaultMemory, DefaultPinVec}, variant::ListVariant, - Doubly, Singly, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, SelfRefCol, Variant}; diff --git a/src/list/slice/list_slice_mut.rs b/src/list/slice/list_slice_mut.rs index 45754bb..50a40b9 100644 --- a/src/list/slice/list_slice_mut.rs +++ b/src/list/slice/list_slice_mut.rs @@ -1,10 +1,10 @@ use crate::{ + Doubly, List, Singly, list::helper_traits::{ HasCol, HasColMut, HasDoublyEnds, HasDoublyEndsMut, HasSinglyEnds, HasSinglyEndsMut, }, type_aliases::{DefaultMemory, DefaultPinVec}, variant::ListVariant, - Doubly, List, Singly, }; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, SelfRefCol, Variant}; diff --git a/src/tests/doubly.rs b/src/tests/doubly.rs index c9502d6..9d4631d 100644 --- a/src/tests/doubly.rs +++ b/src/tests/doubly.rs @@ -1,8 +1,8 @@ #![allow(unused_imports, dead_code)] use crate::{ + DoublyEnds, DoublyIterable, List, type_aliases::{BACK_IDX, FRONT_IDX}, variant::Doubly, - DoublyEnds, DoublyIterable, List, }; use core::fmt::Debug; use orx_pinned_vec::PinnedVec; diff --git a/src/tests/singly.rs b/src/tests/singly.rs index 8414fce..fa77880 100644 --- a/src/tests/singly.rs +++ b/src/tests/singly.rs @@ -1,5 +1,5 @@ #![allow(unused_imports, dead_code)] -use crate::{variant::Singly, List, SinglyEnds, SinglyIterable}; +use crate::{List, SinglyEnds, SinglyIterable, variant::Singly}; use core::fmt::Debug; use orx_pinned_vec::PinnedVec; use orx_selfref_col::{MemoryPolicy, Node, NodePtr}; diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 6ff8bc4..925dec7 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -1,7 +1,7 @@ use crate::{ + ListSlice, ListSliceMut, list::List, variant::{Doubly, ListVariant, Singly}, - ListSlice, ListSliceMut, }; use orx_selfref_col::{MemoryReclaimNever, MemoryReclaimOnThreshold, Node, NodeIdx}; use orx_split_vec::{Recursive, SplitVec};