From 73d79017dca36fe3c540627a249f9e405e39c058 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:14:29 +0200 Subject: [PATCH 01/18] dependencies updated for parallelization support --- Cargo.toml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbfa0a7..61fede5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.36", 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]] From a74aa0777d0892ace7d682576a730e93f0f1989f Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:14:50 +0200 Subject: [PATCH 02/18] par_x method is defined --- src/list/get.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/list/get.rs b/src/list/get.rs index 2375716..42f5358 100644 --- a/src/list/get.rs +++ b/src/list/get.rs @@ -104,4 +104,48 @@ where pub fn iter_x(&self) -> impl Iterator { self.0.nodes().iter().filter_map(|x| x.data()) } + + /// Creates a parallel iterator over the elements of the linked list in **arbitrary order**. + /// + /// 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 + /// + /// # 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()) + } } From 93217661a72f516786079ce14b51b9b38f3ceeb4 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:15:10 +0200 Subject: [PATCH 03/18] doctest revised --- src/list/get.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/list/get.rs b/src/list/get.rs index 42f5358..0053a8e 100644 --- a/src/list/get.rs +++ b/src/list/get.rs @@ -105,7 +105,7 @@ where self.0.nodes().iter().filter_map(|x| x.data()) } - /// Creates a parallel iterator over the elements of the linked list in **arbitrary order**. + /// Creates a parallel iterator over references to the elements of the linked list in **arbitrary order**. /// /// Please see [`ParIter`] for details of the parallel computation. /// In brief, computation is defined as chain of iterator transformations and parallelization From b8784b481319f3d8ec55581567f02d80f3ee268c Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:17:35 +0200 Subject: [PATCH 04/18] into_par_x is defined and documented --- src/list/consuming.rs | 50 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/list/consuming.rs b/src/list/consuming.rs index 3e2c952..7d29cad 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,48 @@ 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**. + /// + /// 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 + /// + /// # 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()) + } } From 129991b70fd841f40c277c77db043d5e70e7269f Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:18:53 +0200 Subject: [PATCH 05/18] doc links fixed --- src/list/consuming.rs | 3 +++ src/list/get.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/list/consuming.rs b/src/list/consuming.rs index 7d29cad..e33b65e 100644 --- a/src/list/consuming.rs +++ b/src/list/consuming.rs @@ -44,6 +44,8 @@ where /// 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. @@ -51,6 +53,7 @@ where /// Required **orx-parallel** feature. /// /// [`ParIter`]: orx_parallel::ParIter + /// [`into_iter_x`]: crate::List::into_iter_x /// /// # Examples /// diff --git a/src/list/get.rs b/src/list/get.rs index 0053a8e..bef27e7 100644 --- a/src/list/get.rs +++ b/src/list/get.rs @@ -107,6 +107,8 @@ where /// 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. @@ -114,6 +116,7 @@ where /// Required **orx-parallel** feature. /// /// [`ParIter`]: orx_parallel::ParIter + /// [`iter_x`]: crate::List::iter_x /// /// # Examples /// From 52b1b3a2c1e042b8d3707dd430eefac0c5cf267b Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:19:46 +0200 Subject: [PATCH 06/18] formatting --- src/iter/doubly_link_iter.rs | 2 +- src/list.rs | 2 +- src/list/common_traits/clone.rs | 2 +- src/list/common_traits/debug.rs | 2 +- src/list/common_traits/extend.rs | 2 +- src/list/common_traits/from_iter.rs | 2 +- src/list/common_traits/index.rs | 4 ++-- src/list/common_traits/into.rs | 4 ++-- src/list/common_traits/into_iter.rs | 2 +- src/list/ends_traits/doubly_ends.rs | 2 +- src/list/ends_traits/doubly_ends_mut.rs | 2 +- src/list/ends_traits/singly_ends.rs | 2 +- src/list/ends_traits/singly_ends_mut.rs | 2 +- src/list/get_doubly.rs | 4 ++-- src/list/helper_traits/doubly_ends.rs | 2 +- src/list/idx_doubly.rs | 2 +- src/list/iter_traits/doubly_iterable_mut.rs | 2 +- src/list/iter_traits/singly_iterable.rs | 2 +- src/list/iter_traits/singly_iterable_mut.rs | 4 ++-- src/list/linear.rs | 2 +- src/list/mut_doubly.rs | 6 +++--- src/list/mut_singly.rs | 2 +- src/list/new.rs | 4 ++-- src/list/slice/list_slice.rs | 2 +- src/list/slice/list_slice_mut.rs | 2 +- src/tests/doubly.rs | 2 +- src/tests/singly.rs | 2 +- src/type_aliases.rs | 2 +- 28 files changed, 35 insertions(+), 35 deletions(-) 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/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/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_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}; From 6b2fe6fbe589b1a52140c2f940043e79545fcd9c Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:20:00 +0200 Subject: [PATCH 07/18] benchmark for owned parallelization --- benches/parallelization_owned.rs | 254 +++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 benches/parallelization_owned.rs 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); From fae3a9a517291e152e0d0f9db01a2039699b85b3 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:20:07 +0200 Subject: [PATCH 08/18] benchmark for ref parallelization --- benches/parallelization_ref.rs | 276 +++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 benches/parallelization_ref.rs 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); From 797f74dca30c032d4b360daec0cc92d413633217 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:53:36 +0200 Subject: [PATCH 09/18] export parallelization --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) 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::*; From a252a16d28e9fc9ec95c79ed6587b72832ed6222 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 21:56:32 +0200 Subject: [PATCH 10/18] no-std note is updated --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8d2683..ca04b1e 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ 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) + +> **no-std**: This crate supports **no-std**; however, *std* is added due to the default "orx-parallel" feature. Please include with **no-default-features** for no-std use cases: `cargo add orx-linked-list --no-default-features`. -Some notable features are as follows. ## Efficiency From cb47094fd5e041745ae71dfefb057de1912fb4ae Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:16:38 +0200 Subject: [PATCH 11/18] benchmark example is added for parallelization --- examples/bench_parallelization.rs | 92 ++++++++++++++++++++++++++++++ examples/utils/benchmark_utils.rs | 94 +++++++++++++++++++++++++++++++ examples/utils/mod.rs | 5 ++ 3 files changed, 191 insertions(+) create mode 100644 examples/bench_parallelization.rs create mode 100644 examples/utils/benchmark_utils.rs create mode 100644 examples/utils/mod.rs diff --git a/examples/bench_parallelization.rs b/examples/bench_parallelization.rs new file mode 100644 index 0000000..20d1a3f --- /dev/null +++ b/examples/bench_parallelization.rs @@ -0,0 +1,92 @@ +// cargo run --release --example bench_parallelization +// cargo run --release --example bench_parallelization -- --help +// cargo run --release --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: Vec<_> = (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::>() + }), + ), + ( + "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::>() + }), + ), + ( + "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/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::*; From 60cd672c2c71039d74bfc2fadac83cd889cf8333 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:16:50 +0200 Subject: [PATCH 12/18] parallelization demo is added --- examples/demo_parallelization.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 examples/demo_parallelization.rs diff --git a/examples/demo_parallelization.rs b/examples/demo_parallelization.rs new file mode 100644 index 0000000..32d4ea5 --- /dev/null +++ b/examples/demo_parallelization.rs @@ -0,0 +1,32 @@ +// cargo run --release --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); + + // 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); +} From 8d2abd5a3c5a5d73b4c6012aca59b67b37224073 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:17:12 +0200 Subject: [PATCH 13/18] dev dependency upgraded --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 61fede5..49808eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ orx-selfref-col = { version = "2.9.0", default-features = false } orx-parallel = { version = "2.1.0", optional = true } [dev-dependencies] -clap = { version = "4.5.36", features = ["derive"] } +clap = { version = "4.5.38", features = ["derive"] } criterion = "0.5.1" rand = "0.9" rand_chacha = "0.9" From 7394a9c39fd9831b102880dda2ca600e2f3953f4 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:17:48 +0200 Subject: [PATCH 14/18] Parallelization section is added to readme --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca04b1e..c6d4dd3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Both doubly and singly lists are provided as generic variants of the core struct > **no-std**: This crate supports **no-std**; however, *std* is added due to the default "orx-parallel" feature. Please include with **no-default-features** for no-std use cases: `cargo add orx-linked-list --no-default-features`. - ## Efficiency Link lists are self organizing to keep the nodes close to each other to benefit from cache locality. Further, it uses safe direct references without an additional indirection to traverse through the nodes. @@ -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: From 453260b5e8722c623723be8f4fe40f3bd5afdd7d Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:18:37 +0200 Subject: [PATCH 15/18] minor readme revision --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6d4dd3..d176ca3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Both doubly and singly lists are provided as generic variants of the core struct * [`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) -> **no-std**: This crate supports **no-std**; however, *std* is added due to the default "orx-parallel" feature. Please include with **no-default-features** for no-std use cases: `cargo add orx-linked-list --no-default-features`. +> **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 From 19c06923a89a821b31bf8c6aad03e23f929b889b Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:23:40 +0200 Subject: [PATCH 16/18] ci fixed for the parallelization feature --- .github/workflows/ci.yml | 6 ++--- examples/bench_parallelization.rs | 10 ++++---- examples/demo_parallelization.rs | 39 +++++++++++++++++-------------- 3 files changed, 30 insertions(+), 25 deletions(-) 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/examples/bench_parallelization.rs b/examples/bench_parallelization.rs index 20d1a3f..c802d0e 100644 --- a/examples/bench_parallelization.rs +++ b/examples/bench_parallelization.rs @@ -1,6 +1,6 @@ -// cargo run --release --example bench_parallelization -// cargo run --release --example bench_parallelization -- --help -// cargo run --release --example bench_parallelization -- --len 50000 --num-repetitions 20 +// 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; @@ -33,7 +33,7 @@ fn main() { let args = Args::parse(); let expected_output = { - let list: Vec<_> = (0..args.len as usize).collect(); + let list: DoublyList<_> = (0..args.len as usize).collect(); list.iter() .filter(|x| *x % 3 != 0) @@ -55,6 +55,7 @@ fn main() { .collect::>() }), ), + #[cfg(feature = "orx-parallel")] ( "Sequential computation over DoublyList", Box::new(move || { @@ -67,6 +68,7 @@ fn main() { .collect::>() }), ), + #[cfg(feature = "orx-parallel")] ( "Parallelized over DoublyList using orx_parallel", Box::new(move || { diff --git a/examples/demo_parallelization.rs b/examples/demo_parallelization.rs index 32d4ea5..65bb086 100644 --- a/examples/demo_parallelization.rs +++ b/examples/demo_parallelization.rs @@ -1,4 +1,4 @@ -// cargo run --release --example demo_parallelization +// cargo run --release --features orx-parallel --example demo_parallelization use orx_linked_list::*; @@ -12,21 +12,24 @@ fn main() { let total_num_characters: usize = input.iter_x().map(|x| x.len()).sum(); assert_eq!(total_num_characters, expected_num_characters); - // 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); + #[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); + } } From c8a84d256e61fcc8d2aa42cf67d5d609a3cbd7a1 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:23:53 +0200 Subject: [PATCH 17/18] version number is incremented --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 49808eb..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." From 4dc144e17cf72c7aaaa475ead092e839a37e7b77 Mon Sep 17 00:00:00 2001 From: orxfun Date: Sat, 24 May 2025 22:24:23 +0200 Subject: [PATCH 18/18] minor readme revision --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d176ca3..260c5c4 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The significance of improvement can further be increased by using `DoublyList::i -### Parallelization +## Parallelization When [orx-parallel](https://crates.io/crates/orx-parallel) feature is used (by default), computations over `LinkedList` elements can be efficiently parallelized.