use std::{
cmp::Ordering as CmpOrdering,
collections::BinaryHeap,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
};
use crossbeam_deque::{Injector, Worker};
use vibesql_storage::Row;
use super::{
config::{global_config, MorselConfig},
create_morsels, morsel_debug_enabled, steal_morsel, Morsel, MorselResultsOrdered,
};
pub fn morsel_parallel_sort<F>(rows: &[Row], config: &MorselConfig, compare: F) -> Vec<Row>
where
F: Fn(&Row, &Row) -> CmpOrdering + Sync + Send,
{
if rows.is_empty() {
return Vec::new();
}
let morsel_size = config.sort_size;
if rows.len() < morsel_size {
let mut result = rows.to_vec();
result.sort_by(&compare);
return result;
}
let morsels = create_morsels(rows.len(), morsel_size);
let morsel_count = morsels.len();
if morsel_debug_enabled() {
eprintln!(
"[MORSEL] Sort: {} morsels for {} rows (size={})",
morsel_count,
rows.len(),
morsel_size
);
}
let injector: Injector<Morsel> = Injector::new();
for morsel in morsels {
injector.push(morsel);
}
let results: MorselResultsOrdered = Arc::new(Mutex::new(Vec::with_capacity(morsel_count)));
let morsel_index = Arc::new(AtomicUsize::new(0));
rayon::scope(|s| {
let num_threads = rayon::current_num_threads();
for _ in 0..num_threads {
let injector_ref = &injector;
let compare_ref = &compare;
let results_ref = results.clone();
let morsel_index_ref = morsel_index.clone();
s.spawn(move |_| {
let worker: Worker<Morsel> = Worker::new_fifo();
while let Some(m) = steal_morsel(injector_ref, &worker) {
let idx = morsel_index_ref.fetch_add(1, Ordering::SeqCst);
let mut sorted: Vec<Row> = m.rows(rows).to_vec();
sorted.sort_by(compare_ref);
if morsel_debug_enabled() {
eprintln!("[MORSEL] Thread sorted morsel {} ({} rows)", idx, sorted.len());
}
results_ref.lock().unwrap().push((idx, sorted));
}
});
}
});
let mut sorted_morsels = Arc::try_unwrap(results)
.expect("all threads should have completed")
.into_inner()
.expect("mutex not poisoned");
sorted_morsels.sort_by_key(|(idx, _)| *idx);
if morsel_debug_enabled() {
eprintln!("[MORSEL] Sort phase 1 complete: {} sorted morsels", sorted_morsels.len());
}
if sorted_morsels.len() == 1 {
return sorted_morsels.pop().unwrap().1;
}
let sorted_chunks: Vec<Vec<Row>> = sorted_morsels.into_iter().map(|(_, rows)| rows).collect();
let result = kway_merge(sorted_chunks, &compare);
if morsel_debug_enabled() {
eprintln!("[MORSEL] Sort complete: {} total rows", result.len());
}
result
}
fn kway_merge<F>(sorted_chunks: Vec<Vec<Row>>, compare: &F) -> Vec<Row>
where
F: Fn(&Row, &Row) -> CmpOrdering,
{
if sorted_chunks.is_empty() {
return Vec::new();
}
if sorted_chunks.len() == 1 {
return sorted_chunks.into_iter().next().unwrap();
}
let total_size: usize = sorted_chunks.iter().map(|c| c.len()).sum();
let mut result = Vec::with_capacity(total_size);
let mut iters: Vec<std::vec::IntoIter<Row>> =
sorted_chunks.into_iter().map(|c| c.into_iter()).collect();
let mut heap: BinaryHeap<MergeItem<F>> = BinaryHeap::new();
for (chunk_idx, iter) in iters.iter_mut().enumerate() {
if let Some(row) = iter.next() {
heap.push(MergeItem { row, chunk_idx, compare });
}
}
while let Some(MergeItem { row, chunk_idx, .. }) = heap.pop() {
result.push(row);
if let Some(next_row) = iters[chunk_idx].next() {
heap.push(MergeItem { row: next_row, chunk_idx, compare });
}
}
result
}
struct MergeItem<'a, F> {
row: Row,
chunk_idx: usize,
compare: &'a F,
}
impl<'a, F> PartialEq for MergeItem<'a, F>
where
F: Fn(&Row, &Row) -> CmpOrdering,
{
fn eq(&self, other: &Self) -> bool {
(self.compare)(&self.row, &other.row) == CmpOrdering::Equal
}
}
impl<'a, F> Eq for MergeItem<'a, F> where F: Fn(&Row, &Row) -> CmpOrdering {}
impl<'a, F> PartialOrd for MergeItem<'a, F>
where
F: Fn(&Row, &Row) -> CmpOrdering,
{
fn partial_cmp(&self, other: &Self) -> Option<CmpOrdering> {
Some(self.cmp(other))
}
}
impl<'a, F> Ord for MergeItem<'a, F>
where
F: Fn(&Row, &Row) -> CmpOrdering,
{
fn cmp(&self, other: &Self) -> CmpOrdering {
(self.compare)(&other.row, &self.row)
}
}
pub fn morsel_sort_by<F>(rows: &[Row], compare: F) -> Vec<Row>
where
F: Fn(&Row, &Row) -> CmpOrdering + Sync + Send,
{
morsel_parallel_sort(rows, global_config(), compare)
}