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#[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
134pub 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 if thread_index == num_threads - 1 {
357 bucket_queues[val].1.recv().unwrap()
358 } else {
359 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 }