Skip to main content

parallel_processor/
fast_smart_bucket_sort.rs

1use crate::buckets::bucket_writer::BucketItemSerializer;
2use rand::rng;
3use rand::RngCore;
4use rayon::prelude::*;
5use std::cell::UnsafeCell;
6use std::cmp::min;
7use std::cmp::Ordering;
8use std::fmt::Debug;
9use std::io::{Read, Write};
10use std::slice::from_raw_parts_mut;
11use std::sync::atomic::AtomicUsize;
12use unchecked_index::{unchecked_index, UncheckedIndex};
13
14type IndexType = usize;
15
16// #[repr(packed)]
17#[derive(Eq, PartialOrd, PartialEq, Ord, Copy, Clone, Debug)]
18pub struct SortedData<const LEN: usize> {
19    pub data: [u8; LEN],
20}
21
22impl<const LEN: usize> SortedData<LEN> {
23    #[inline(always)]
24    pub fn new(data: [u8; LEN]) -> Self {
25        Self { data }
26    }
27}
28
29impl<const LEN: usize> Default for SortedData<LEN> {
30    fn default() -> Self {
31        Self { data: [0; LEN] }
32    }
33}
34
35pub struct SortedDataSerializer<const LEN: usize>;
36impl<const LEN: usize> BucketItemSerializer for SortedDataSerializer<LEN> {
37    type InputElementType<'a> = SortedData<LEN>;
38    type ExtraData = ();
39    type ExtraDataBuffer = ();
40    type ReadBuffer = SortedData<LEN>;
41    type ReadType<'a> = &'a SortedData<LEN>;
42    type InitData = ();
43
44    type CheckpointData = ();
45
46    fn clear_buffer(_buffer: &mut Self::ReadBuffer) {}
47
48    #[inline(always)]
49    fn new(_: ()) -> Self {
50        Self
51    }
52
53    #[inline(always)]
54    fn reset(&mut self) {}
55
56    #[inline(always)]
57    fn write_to(
58        &mut self,
59        element: &Self::InputElementType<'_>,
60        bucket: &mut Vec<u8>,
61        _: &Self::ExtraData,
62        _: &Self::ExtraDataBuffer,
63    ) {
64        bucket.write(element.data.as_slice()).unwrap();
65    }
66
67    #[inline(always)]
68    fn read_from<'a, S: Read>(
69        &mut self,
70        mut stream: S,
71        read_buffer: &'a mut Self::ReadBuffer,
72        _: &mut Self::ExtraDataBuffer,
73    ) -> Option<Self::ReadType<'a>> {
74        stream.read(read_buffer.data.as_mut_slice()).ok()?;
75        Some(read_buffer)
76    }
77
78    #[inline(always)]
79    fn get_size(&self, _: &Self::InputElementType<'_>, _: &()) -> usize {
80        LEN
81    }
82}
83
84pub trait FastSortable: Ord {
85    fn get_shifted(&self, rhs: u8) -> u8;
86}
87
88macro_rules! fast_sortable_impl {
89    ($int_type:ty) => {
90        impl FastSortable for $int_type {
91            #[inline(always)]
92            fn get_shifted(&self, rhs: u8) -> u8 {
93                (*self >> rhs) as u8
94            }
95        }
96    };
97}
98
99fast_sortable_impl!(u8);
100fast_sortable_impl!(u16);
101fast_sortable_impl!(u32);
102fast_sortable_impl!(u64);
103fast_sortable_impl!(u128);
104
105pub trait SortKey<T> {
106    type KeyType: Ord;
107    const KEY_BITS: usize;
108    fn compare(left: &T, right: &T) -> Ordering;
109    fn get_shifted(value: &T, rhs: u8) -> u8;
110}
111
112#[macro_export]
113macro_rules! make_comparer {
114    ($Name:ident, $type_name:ty, $key:ident: $key_type:ty) => {
115        struct $Name;
116        impl SortKey<$type_name> for $Name {
117            type KeyType = $key_type;
118            const KEY_BITS: usize = std::mem::size_of::<$key_type>() * 8;
119
120            fn compare(left: &$type_name, right: &$type_name) -> std::cmp::Ordering {
121                left.$key.cmp(&right.$key)
122            }
123
124            fn get_shifted(value: &$type_name, rhs: u8) -> u8 {
125                (value.$key >> rhs) as u8
126            }
127        }
128    };
129}
130
131const RADIX_SIZE_LOG: u8 = 8;
132const RADIX_SIZE: usize = 1 << 8;
133
134// pub fn striped_parallel_smart_radix_sort_memfile<
135//     T: Ord + Send + Sync + Debug + 'static,
136//     F: SortKey<T>,
137// >(
138//     mem_file: FileReader,
139//     dest_buffer: &mut Vec<T>,
140// ) -> usize {
141//     let chunks: Vec<_> = unsafe { mem_file.get_typed_chunks_mut::<T>().collect() };
142//     let tot_entries = chunks.iter().map(|x| x.len()).sum();
143//
144//     dest_buffer.clear();
145//     dest_buffer.reserve(tot_entries);
146//     unsafe { dest_buffer.set_len(tot_entries) };
147//
148//     striped_parallel_smart_radix_sort::<T, F>(chunks.as_slice(), dest_buffer.as_mut_slice());
149//
150//     assert_eq!(dest_buffer.len(), chunks.iter().map(|x| x.len()).sum());
151//     assert_eq!(dest_buffer.len(), mem_file.len() / size_of::<T>());
152//
153//     drop(mem_file);
154//     tot_entries
155// }
156
157pub fn striped_parallel_smart_radix_sort<T: Ord + Send + Sync + Debug, F: SortKey<T>>(
158    striped_file: &[&mut [T]],
159    dest_buffer: &mut [T],
160) {
161    let num_threads = rayon::current_num_threads();
162    let queue = crossbeam::queue::ArrayQueue::new(num_threads);
163
164    let first_shift = F::KEY_BITS as u8 - RADIX_SIZE_LOG;
165
166    for _ in 0..num_threads {
167        queue.push([0; RADIX_SIZE + 1]).unwrap();
168    }
169
170    striped_file.par_iter().for_each(|chunk| {
171        let mut counts = queue.pop().unwrap();
172        for el in chunk.iter() {
173            counts[(F::get_shifted(el, first_shift)) as usize + 1] += 1usize;
174        }
175        queue.push(counts).unwrap();
176    });
177
178    let mut counters = [0; RADIX_SIZE + 1];
179    while let Some(counts) = queue.pop() {
180        for i in 1..(RADIX_SIZE + 1) {
181            counters[i] += counts[i];
182        }
183    }
184    const ATOMIC_USIZE_ZERO: AtomicUsize = AtomicUsize::new(0);
185    let offsets = [ATOMIC_USIZE_ZERO; RADIX_SIZE + 1];
186    let mut offsets_reference = [0; RADIX_SIZE + 1];
187
188    use std::sync::atomic::Ordering;
189    for i in 1..(RADIX_SIZE + 1) {
190        offsets_reference[i] = offsets[i - 1].load(Ordering::Relaxed) + counters[i];
191        offsets[i].store(offsets_reference[i], Ordering::Relaxed);
192    }
193
194    let dest_buffer_addr = dest_buffer.as_mut_ptr() as usize;
195    striped_file.par_iter().for_each(|chunk| {
196        let dest_buffer_ptr = dest_buffer_addr as *mut T;
197
198        let chunk_addr = chunk.as_ptr() as usize;
199        let chunk_data_mut = unsafe { from_raw_parts_mut(chunk_addr as *mut T, chunk.len()) };
200
201        let choffs = smart_radix_sort_::<T, F, false, true>(
202            chunk_data_mut,
203            F::KEY_BITS as u8 - RADIX_SIZE_LOG,
204        );
205        let mut offset = 0;
206        for idx in 1..(RADIX_SIZE + 1) {
207            let count = choffs[idx] - choffs[idx - 1];
208            let dest_position = offsets[idx - 1].fetch_add(count, Ordering::Relaxed);
209
210            unsafe {
211                std::ptr::copy_nonoverlapping(
212                    chunk.as_ptr().add(offset),
213                    dest_buffer_ptr.add(dest_position),
214                    count,
215                );
216            }
217
218            offset += count;
219        }
220    });
221
222    if F::KEY_BITS >= 16 {
223        let offsets_reference = offsets_reference;
224        (0..256usize).into_par_iter().for_each(|idx| {
225            let dest_buffer_ptr = dest_buffer_addr as *mut T;
226
227            let bucket_start = offsets_reference[idx];
228            let bucket_len = offsets_reference[idx + 1] - bucket_start;
229
230            let crt_slice =
231                unsafe { from_raw_parts_mut(dest_buffer_ptr.add(bucket_start), bucket_len) };
232            smart_radix_sort_::<T, F, false, false>(crt_slice, F::KEY_BITS as u8 - 16);
233        });
234    }
235}
236
237pub fn fast_smart_radix_sort<T: Sync + Send, F: SortKey<T>, const PARALLEL: bool>(data: &mut [T]) {
238    smart_radix_sort_::<T, F, PARALLEL, false>(data, F::KEY_BITS as u8 - RADIX_SIZE_LOG);
239}
240
241pub fn fast_smart_radix_sort_by_value<T: Sync + Send, F: SortKey<T>, const PARALLEL: bool>(
242    data: &mut [T],
243) {
244    smart_radix_sort_::<T, F, PARALLEL, false>(data, F::KEY_BITS as u8 - RADIX_SIZE_LOG);
245}
246
247fn smart_radix_sort_<
248    T: Sync + Send,
249    F: SortKey<T>,
250    const PARALLEL: bool,
251    const SINGLE_STEP: bool,
252>(
253    data: &mut [T],
254    shift: u8,
255) -> [IndexType; RADIX_SIZE + 1] {
256    let mut stack = unsafe { unchecked_index(vec![(0..0, 0); shift as usize * RADIX_SIZE]) };
257
258    let mut stack_index = 1;
259    stack[0] = (0..data.len(), shift);
260
261    let mut ret_counts = [0; RADIX_SIZE + 1];
262
263    let mut first = true;
264
265    while stack_index > 0 {
266        stack_index -= 1;
267        let (range, shift) = stack[stack_index].clone();
268
269        let mut data = unsafe { unchecked_index(&mut data[range.clone()]) };
270
271        let mut counts: UncheckedIndex<[IndexType; RADIX_SIZE + 1]> =
272            unsafe { unchecked_index([0; RADIX_SIZE + 1]) };
273        let mut sums: UncheckedIndex<[IndexType; RADIX_SIZE + 1]>;
274
275        {
276            if PARALLEL {
277                const ATOMIC_ZERO: AtomicUsize = AtomicUsize::new(0);
278                let par_counts: UncheckedIndex<[AtomicUsize; RADIX_SIZE + 1]> =
279                    unsafe { unchecked_index([ATOMIC_ZERO; RADIX_SIZE + 1]) };
280                let num_threads = rayon::current_num_threads();
281                let chunk_size = (data.len() + num_threads - 1) / num_threads;
282                data.chunks(chunk_size).par_bridge().for_each(|chunk| {
283                    let mut thread_counts = unsafe { unchecked_index([0; RADIX_SIZE + 1]) };
284
285                    for el in chunk {
286                        thread_counts[(F::get_shifted(el, shift)) as usize + 1] += 1;
287                    }
288
289                    for (p, t) in par_counts.iter().zip(thread_counts.iter()) {
290                        p.fetch_add(*t, std::sync::atomic::Ordering::Relaxed);
291                    }
292                });
293
294                for i in 1..(RADIX_SIZE + 1) {
295                    counts[i] =
296                        counts[i - 1] + par_counts[i].load(std::sync::atomic::Ordering::Relaxed);
297                }
298                sums = counts;
299
300                let mut bucket_queues = Vec::with_capacity(RADIX_SIZE);
301                for i in 0..RADIX_SIZE {
302                    bucket_queues.push(crossbeam::channel::unbounded());
303
304                    let range = sums[i]..counts[i + 1];
305                    let range_steps = num_threads * 2;
306                    let tot_range_len = range.len();
307                    let subrange_len = (tot_range_len + range_steps - 1) / range_steps;
308
309                    let mut start = range.start;
310                    while start < range.end {
311                        let end = min(start + subrange_len, range.end);
312                        if start < end {
313                            bucket_queues[i].0.send(start..end).unwrap();
314                        }
315                        start += subrange_len;
316                    }
317                }
318
319                let data_ptr = data.as_mut_ptr() as usize;
320                (0..num_threads).into_par_iter().for_each(|thread_index| {
321                    let mut start_buckets = unsafe { unchecked_index([0; RADIX_SIZE]) };
322                    let mut end_buckets = unsafe { unchecked_index([0; RADIX_SIZE]) };
323
324                    let data = unsafe { from_raw_parts_mut(data_ptr as *mut T, data.len()) };
325
326                    let get_bpart = || {
327                        let start = rng().next_u32() as usize % RADIX_SIZE;
328                        let mut res = None;
329                        for i in 0..RADIX_SIZE {
330                            let bucket_num = (i + start) % RADIX_SIZE;
331                            if let Ok(val) = bucket_queues[bucket_num].1.try_recv() {
332                                res = Some((bucket_num, val));
333                                break;
334                            }
335                        }
336                        res
337                    };
338
339                    let mut buckets_stack: Vec<_> = vec![];
340
341                    while let Some((bidx, bpart)) = get_bpart() {
342                        start_buckets[bidx] = bpart.start;
343                        end_buckets[bidx] = bpart.end;
344                        buckets_stack.push(bidx);
345
346                        while let Some(bucket) = buckets_stack.pop() {
347                            while start_buckets[bucket] < end_buckets[bucket] {
348                                let val =
349                                    (F::get_shifted(&data[start_buckets[bucket]], shift)) as usize;
350
351                                while start_buckets[val] == end_buckets[val] {
352                                    let next_bucket = match bucket_queues[val].1.try_recv() {
353                                        Ok(val) => val,
354                                        Err(_) => {
355                                            // Final thread
356                                            if thread_index == num_threads - 1 {
357                                                bucket_queues[val].1.recv().unwrap()
358                                            } else {
359                                                // Non final thread, exit and let the final thread finish the computation
360                                                for i in 0..RADIX_SIZE {
361                                                    if start_buckets[i] < end_buckets[i] {
362                                                        bucket_queues[i]
363                                                            .0
364                                                            .send(start_buckets[i]..end_buckets[i])
365                                                            .unwrap();
366                                                    }
367                                                }
368                                                return;
369                                            }
370                                        }
371                                    };
372                                    start_buckets[val] = next_bucket.start;
373                                    end_buckets[val] = next_bucket.end;
374                                    buckets_stack.push(val);
375                                }
376
377                                data.swap(start_buckets[bucket], start_buckets[val]);
378                                start_buckets[val] += 1;
379                            }
380                        }
381                    }
382                });
383            } else {
384                for el in data.iter() {
385                    counts[(F::get_shifted(el, shift)) as usize + 1] += 1;
386                }
387
388                for i in 1..(RADIX_SIZE + 1) {
389                    counts[i] += counts[i - 1];
390                }
391                sums = counts;
392
393                for bucket in 0..RADIX_SIZE {
394                    let end = counts[bucket + 1];
395                    while sums[bucket] < end {
396                        let val = (F::get_shifted(&data[sums[bucket]], shift)) as usize;
397                        data.swap(sums[bucket], sums[val]);
398                        sums[val] += 1;
399                    }
400                }
401            }
402        }
403
404        if first {
405            ret_counts = *counts;
406            first = false;
407        }
408
409        struct UCWrapper<T> {
410            uc: UnsafeCell<T>,
411        }
412        unsafe impl<T> Sync for UCWrapper<T> {}
413        let data_ptr = UCWrapper {
414            uc: UnsafeCell::new(data),
415        };
416
417        if !SINGLE_STEP && shift >= RADIX_SIZE_LOG {
418            if PARALLEL && shift as usize == (F::KEY_BITS - RADIX_SIZE_LOG as usize) {
419                let data_ptr = &data_ptr;
420                (0..256usize)
421                    .into_par_iter()
422                    .filter(|x| (counts[(*x as usize) + 1] - counts[*x as usize]) > 1)
423                    .for_each(|i| {
424                        let mut data_ptr = unsafe { std::ptr::read(data_ptr.uc.get()) };
425                        let slice = &mut data_ptr[counts[i] as usize..counts[i + 1] as usize];
426                        smart_radix_sort_::<T, F, false, false>(slice, shift - RADIX_SIZE_LOG);
427                    });
428            } else {
429                (0..RADIX_SIZE).into_iter().for_each(|i| {
430                    let slice_len = counts[i + 1] - counts[i];
431                    let mut data_ptr = unsafe { std::ptr::read(data_ptr.uc.get()) };
432
433                    match slice_len {
434                        2 => {
435                            if F::compare(&data_ptr[counts[i]], &data_ptr[counts[i] + 1])
436                                == Ordering::Greater
437                            {
438                                data_ptr.swap(counts[i], counts[i] + 1);
439                            }
440                        }
441                        0 | 1 => return,
442
443                        _ => {}
444                    }
445
446                    if slice_len < 192 {
447                        let slice = &mut data_ptr[counts[i] as usize..counts[i + 1] as usize];
448                        slice.sort_unstable_by(F::compare);
449                        return;
450                    }
451
452                    stack[stack_index] = (
453                        range.start + counts[i] as usize..range.start + counts[i + 1] as usize,
454                        shift - RADIX_SIZE_LOG,
455                    );
456                    stack_index += 1;
457                });
458            }
459        }
460    }
461    ret_counts
462}
463
464#[cfg(test)]
465mod tests {
466    use crate::fast_smart_bucket_sort::{fast_smart_radix_sort, SortKey};
467    use rand::{rng, RngCore};
468    use std::time::Instant;
469    use voracious_radix_sort::RadixSort;
470
471    #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
472    struct DataTypeStruct(u128, [u8; 32 - 16]);
473
474    struct U64SortKey;
475    impl SortKey<DataTypeStruct> for U64SortKey {
476        type KeyType = u128;
477        const KEY_BITS: usize = std::mem::size_of::<u128>() * 8;
478
479        #[inline(always)]
480        fn compare(left: &DataTypeStruct, right: &DataTypeStruct) -> std::cmp::Ordering {
481            left.0.cmp(&right.0)
482        }
483
484        #[inline(always)]
485        fn get_shifted(value: &DataTypeStruct, rhs: u8) -> u8 {
486            (value.0 >> rhs) as u8
487        }
488    }
489
490    #[test]
491    #[ignore]
492    fn parallel_sorting() {
493        const ARRAY_SIZE: usize = 5000000000;
494
495        let mut vec = Vec::with_capacity(ARRAY_SIZE);
496
497        let mut rng = rng();
498
499        for _ in 0..ARRAY_SIZE {
500            vec.push((rng.next_u32()) as u32);
501        }
502        let mut vec2 = vec.clone();
503
504        crate::log_info!("Starting...");
505        let start = Instant::now();
506
507        struct U16SortKey;
508        impl SortKey<u32> for U16SortKey {
509            type KeyType = u32;
510            const KEY_BITS: usize = std::mem::size_of::<u32>() * 8;
511
512            #[inline(always)]
513            fn compare(left: &u32, right: &u32) -> std::cmp::Ordering {
514                left.cmp(&right)
515            }
516
517            #[inline(always)]
518            fn get_shifted(value: &u32, rhs: u8) -> u8 {
519                (value >> rhs) as u8
520            }
521        }
522
523        fast_smart_radix_sort::<_, U16SortKey, true>(vec.as_mut_slice());
524
525        let end = start.elapsed();
526        crate::log_info!("Total time: {:.2?}", end);
527
528        crate::log_info!("Starting2...");
529        let start = Instant::now();
530
531        vec2.voracious_mt_sort(16);
532        let end = start.elapsed();
533        crate::log_info!("Total time 2: {:.2?}", end);
534    }
535
536    // #[test]
537    // fn sorting_test() {
538    //     let mut data = vec![DataTypeStruct(0, [0; 32 - 16]); VEC_SIZE];
539    //
540    //     data.par_iter_mut()
541    //         .enumerate()
542    //         .for_each(|(i, x)| *x = DataTypeStruct(rng().gen(), [2; 32 - 16]));
543    //
544    //     crate::log_info!("Started sorting...");
545    //     let start = Instant::now();
546    //     fast_smart_radix_sort::<_, U64SortKey, true>(data.as_mut_slice());
547    //     crate::log_info!("Done sorting => {:.2?}!", start.elapsed());
548    //     assert!(data.is_sorted_by(|a, b| {
549    //         Some(match a.cmp(b) {
550    //             Ordering::Less => Ordering::Less,
551    //             Ordering::Equal => Ordering::Equal,
552    //             Ordering::Greater => {
553    //                 panic!("{:?} > {:?}!", a, b);
554    //             }
555    //         })
556    //     }));
557    // }
558}