From f08883940ea04acc2da5eda1f0bf3c5e32793c32 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Tue, 13 Oct 2026 10:16:19 +0300 Subject: [PATCH 1/5] lazy comporator --- iKeySort/src/sort/one_key_cmp.rs | 2 +- iKeySort/src/sort/two_keys.rs | 8 ++++++-- iKeySort/src/sort/two_keys_cmp.rs | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/iKeySort/src/sort/one_key_cmp.rs b/iKeySort/src/sort/one_key_cmp.rs index 97559f8..16d8d2c 100644 --- a/iKeySort/src/sort/one_key_cmp.rs +++ b/iKeySort/src/sort/one_key_cmp.rs @@ -155,7 +155,7 @@ where F1: KeyFn, F2: CmpFn, { - slice.sort_unstable_by(|a, b| key(a).cmp(&key(b)).then(compare(a, b))); + slice.sort_unstable_by(|a, b| key(a).cmp(&key(b)).then_with(|| compare(a, b))); } #[cfg(test)] diff --git a/iKeySort/src/sort/two_keys.rs b/iKeySort/src/sort/two_keys.rs index 5b6c05f..d76bc29 100644 --- a/iKeySort/src/sort/two_keys.rs +++ b/iKeySort/src/sort/two_keys.rs @@ -161,7 +161,11 @@ where F1: KeyFn, F2: KeyFn, { - slice.sort_unstable_by(|a, b| key1(a).cmp(&key1(b)).then(key2(a).cmp(&key2(b)))) + slice.sort_unstable_by(|a, b| { + key1(a) + .cmp(&key1(b)) + .then_with(|| key2(a).cmp(&key2(b))) + }) } #[cfg(test)] @@ -227,4 +231,4 @@ mod tests { arr } -} \ No newline at end of file +} diff --git a/iKeySort/src/sort/two_keys_cmp.rs b/iKeySort/src/sort/two_keys_cmp.rs index a276755..45f8444 100644 --- a/iKeySort/src/sort/two_keys_cmp.rs +++ b/iKeySort/src/sort/two_keys_cmp.rs @@ -213,8 +213,8 @@ pub(crate) fn sort_unstable_by_two_keys_then_by( slice.sort_unstable_by(|a, b| { key1(a) .cmp(&key1(b)) - .then(key2(a).cmp(&key2(b))) - .then(compare(a, b)) + .then_with(|| key2(a).cmp(&key2(b))) + .then_with(|| compare(a, b)) }); } From 3547d72276bd4dd7c4e07c7400c0f8d3503bf8d9 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Tue, 13 Oct 2026 10:27:49 +0300 Subject: [PATCH 2/5] use rayon pool count --- iKeySort/src/sort/parallel/cpu_count.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iKeySort/src/sort/parallel/cpu_count.rs b/iKeySort/src/sort/parallel/cpu_count.rs index 48a2b1e..eae6d15 100644 --- a/iKeySort/src/sort/parallel/cpu_count.rs +++ b/iKeySort/src/sort/parallel/cpu_count.rs @@ -3,7 +3,7 @@ pub(super) struct CPUCount; impl CPUCount { #[inline] pub(super) fn count() -> usize { - std::thread::available_parallelism().map_or(1, |n| n.get()) + rayon::current_num_threads() } #[cfg(not(debug_assertions))] From f871d5797842454b73f6f59ac55811168f757c3b Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Tue, 13 Oct 2026 10:53:18 +0300 Subject: [PATCH 3/5] bin working memory optimization --- iKeySort/src/lib.rs | 2 + iKeySort/src/sort/parallel/bin_layout.rs | 232 ++++++++++++-------- iKeySort/src/sort/parallel/pre_sort.rs | 173 +++++++-------- iKeySort/src/sort/parallel/slice_one_key.rs | 1 - iKeySort/src/sort/parallel/sub_sort.rs | 68 +++++- 5 files changed, 293 insertions(+), 183 deletions(-) diff --git a/iKeySort/src/lib.rs b/iKeySort/src/lib.rs index 5467fcc..2df6b3e 100644 --- a/iKeySort/src/lib.rs +++ b/iKeySort/src/lib.rs @@ -20,6 +20,8 @@ //! - `std` — opt in to the Rust standard library (still `no_std` by default). //! - `allow_multithreading` — enables parallel pre-spread via Rayon and exposes `parallel: bool` paths. //! - When this feature is **off**, the `parallel` argument is accepted but **ignored**. +//! - Parallel sorts cache an index table per calling thread, retaining its largest +//! capacity until thread exit. Element data is never retained in this cache. //! //! ## Complexity (high level) //! - Pre-spread: `O(n)` with one pass over the input. diff --git a/iKeySort/src/sort/parallel/bin_layout.rs b/iKeySort/src/sort/parallel/bin_layout.rs index d9e5a4c..699176b 100644 --- a/iKeySort/src/sort/parallel/bin_layout.rs +++ b/iKeySort/src/sort/parallel/bin_layout.rs @@ -1,10 +1,10 @@ use crate::sort::bin_layout::{BinLayout, LayoutConstraints, MAX_BINS_COUNT}; use crate::sort::key::{KeyFn, SortKey}; -use crate::sort::parallel::pre_sort::{PreSortFragment, IdRange, FragmentationByCount}; -use rayon::iter::ParallelIterator; -use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator}; +use crate::sort::parallel::pre_sort::{ScratchRanges, spread_fragment}; +use crate::sort::parallel::sub_sort::BinMarks; use core::ops::Range; -use core::ptr; +use rayon::iter::ParallelIterator; +use rayon::prelude::*; impl BinLayout { #[inline] @@ -22,7 +22,13 @@ impl BinLayout { let max_bins_count = cpu.saturating_mul(4).min(MAX_BINS_COUNT); - Some(BinLayout::with_constraints(min_key, max_key, LayoutConstraints { max_split_count: max_bins_count })) + Some(BinLayout::with_constraints( + min_key, + max_key, + LayoutConstraints { + max_split_count: max_bins_count, + }, + )) } } @@ -65,7 +71,7 @@ impl BinLayout { src: &mut [T], buf: &mut Vec, key: F, - ) -> Vec + ) -> BinMarks where T: Copy + Send + Sync, F: KeyFn + Send + Sync, @@ -75,99 +81,151 @@ impl BinLayout { buf.reserve(src.len()); } - let mut fragments = src.fragment_by_count(buf, cpu); - - let groups = self.par_spread(&mut fragments, key); - - // at this time buffer is fully initialized - #[allow(clippy::uninit_vec)] - unsafe { buf.set_len(src.len()); } - - copy_groups(src, buf, groups) - } - - #[inline] - fn par_spread(&self, fragments: &mut [PreSortFragment], key: F) -> Vec>> - where - T: Copy + Send + Sync, - F: KeyFn + Send + Sync, - { let bins_count = self.count(); - let frags_count = fragments.len(); - - let id_ranges = fragments - .par_iter_mut() - .map(|f| f.spread(self.clone(), key)) - .reduce( - || Vec::::with_capacity(frags_count * bins_count), - |mut a, mut b| { - a.append(&mut b); - a - }, - ); - - let mut counter = vec![0usize; bins_count]; - for range in id_ranges.iter() { - counter[range.index] += 1; - } - - let mut groups: Vec>> = (0..bins_count).map(|_| Vec::new()).collect(); - for (group, &count) in groups.iter_mut().zip(counter.iter()) { - if count > 0 { - *group = Vec::with_capacity(count); - } - } - - for id_range in id_ranges { - unsafe { - groups - .get_unchecked_mut(id_range.index) - .push(id_range.range); - } + let step_len = src.len().div_ceil(cpu); + let fragments_count = src.len().div_ceil(step_len); + let ranges_count = fragments_count + .checked_mul(bins_count) + .expect("too many bin ranges"); + let mut scratch = ScratchRanges::new(ranges_count); + + src.par_chunks(step_len) + .zip(buf.spare_capacity_mut()[..src.len()].par_chunks_mut(step_len)) + .zip(scratch.ranges.par_chunks_mut(bins_count)) + .enumerate() + .for_each(|(index, ((src, buf), ranges))| { + spread_fragment(src, buf, ranges, index * step_len, self, key); + }); + + // SAFETY: every fragment has initialized its disjoint part of the buffer. + // If a key function panics, execution never reaches set_len. + unsafe { + buf.set_len(src.len()); } - groups.retain(|g| !g.is_empty()); - groups + copy_groups(src, buf, &scratch.ranges, bins_count) } } #[inline] -fn copy_groups(mut dst: &mut [T], src: &[T], mut groups: Vec>>) -> Vec { - let mut marks = Vec::with_capacity(groups.len()); - let last_group = if let Some(last) = groups.pop() { - last - } else { - return marks; - }; - - let mut base = 0; - for ranges in groups.iter() { - let length = copy_ranges(dst, src, ranges); - (_, dst) = dst.split_at_mut(length); - - base += length; - marks.push(base); +fn copy_groups( + dst: &mut [T], + src: &[T], + ranges: &[Range], + bins_count: usize, +) -> BinMarks { + let mut marks = BinMarks::new(); + let mut offset = 0; + for bin in 0..bins_count { + let start = offset; + for row in ranges.chunks_exact(bins_count) { + let range = &row[bin]; + if range.is_empty() { + continue; + } + let end = offset + range.len(); + dst[offset..end].copy_from_slice(&src[range.clone()]); + offset = end; + } + if offset > start && offset < dst.len() { + marks.push(offset); + } } - - let _ = copy_ranges(dst, src, &last_group); - + debug_assert_eq!(offset, dst.len()); marks } -#[inline] -fn copy_ranges(dst: &mut [T], src: &[T], ranges: &[Range]) -> usize { - let mut offset = 0; - let dst_base = dst.as_mut_ptr(); - let src_base = src.as_ptr(); - for range in ranges.iter() { - unsafe { - let dst_ptr = dst_base.add(offset); - let src_ptr = src_base.add(range.start); - ptr::copy_nonoverlapping(src_ptr, dst_ptr, range.len()); - } +#[cfg(test)] +mod scratch_tests { + use super::BinLayout; + use crate::sort::parallel::sub_sort::FragmentationByMarks; + use rayon::iter::ParallelIterator; + use std::panic::{AssertUnwindSafe, catch_unwind}; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[test] + fn sparse_and_full_bins_reuse_scratch_with_uneven_fragments() { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(3) + .build() + .unwrap(); + pool.install(|| { + let mut buffer = Vec::new(); + for bins in [256, 2, 17, 256] { + let mut values: Vec = (0..1_031).rev().map(|i| i % bins).collect(); + let mut expected = values.clone(); + expected.sort_unstable(); + let layout = BinLayout::with_keys(&values, |x| *x).unwrap(); + let marks = layout.par_pre_sort(3, &mut values, &mut buffer, |x| *x); + assert_eq!(values, expected); + assert_eq!(marks.len(), bins as usize - 1); + values + .fragment_by_marks(&mut buffer, &marks) + .par_iter_mut() + .for_each(|fragment| { + assert!(fragment.src.iter().all(|x| *x == fragment.src[0])) + }); + } + let mut values = [i64::MIN, 0, i64::MAX].repeat(347); + let mut expected = values.clone(); + expected.sort_unstable(); + let layout = BinLayout::with_keys(&values, |x| *x).unwrap(); + let marks = layout.par_pre_sort(3, &mut values, &mut buffer, |x| *x); + assert_eq!(values, expected); + assert_eq!(marks.len(), 2); + }); + } - offset += range.len(); + #[test] + fn nested_spread_can_rent_scratch_on_the_same_thread() { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(1) + .build() + .unwrap(); + pool.install(|| { + let mut values: Vec = (0..64).rev().collect(); + let layout = BinLayout::with_keys(&values, |x| *x).unwrap(); + let nested = AtomicBool::new(false); + layout.par_pre_sort(1, &mut values, &mut Vec::new(), |x| { + if !nested.swap(true, Ordering::Relaxed) { + let mut inner: Vec = (0..256).rev().collect(); + let inner_layout = BinLayout::with_keys(&inner, |x| *x).unwrap(); + inner_layout.par_pre_sort(1, &mut inner, &mut Vec::new(), |x| *x); + assert!(inner.is_sorted()); + } + *x + }); + assert!(values.is_sorted()); + assert!(nested.load(Ordering::Relaxed)); + }); } - offset + #[test] + fn panic_during_scatter_keeps_buffer_uninitialized_and_reusable() { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(2) + .build() + .unwrap(); + pool.install(|| { + let mut values: Vec = (0..128).rev().collect(); + let original = values.clone(); + let layout = BinLayout::with_keys(&values, |x| *x).unwrap(); + let seen = AtomicBool::new(false); + let mut buffer = vec![0; values.len()]; + let result = catch_unwind(AssertUnwindSafe(|| { + layout.par_pre_sort(2, &mut values, &mut buffer, |x| { + if *x == 0 && seen.swap(true, Ordering::Relaxed) { + panic!("key failed during scatter"); + } + *x + }); + })); + assert!(result.is_err()); + assert!(buffer.is_empty()); + assert_eq!(values, original); + layout.par_pre_sort(2, &mut values, &mut buffer, |x| *x); + assert!(values.is_sorted()); + assert_eq!(buffer.len(), values.len()); + }); + } } diff --git a/iKeySort/src/sort/parallel/pre_sort.rs b/iKeySort/src/sort/parallel/pre_sort.rs index cf0627d..dab1613 100644 --- a/iKeySort/src/sort/parallel/pre_sort.rs +++ b/iKeySort/src/sort/parallel/pre_sort.rs @@ -2,111 +2,102 @@ use crate::sort::bin_layout::BinLayout; use crate::sort::key::{KeyFn, SortKey}; use crate::sort::mapper::Mapper; use core::ops::Range; +use std::cell::RefCell; use std::mem::MaybeUninit; -pub(super) struct PreSortFragment<'a, T> { - pub(super) base: usize, - pub(super) src: &'a mut [T], - pub(super) buf: &'a mut [MaybeUninit], +thread_local! { + // Only indices are retained; no element data or references survive a sort. + static RANGES: RefCell>> = const { RefCell::new(Vec::new()) }; } -pub(super) struct IdRange { - pub(super) index: usize, - pub(super) range: Range, +pub(super) struct ScratchRanges { + pub(super) ranges: Vec>, } -impl PreSortFragment<'_, T> -where - T: Send + Copy, -{ - #[inline] - pub(super) fn spread(&mut self, layout: BinLayout, key: F) -> Vec - where - K: SortKey, - F: KeyFn, - { - let mut mapper = Mapper::new(layout.count()); - for a in self.src.iter() { - mapper.inc_bin_count(layout.index(key(a))); - } - - mapper.init_indices(); - - for val in self.src.iter() { - let index = mapper.next_index(layout.index(key(val))); - unsafe { - let _ = *self.buf.get_unchecked_mut(index).write(*val); - } - } - - let mut ranges = Vec::with_capacity(mapper.count); - for (index, chunk) in mapper.iter().enumerate() { - let range = chunk.as_range(); - if !range.is_empty() { - let global_range = range.start + self.base..range.end + self.base; - ranges.push(IdRange { - index, - range: global_range, - }); - } - } - - ranges +impl ScratchRanges { + pub(super) fn new(len: usize) -> Self { + // Take ownership before calling user code or entering Rayon, so nested + // sorts can rent their own storage without holding a RefCell borrow. + let ranges = RANGES + .try_with(|cache| core::mem::take(&mut *cache.borrow_mut())) + .unwrap_or_default(); + let mut scratch = Self { ranges }; + scratch.ranges.resize(len, 0..0); + scratch } } -pub(super) trait FragmentationByCount { - fn fragment_by_count<'a>( - &'a mut self, - buffer: &'a mut Vec, - count: usize, - ) -> Vec>; +impl Drop for ScratchRanges { + fn drop(&mut self) { + // A nested sort may already have returned a buffer. Keep the larger one. + // try_with also permits sorting during thread-local destruction. + let _ = RANGES.try_with(|cache| { + if let Ok(mut cached) = cache.try_borrow_mut() { + if self.ranges.capacity() > cached.capacity() { + core::mem::swap(&mut *cached, &mut self.ranges); + } + } + }); + } } -impl FragmentationByCount for [T] { - #[inline] - fn fragment_by_count<'a>( - &'a mut self, - buffer: &'a mut Vec, - count: usize, - ) -> Vec> { - let (capacity, step_len) = if self.len() < count { - (self.len(), 1) - } else { - let step_len = self.len().div_ceil(count); - let count = self.len().div_ceil(step_len); - (count, step_len) - }; - - let mut scratch: &mut [MaybeUninit] = &mut buffer.spare_capacity_mut()[..self.len()]; - - let mut frags = Vec::with_capacity(capacity); - let mut base = 0; - - let mut src = self; - - for _ in 0..capacity.saturating_sub(1) { - let (left_src, right_src) = src.split_at_mut(step_len); - let (left_buf, right_buf) = scratch.split_at_mut(step_len); +#[inline] +pub(super) fn spread_fragment( + src: &[T], + buf: &mut [MaybeUninit], + ranges: &mut [Range], + base: usize, + layout: &BinLayout, + key: F, +) where + T: Copy, + K: SortKey, + F: KeyFn, +{ + debug_assert_eq!(src.len(), buf.len()); + debug_assert_eq!(ranges.len(), layout.count()); + let mut mapper = Mapper::new(layout.count()); + for a in src { + mapper.inc_bin_count(layout.index(key(a))); + } + mapper.init_indices(); - frags.push(PreSortFragment { - base, - src: left_src, - buf: left_buf, - }); + for val in src { + let index = mapper.next_index(layout.index(key(val))); + // SAFETY: the histogram reserves one destination for every source item. + unsafe { buf.get_unchecked_mut(index).write(*val) }; + } - src = right_src; - scratch = right_buf; + for (dst, chunk) in ranges.iter_mut().zip(mapper.iter()) { + let range = chunk.as_range(); + *dst = range.start + base..range.end + base; + } +} - base += step_len; +#[cfg(test)] +mod tests { + use super::ScratchRanges; + + #[test] + fn scratch_is_available_after_thread_cache_destruction() { + struct RentOnDrop; + impl Drop for RentOnDrop { + fn drop(&mut self) { + let mut scratch = ScratchRanges::new(8); + scratch.ranges[0] = 2..4; + assert_eq!(scratch.ranges[0], 2..4); + } } - - frags.push(PreSortFragment { - base, - src, - buf: scratch, - }); - - frags + thread_local! { + static RENT_ON_DROP: RentOnDrop = const { RentOnDrop }; + } + std::thread::spawn(|| { + // TLS is destroyed in reverse initialization order: the index cache + // is gone when RentOnDrop requests scratch storage. + RENT_ON_DROP.with(|_| {}); + drop(ScratchRanges::new(8)); + }) + .join() + .unwrap(); } } diff --git a/iKeySort/src/sort/parallel/slice_one_key.rs b/iKeySort/src/sort/parallel/slice_one_key.rs index b37fc55..b6dbd4e 100644 --- a/iKeySort/src/sort/parallel/slice_one_key.rs +++ b/iKeySort/src/sort/parallel/slice_one_key.rs @@ -4,7 +4,6 @@ use crate::sort::parallel::cpu_count::CPUCount; use crate::sort::parallel::sub_sort::{FragmentationByMarks, SubSortFragment}; use crate::sort::serial::slice_one_key::OneKeyBinSortSerial; use rayon::iter::ParallelIterator; -use rayon::prelude::IntoParallelRefMutIterator; pub(crate) trait OneKeyBinSortParallel { fn par_sort_by_one_key(&mut self, reusable_buffer: &mut Vec, key: F) diff --git a/iKeySort/src/sort/parallel/sub_sort.rs b/iKeySort/src/sort/parallel/sub_sort.rs index 0f35767..dc61d64 100644 --- a/iKeySort/src/sort/parallel/sub_sort.rs +++ b/iKeySort/src/sort/parallel/sub_sort.rs @@ -1,3 +1,64 @@ +use crate::sort::bin_layout::MAX_BINS_COUNT; +use core::ops::Deref; +use rayon::prelude::*; + +pub(super) struct BinMarks { + values: [usize; MAX_BINS_COUNT], + len: usize, +} + +impl BinMarks { + pub(super) fn new() -> Self { + Self { + values: [0; MAX_BINS_COUNT], + len: 0, + } + } + + pub(super) fn push(&mut self, mark: usize) { + self.values[self.len] = mark; + self.len += 1; + } +} + +impl Deref for BinMarks { + type Target = [usize]; + + fn deref(&self) -> &Self::Target { + &self.values[..self.len] + } +} + +pub(super) struct SubSortFragments<'a, T> { + items: [Option>; MAX_BINS_COUNT], + len: usize, +} + +impl<'a, T> SubSortFragments<'a, T> { + fn new() -> Self { + Self { + items: [const { None }; MAX_BINS_COUNT], + len: 0, + } + } + + fn push(&mut self, fragment: SubSortFragment<'a, T>) { + self.items[self.len] = Some(fragment); + self.len += 1; + } +} + +impl<'a, T: Send> SubSortFragments<'a, T> { + #[inline] + pub(super) fn par_iter_mut( + &mut self, + ) -> impl ParallelIterator> { + self.items[..self.len] + .par_iter_mut() + .map(|fragment| fragment.as_mut().unwrap()) + } +} + pub(super) struct SubSortFragment<'a, T> { pub(super) src: &'a mut [T], pub(super) buf: &'a mut [T], @@ -8,20 +69,19 @@ pub(super) trait FragmentationByMarks { &'a mut self, buf: &'a mut [T], marks: &[usize], - ) -> Vec>; + ) -> SubSortFragments<'a, T>; } impl FragmentationByMarks for [T] { - #[inline] fn fragment_by_marks<'a>( &'a mut self, buffer: &'a mut [T], marks: &[usize], - ) -> Vec> { + ) -> SubSortFragments<'a, T> { debug_assert_eq!(self.len(), buffer.len()); - let mut frags = Vec::with_capacity(marks.len() + 1); + let mut frags = SubSortFragments::new(); let mut src = self; let mut buf = buffer; From 5b51c6de84124035839cc35f542e72b1a089fc26 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Tue, 13 Oct 2026 10:55:15 +0300 Subject: [PATCH 4/5] clean --- iKeySort/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/iKeySort/src/lib.rs b/iKeySort/src/lib.rs index 2df6b3e..5467fcc 100644 --- a/iKeySort/src/lib.rs +++ b/iKeySort/src/lib.rs @@ -20,8 +20,6 @@ //! - `std` — opt in to the Rust standard library (still `no_std` by default). //! - `allow_multithreading` — enables parallel pre-spread via Rayon and exposes `parallel: bool` paths. //! - When this feature is **off**, the `parallel` argument is accepted but **ignored**. -//! - Parallel sorts cache an index table per calling thread, retaining its largest -//! capacity until thread exit. Element data is never retained in this cache. //! //! ## Complexity (high level) //! - Pre-spread: `O(n)` with one pass over the input. From b36d290aeddddb2bad6d74eff5f41f3664dd697b Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Tue, 13 Oct 2026 10:59:12 +0300 Subject: [PATCH 5/5] fmt + up rayon ver --- iKeySort/Cargo.toml | 4 ++-- iKeySort/src/sort/mod.rs | 14 +++++++------- iKeySort/src/sort/one_key.rs | 19 +++++-------------- iKeySort/src/sort/parallel/mod.rs | 2 +- .../src/sort/parallel/slice_one_key_cmp.rs | 2 +- iKeySort/src/sort/parallel/slice_two_keys.rs | 2 +- iKeySort/src/sort/serial/mapper_cmp.rs | 3 +-- .../src/sort/serial/mapper_one_key_cmp.rs | 2 +- iKeySort/src/sort/serial/mapper_two_keys.rs | 4 ++-- iKeySort/src/sort/serial/mod.rs | 2 +- iKeySort/src/sort/serial/slice_one_key_cmp.rs | 7 +++++-- iKeySort/src/sort/spread.rs | 12 ++++++++---- iKeySort/src/sort/two_keys.rs | 8 ++------ 13 files changed, 37 insertions(+), 44 deletions(-) diff --git a/iKeySort/Cargo.toml b/iKeySort/Cargo.toml index 6c45ccd..7089cd6 100644 --- a/iKeySort/Cargo.toml +++ b/iKeySort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "i_key_sort" -version = "0.11.0" +version = "0.12.0" authors = ["Nail Sharipov "] edition = "2024" description = "Counting sort algorithm." @@ -15,7 +15,7 @@ lto = true codegen-units = 1 [dependencies] -rayon = { optional = true, version = "^1.11" } +rayon = { optional = true, version = "^1.12" } [features] diff --git a/iKeySort/src/sort/mod.rs b/iKeySort/src/sort/mod.rs index 9cf8e3e..fb6dd4d 100644 --- a/iKeySort/src/sort/mod.rs +++ b/iKeySort/src/sort/mod.rs @@ -1,13 +1,13 @@ -#[cfg(feature = "allow_multithreading")] -mod parallel; -mod serial; mod bin_layout; -mod mapper; -mod min_max; -mod spread; mod buffer; pub mod key; +mod mapper; +mod min_max; pub mod one_key; pub mod one_key_cmp; +#[cfg(feature = "allow_multithreading")] +mod parallel; +mod serial; +mod spread; pub mod two_keys; -pub mod two_keys_cmp; \ No newline at end of file +pub mod two_keys_cmp; diff --git a/iKeySort/src/sort/one_key.rs b/iKeySort/src/sort/one_key.rs index 8489526..74930cc 100644 --- a/iKeySort/src/sort/one_key.rs +++ b/iKeySort/src/sort/one_key.rs @@ -34,12 +34,8 @@ pub trait OneKeySort { K: SortKey, F: KeyFn; - fn sort_by_one_key_and_buffer( - &mut self, - parallel: bool, - buffer: &mut Vec, - key: F, - ) where + fn sort_by_one_key_and_buffer(&mut self, parallel: bool, buffer: &mut Vec, key: F) + where K: SortKey, F: KeyFn; } @@ -117,12 +113,8 @@ where } #[inline] - fn sort_by_one_key_and_buffer( - &mut self, - parallel: bool, - buffer: &mut Vec, - key: F, - ) where + fn sort_by_one_key_and_buffer(&mut self, parallel: bool, buffer: &mut Vec, key: F) + where K: SortKey + Send + Sync, F: KeyFn + Send + Sync, { @@ -140,11 +132,10 @@ where } } - #[cfg(test)] mod tests { - use alloc::vec::Vec; use crate::sort::one_key::OneKeySort; + use alloc::vec::Vec; #[test] fn test_0() { diff --git a/iKeySort/src/sort/parallel/mod.rs b/iKeySort/src/sort/parallel/mod.rs index 6693513..fa2a409 100644 --- a/iKeySort/src/sort/parallel/mod.rs +++ b/iKeySort/src/sort/parallel/mod.rs @@ -1,8 +1,8 @@ +mod bin_layout; mod cpu_count; mod pre_sort; pub(crate) mod slice_one_key; pub(crate) mod slice_one_key_cmp; pub(crate) mod slice_two_keys; pub(crate) mod slice_two_keys_cmp; -mod bin_layout; mod sub_sort; diff --git a/iKeySort/src/sort/parallel/slice_one_key_cmp.rs b/iKeySort/src/sort/parallel/slice_one_key_cmp.rs index 822c265..59f83ac 100644 --- a/iKeySort/src/sort/parallel/slice_one_key_cmp.rs +++ b/iKeySort/src/sort/parallel/slice_one_key_cmp.rs @@ -1,9 +1,9 @@ use crate::sort::bin_layout::BinLayout; use crate::sort::key::{CmpFn, KeyFn, SortKey}; use crate::sort::parallel::cpu_count::CPUCount; +use crate::sort::parallel::sub_sort::{FragmentationByMarks, SubSortFragment}; use crate::sort::serial::slice_one_key_cmp::OneKeyBinSortCmpSerial; use rayon::prelude::*; -use crate::sort::parallel::sub_sort::{FragmentationByMarks, SubSortFragment}; pub(crate) trait OneKeyBinSortCmpParallel { fn par_sort_by_one_key_then_by( diff --git a/iKeySort/src/sort/parallel/slice_two_keys.rs b/iKeySort/src/sort/parallel/slice_two_keys.rs index 3698b17..5658942 100644 --- a/iKeySort/src/sort/parallel/slice_two_keys.rs +++ b/iKeySort/src/sort/parallel/slice_two_keys.rs @@ -2,9 +2,9 @@ use crate::sort::bin_layout::BinLayout; use crate::sort::key::{KeyFn, SortKey}; use crate::sort::parallel::cpu_count::CPUCount; use crate::sort::parallel::slice_one_key::OneKeyBinSortParallel; +use crate::sort::parallel::sub_sort::{FragmentationByMarks, SubSortFragment}; use crate::sort::serial::slice_two_keys::TwoKeysBinSortSerial; use rayon::prelude::*; -use crate::sort::parallel::sub_sort::{FragmentationByMarks, SubSortFragment}; pub(crate) trait TwoKeysBinSortParallel { fn par_sort_by_two_keys( diff --git a/iKeySort/src/sort/serial/mapper_cmp.rs b/iKeySort/src/sort/serial/mapper_cmp.rs index 0167500..ea1f67e 100644 --- a/iKeySort/src/sort/serial/mapper_cmp.rs +++ b/iKeySort/src/sort/serial/mapper_cmp.rs @@ -11,7 +11,6 @@ impl Mapper { compare: F, copy_to_src: bool, ) { - // if `copy_to_src` is true // must copy `src` to `buf`, since the result array is in the buffer @@ -34,4 +33,4 @@ impl Mapper { } } } -} \ No newline at end of file +} diff --git a/iKeySort/src/sort/serial/mapper_one_key_cmp.rs b/iKeySort/src/sort/serial/mapper_one_key_cmp.rs index 5062168..cba2f0f 100644 --- a/iKeySort/src/sort/serial/mapper_one_key_cmp.rs +++ b/iKeySort/src/sort/serial/mapper_one_key_cmp.rs @@ -38,7 +38,7 @@ impl Mapper { // SAFETY: mapper ranges never overlap; (src, buf) are distinct buffers. let sub_buf = unsafe { buf.get_unchecked_mut(range.clone()) }; sort_unstable_by_one_key_then_by(sub_buf, key, compare); - + if copy_to_src { src.copy_to_range_from_not_overlap(sub_buf, range); } diff --git a/iKeySort/src/sort/serial/mapper_two_keys.rs b/iKeySort/src/sort/serial/mapper_two_keys.rs index 6e52bb3..f4a996c 100644 --- a/iKeySort/src/sort/serial/mapper_two_keys.rs +++ b/iKeySort/src/sort/serial/mapper_two_keys.rs @@ -1,8 +1,8 @@ +use crate::sort::bin_layout::BIN_SORT_MIN; use crate::sort::buffer::{CopyFromNotOverlap, CopyNotOverlapValue, DoubleRangeSlices}; use crate::sort::key::{KeyFn, SortKey}; use crate::sort::mapper::Mapper; use crate::sort::serial::slice_two_keys::TwoKeysBinSortSerial; -use crate::sort::bin_layout::BIN_SORT_MIN; use crate::sort::two_keys::sort_unstable_by_two_keys; impl Mapper { @@ -39,7 +39,7 @@ impl Mapper { // SAFETY: mapper ranges never overlap; (src, buf) are distinct buffers. let sub_buf = unsafe { buf.get_unchecked_mut(range.clone()) }; sort_unstable_by_two_keys(sub_buf, key1, key2); - + if copy_to_src { src.copy_to_range_from_not_overlap(sub_buf, range); } diff --git a/iKeySort/src/sort/serial/mod.rs b/iKeySort/src/sort/serial/mod.rs index d6fb900..1b35834 100644 --- a/iKeySort/src/sort/serial/mod.rs +++ b/iKeySort/src/sort/serial/mod.rs @@ -2,6 +2,7 @@ mod layout_one_key; mod layout_one_key_cmp; mod layout_two_keys; mod layout_two_keys_cmp; +mod mapper_cmp; mod mapper_one_key; mod mapper_one_key_cmp; mod mapper_two_keys; @@ -10,4 +11,3 @@ pub(crate) mod slice_one_key; pub(crate) mod slice_one_key_cmp; pub(crate) mod slice_two_keys; pub(crate) mod slice_two_keys_cmp; -mod mapper_cmp; diff --git a/iKeySort/src/sort/serial/slice_one_key_cmp.rs b/iKeySort/src/sort/serial/slice_one_key_cmp.rs index afd92cf..8aee1ed 100644 --- a/iKeySort/src/sort/serial/slice_one_key_cmp.rs +++ b/iKeySort/src/sort/serial/slice_one_key_cmp.rs @@ -4,7 +4,6 @@ use crate::sort::key::{CmpFn, KeyFn, SortKey}; use alloc::vec::Vec; pub(crate) trait OneKeyBinSortCmpSerial { - fn ser_sort_by_one_key_then_by_and_buffer( &mut self, buf: &mut [T], @@ -110,7 +109,11 @@ mod tests { fn test(count: usize) { let mut org: Vec<_> = reversed_2d_array(count); let mut arr = org.clone(); - arr.ser_sort_by_one_key_then_by_and_uninit_buffer(&mut Vec::new(), |a| a.0, |a, b| a.1.cmp(&b.1)); + arr.ser_sort_by_one_key_then_by_and_uninit_buffer( + &mut Vec::new(), + |a| a.0, + |a, b| a.1.cmp(&b.1), + ); org.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1))); assert!(arr == org); } diff --git a/iKeySort/src/sort/spread.rs b/iKeySort/src/sort/spread.rs index cf714db..4476400 100644 --- a/iKeySort/src/sort/spread.rs +++ b/iKeySort/src/sort/spread.rs @@ -1,8 +1,8 @@ -use alloc::vec::Vec; -use core::mem::MaybeUninit; use crate::sort::bin_layout::BinLayout; use crate::sort::key::{KeyFn, SortKey}; use crate::sort::mapper::Mapper; +use alloc::vec::Vec; +use core::mem::MaybeUninit; impl BinLayout { #[inline(always)] @@ -30,11 +30,15 @@ impl BinLayout { for val in src.iter() { let index = mapper.next_index(self.index(key(val))); - unsafe { scratch.get_unchecked_mut(index).write(*val); } + unsafe { + scratch.get_unchecked_mut(index).write(*val); + } } #[allow(clippy::uninit_vec)] - unsafe { buf.set_len(need); } + unsafe { + buf.set_len(need); + } mapper } diff --git a/iKeySort/src/sort/two_keys.rs b/iKeySort/src/sort/two_keys.rs index d76bc29..42fdde4 100644 --- a/iKeySort/src/sort/two_keys.rs +++ b/iKeySort/src/sort/two_keys.rs @@ -161,17 +161,13 @@ where F1: KeyFn, F2: KeyFn, { - slice.sort_unstable_by(|a, b| { - key1(a) - .cmp(&key1(b)) - .then_with(|| key2(a).cmp(&key2(b))) - }) + slice.sort_unstable_by(|a, b| key1(a).cmp(&key1(b)).then_with(|| key2(a).cmp(&key2(b)))) } #[cfg(test)] mod tests { - use alloc::vec::Vec; use crate::sort::two_keys::TwoKeysSort; + use alloc::vec::Vec; #[test] fn test_0() {