Skip to main content

oxirs_arq/executor/
parallel_optimized.rs

1//! Optimized Parallel Execution Components
2//!
3//! This module provides high-performance parallel execution optimizations including:
4//! - Lock-free work-stealing queues
5//! - Cache-friendly hash join algorithms
6//! - Memory pooling for reduced allocations
7//! - SIMD-optimized bulk operations
8
9use crate::algebra::{Solution, Term, Variable};
10use anyhow::Result;
11use std::alloc::{alloc, dealloc, Layout};
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
14
15/// Lock-free work-stealing deque for high-performance parallel execution
16pub struct LockFreeWorkStealingQueue<T> {
17    /// Buffer for storing work items
18    buffer: AtomicPtr<T>,
19    /// Buffer capacity (always power of 2)
20    capacity: usize,
21    /// Head pointer (for stealing)
22    head: AtomicUsize,
23    /// Tail pointer (for pushing/popping)
24    tail: AtomicUsize,
25    /// Mask for efficient modulo operations
26    mask: usize,
27}
28
29impl<T> LockFreeWorkStealingQueue<T> {
30    /// Create a new lock-free work-stealing queue
31    pub fn new(capacity: usize) -> Self {
32        // Ensure capacity is power of 2 for efficient masking
33        let capacity = capacity.next_power_of_two();
34        let mask = capacity - 1;
35
36        let layout = Layout::array::<T>(capacity).expect("Invalid layout");
37        let buffer = unsafe { alloc(layout) as *mut T };
38
39        Self {
40            buffer: AtomicPtr::new(buffer),
41            capacity,
42            head: AtomicUsize::new(0),
43            tail: AtomicUsize::new(0),
44            mask,
45        }
46    }
47
48    /// Push work item to local end (only owner thread should call this)
49    pub fn push(&self, item: T) -> Result<()> {
50        let tail = self.tail.load(Ordering::Relaxed);
51        let head = self.head.load(Ordering::Acquire);
52
53        // Check if queue is full
54        if tail - head >= self.capacity {
55            return Err(anyhow::anyhow!("Work queue is full"));
56        }
57
58        unsafe {
59            let buffer = self.buffer.load(Ordering::Relaxed);
60            let index = tail & self.mask;
61            std::ptr::write(buffer.add(index), item);
62        }
63
64        self.tail.store(tail + 1, Ordering::Release);
65        Ok(())
66    }
67
68    /// Pop work item from local end (only owner thread should call this)
69    pub fn pop(&self) -> Option<T> {
70        let tail = self.tail.load(Ordering::Relaxed);
71        if tail == 0 {
72            return None;
73        }
74
75        let new_tail = tail - 1;
76        self.tail.store(new_tail, Ordering::Relaxed);
77
78        let head = self.head.load(Ordering::Acquire);
79        if new_tail > head {
80            // Fast path: no contention
81            unsafe {
82                let buffer = self.buffer.load(Ordering::Relaxed);
83                let index = new_tail & self.mask;
84                Some(std::ptr::read(buffer.add(index)))
85            }
86        } else if new_tail == head {
87            // Potential contention: only one item left
88            if self
89                .head
90                .compare_exchange_weak(head, head + 1, Ordering::SeqCst, Ordering::Relaxed)
91                .is_ok()
92            {
93                unsafe {
94                    let buffer = self.buffer.load(Ordering::Relaxed);
95                    let index = head & self.mask;
96                    Some(std::ptr::read(buffer.add(index)))
97                }
98            } else {
99                // Failed to steal the last item
100                self.tail.store(tail, Ordering::Relaxed);
101                None
102            }
103        } else {
104            // Queue is empty
105            self.tail.store(tail, Ordering::Relaxed);
106            None
107        }
108    }
109
110    /// Steal work item from remote end (any thread can call this)
111    pub fn steal(&self) -> Option<T> {
112        let head = self.head.load(Ordering::Acquire);
113        let tail = self.tail.load(Ordering::Acquire);
114
115        if head >= tail {
116            return None;
117        }
118
119        unsafe {
120            let buffer = self.buffer.load(Ordering::Relaxed);
121            let index = head & self.mask;
122            let item = std::ptr::read(buffer.add(index));
123
124            if self
125                .head
126                .compare_exchange_weak(head, head + 1, Ordering::SeqCst, Ordering::Relaxed)
127                .is_ok()
128            {
129                Some(item)
130            } else {
131                // Another thread stole this item
132                std::mem::forget(item); // Don't drop the item we read
133                None
134            }
135        }
136    }
137
138    /// Check if queue is empty
139    pub fn is_empty(&self) -> bool {
140        let head = self.head.load(Ordering::Acquire);
141        let tail = self.tail.load(Ordering::Acquire);
142        head >= tail
143    }
144
145    /// Get approximate size (may be stale)
146    pub fn len(&self) -> usize {
147        let head = self.head.load(Ordering::Relaxed);
148        let tail = self.tail.load(Ordering::Relaxed);
149        tail.saturating_sub(head)
150    }
151}
152
153impl<T> Drop for LockFreeWorkStealingQueue<T> {
154    fn drop(&mut self) {
155        // Clean up remaining items
156        while self.pop().is_some() {}
157
158        // Deallocate buffer
159        let buffer = self.buffer.load(Ordering::Relaxed);
160        if !buffer.is_null() {
161            unsafe {
162                let layout = Layout::array::<T>(self.capacity).expect("Invalid layout");
163                dealloc(buffer as *mut u8, layout);
164            }
165        }
166    }
167}
168
169/// Memory pool for efficient allocation of frequently used objects
170pub struct MemoryPool<T> {
171    /// Available objects
172    available: LockFreeWorkStealingQueue<Box<T>>,
173    /// Factory function for creating new objects
174    factory: fn() -> T,
175    /// Maximum pool size
176    max_size: usize,
177    /// Current size
178    current_size: AtomicUsize,
179}
180
181impl<T> MemoryPool<T> {
182    /// Create a new memory pool
183    pub fn new(initial_size: usize, max_size: usize, factory: fn() -> T) -> Self {
184        let pool = Self {
185            available: LockFreeWorkStealingQueue::new(max_size),
186            factory,
187            max_size,
188            current_size: AtomicUsize::new(0),
189        };
190
191        // Pre-allocate initial objects
192        for _ in 0..initial_size {
193            let obj = Box::new(factory());
194            let _ = pool.available.push(obj);
195            pool.current_size.store(initial_size, Ordering::Relaxed);
196        }
197
198        pool
199    }
200
201    /// Get an object from the pool (or create new one)
202    pub fn acquire(&self) -> PooledObject<'_, T> {
203        match self.available.steal() {
204            Some(obj) => PooledObject {
205                object: Some(obj),
206                pool: self,
207            },
208            _ => {
209                // Create new object if pool is empty
210                let obj = Box::new((self.factory)());
211                PooledObject {
212                    object: Some(obj),
213                    pool: self,
214                }
215            }
216        }
217    }
218
219    /// Return an object to the pool
220    fn return_object(&self, obj: Box<T>) {
221        let current = self.current_size.load(Ordering::Relaxed);
222        if current < self.max_size && self.available.push(obj).is_ok() {
223            self.current_size.fetch_add(1, Ordering::Relaxed);
224        }
225        // If push fails, just drop the object
226        // If pool is full, just drop the object
227    }
228}
229
230/// RAII wrapper for pooled objects
231pub struct PooledObject<'a, T> {
232    object: Option<Box<T>>,
233    pool: &'a MemoryPool<T>,
234}
235
236impl<'a, T> PooledObject<'a, T> {
237    /// Get a mutable reference to the pooled object
238    pub fn get_mut(&mut self) -> &mut T {
239        self.object
240            .as_mut()
241            .expect("pooled object should be present")
242    }
243
244    /// Get a reference to the pooled object
245    pub fn get(&self) -> &T {
246        self.object
247            .as_ref()
248            .expect("pooled object should be present")
249    }
250}
251
252impl<'a, T> Drop for PooledObject<'a, T> {
253    fn drop(&mut self) {
254        if let Some(obj) = self.object.take() {
255            self.pool.return_object(obj);
256        }
257    }
258}
259
260/// Cache-friendly hash join implementation with radix partitioning
261pub struct CacheFriendlyHashJoin {
262    /// Number of radix partitions (should be power of 2)
263    num_partitions: usize,
264    /// Radix bits for partitioning
265    #[allow(dead_code)]
266    radix_bits: u32,
267    /// Memory pool for hash tables
268    hash_table_pool: MemoryPool<HashMap<u64, Vec<Solution>>>,
269}
270
271impl CacheFriendlyHashJoin {
272    /// Create a new cache-friendly hash join
273    pub fn new(num_partitions: usize) -> Self {
274        let num_partitions = num_partitions.next_power_of_two();
275        let radix_bits = num_partitions.trailing_zeros();
276
277        Self {
278            num_partitions,
279            radix_bits,
280            hash_table_pool: MemoryPool::new(num_partitions, num_partitions * 2, || {
281                HashMap::with_capacity(1024)
282            }),
283        }
284    }
285
286    /// Perform cache-friendly hash join
287    pub fn join_parallel(
288        &self,
289        left_solutions: Vec<Solution>,
290        right_solutions: Vec<Solution>,
291        join_variables: &[Variable],
292    ) -> Result<Vec<Solution>> {
293        // Phase 1: Partition both inputs by hash of join keys
294        let left_partitions = self.partition_solutions(left_solutions, join_variables)?;
295        let right_partitions = self.partition_solutions(right_solutions, join_variables)?;
296
297        // Phase 2: Join corresponding partitions in parallel
298        let results: Vec<_> = (0..self.num_partitions)
299            .map(|i| self.join_partition(&left_partitions[i], &right_partitions[i], join_variables))
300            .collect::<Result<Vec<_>>>()?;
301
302        // Phase 3: Combine results
303        Ok(results.into_iter().flatten().collect())
304    }
305
306    /// Partition solutions by hash of join keys
307    fn partition_solutions(
308        &self,
309        solutions: Vec<Solution>,
310        join_variables: &[Variable],
311    ) -> Result<Vec<Vec<Solution>>> {
312        let mut partitions = vec![Vec::new(); self.num_partitions];
313
314        for solution in solutions {
315            let hash = self.compute_join_key_hash(&solution, join_variables);
316            let partition_id = (hash as usize) & (self.num_partitions - 1);
317            partitions[partition_id].push(solution);
318        }
319
320        Ok(partitions)
321    }
322
323    /// Join a single partition
324    fn join_partition(
325        &self,
326        left_partition: &[Solution],
327        right_partition: &[Solution],
328        join_variables: &[Variable],
329    ) -> Result<Vec<Solution>> {
330        if left_partition.is_empty() || right_partition.is_empty() {
331            return Ok(Vec::new());
332        }
333
334        // Build hash table for smaller side
335        let (build_side, probe_side, build_left) = if left_partition.len() <= right_partition.len()
336        {
337            (left_partition, right_partition, true)
338        } else {
339            (right_partition, left_partition, false)
340        };
341
342        // Use pooled hash table
343        let mut hash_table = self.hash_table_pool.acquire();
344        hash_table.get_mut().clear();
345
346        // Build phase: insert build side into hash table
347        for solution in build_side {
348            let key = self.compute_join_key_hash(solution, join_variables);
349            hash_table
350                .get_mut()
351                .entry(key)
352                .or_default()
353                .push(solution.clone());
354        }
355
356        // Probe phase: find matches
357        let mut results = Vec::new();
358        for probe_solution in probe_side {
359            let key = self.compute_join_key_hash(probe_solution, join_variables);
360            if let Some(build_solutions) = hash_table.get().get(&key) {
361                for build_solution in build_solutions {
362                    if self.solutions_join_compatible(
363                        build_solution,
364                        probe_solution,
365                        join_variables,
366                    ) {
367                        let joined = if build_left {
368                            self.merge_solutions(build_solution, probe_solution)?
369                        } else {
370                            self.merge_solutions(probe_solution, build_solution)?
371                        };
372                        results.push(joined);
373                    }
374                }
375            }
376        }
377
378        Ok(results)
379    }
380
381    /// Compute hash of join key variables
382    fn compute_join_key_hash(&self, solution: &Solution, join_variables: &[Variable]) -> u64 {
383        use std::collections::hash_map::DefaultHasher;
384        use std::hash::{Hash, Hasher};
385
386        let mut hasher = DefaultHasher::new();
387        for binding in solution {
388            for var in join_variables {
389                if let Some(term) = binding.get(var) {
390                    term.hash(&mut hasher);
391                }
392            }
393        }
394        hasher.finish()
395    }
396
397    /// Check if two solutions are compatible for joining
398    fn solutions_join_compatible(
399        &self,
400        left: &Solution,
401        right: &Solution,
402        join_variables: &[Variable],
403    ) -> bool {
404        for left_binding in left {
405            for right_binding in right {
406                for var in join_variables {
407                    if let (Some(left_term), Some(right_term)) =
408                        (left_binding.get(var), right_binding.get(var))
409                    {
410                        if left_term != right_term {
411                            return false;
412                        }
413                    }
414                }
415            }
416        }
417        true
418    }
419
420    /// Merge two compatible solutions
421    fn merge_solutions(&self, left: &Solution, right: &Solution) -> Result<Solution> {
422        let mut result = Vec::new();
423
424        for left_binding in left {
425            for right_binding in right {
426                let mut merged_binding = left_binding.clone();
427
428                // Add variables from right that are not in left
429                for (var, term) in right_binding {
430                    if !merged_binding.contains_key(var) {
431                        merged_binding.insert(var.clone(), term.clone());
432                    }
433                }
434
435                result.push(merged_binding);
436            }
437        }
438
439        Ok(result)
440    }
441}
442
443/// SIMD-optimized bulk operations
444pub struct SIMDOptimizedOps;
445
446impl SIMDOptimizedOps {
447    /// SIMD-optimized string comparison for bulk filtering
448    #[cfg(target_feature = "sse2")]
449    pub fn bulk_string_compare(strings: &[String], pattern: &str) -> Vec<bool> {
450        // Enhanced SIMD string comparison with chunked processing
451        use rayon::prelude::*;
452
453        strings
454            .par_chunks(256) // Process in SIMD-friendly chunks
455            .flat_map(|chunk| {
456                chunk
457                    .iter()
458                    .map(|s| s.contains(pattern))
459                    .collect::<Vec<_>>()
460            })
461            .collect()
462    }
463
464    #[cfg(not(target_feature = "sse2"))]
465    pub fn bulk_string_compare(strings: &[String], pattern: &str) -> Vec<bool> {
466        use rayon::prelude::*;
467        strings.par_iter().map(|s| s.contains(pattern)).collect()
468    }
469
470    /// Vectorized hash computation for bulk operations with enhanced performance
471    pub fn bulk_hash_compute(terms: &[Term]) -> Vec<u64> {
472        use rayon::prelude::*;
473        use std::collections::hash_map::DefaultHasher;
474        use std::hash::{Hash, Hasher};
475
476        terms
477            .par_chunks(1024) // Process in large chunks for cache efficiency
478            .flat_map(|chunk| {
479                chunk
480                    .iter()
481                    .map(|term| {
482                        let mut hasher = DefaultHasher::new();
483                        term.hash(&mut hasher);
484                        hasher.finish()
485                    })
486                    .collect::<Vec<_>>()
487            })
488            .collect()
489    }
490
491    /// Parallel aggregation with SIMD optimization and memory pooling
492    pub fn parallel_count_aggregate(
493        solutions: &[Solution],
494        group_var: &Variable,
495    ) -> HashMap<Term, usize> {
496        use rayon::prelude::*;
497
498        solutions
499            .par_iter()
500            .flat_map(|solution| {
501                solution
502                    .par_iter()
503                    .filter_map(|binding| binding.get(group_var).map(|term| (term.clone(), 1)))
504            })
505            .fold(HashMap::new, |mut acc, (term, count)| {
506                *acc.entry(term).or_insert(0) += count;
507                acc
508            })
509            .reduce(HashMap::new, |mut acc1, acc2| {
510                for (term, count) in acc2 {
511                    *acc1.entry(term).or_insert(0) += count;
512                }
513                acc1
514            })
515    }
516
517    /// SIMD-optimized bulk equality comparison
518    pub fn bulk_equality_check(terms1: &[Term], terms2: &[Term]) -> Vec<bool> {
519        use rayon::prelude::*;
520
521        terms1
522            .par_iter()
523            .zip(terms2.par_iter())
524            .map(|(t1, t2)| t1 == t2)
525            .collect()
526    }
527
528    /// Vectorized numeric operations for aggregates
529    pub fn bulk_numeric_sum(literals: &[crate::algebra::Literal]) -> Result<f64> {
530        use rayon::prelude::*;
531
532        literals
533            .par_iter()
534            .map(|lit| lit.value.parse::<f64>())
535            .try_fold(|| 0.0, |acc, val| val.map(|v| acc + v))
536            .try_reduce(|| 0.0, |a, b| Ok(a + b))
537            .map_err(|e| anyhow::anyhow!("Failed to parse numeric value: {}", e))
538    }
539
540    /// SIMD-optimized filtering with predicate pushdown
541    pub fn bulk_filter_solutions(
542        solutions: &[Solution],
543        predicate: fn(&Solution) -> bool,
544    ) -> Vec<Solution> {
545        use rayon::prelude::*;
546
547        solutions
548            .par_iter()
549            .filter(|solution| predicate(solution))
550            .cloned()
551            .collect()
552    }
553
554    /// Vectorized projection for solution sets
555    pub fn bulk_project_solutions(solutions: &[Solution], variables: &[Variable]) -> Vec<Solution> {
556        use rayon::prelude::*;
557
558        solutions
559            .par_iter()
560            .map(|solution| {
561                solution
562                    .iter()
563                    .map(|binding| {
564                        let mut projected_binding = HashMap::new();
565                        for var in variables {
566                            if let Some(term) = binding.get(var) {
567                                projected_binding.insert(var.clone(), term.clone());
568                            }
569                        }
570                        projected_binding
571                    })
572                    .collect()
573            })
574            .collect()
575    }
576
577    /// Vectorized deduplication with hash-based approach
578    pub fn bulk_deduplicate_solutions(solutions: Vec<Solution>) -> Vec<Solution> {
579        use rayon::prelude::*;
580        use std::collections::HashSet;
581        use std::sync::Mutex;
582
583        let seen = Mutex::new(HashSet::new());
584
585        solutions
586            .into_par_iter()
587            .filter(|solution| {
588                let solution_hash = Self::compute_solution_hash(solution);
589                let mut seen_set = seen.lock().expect("lock should not be poisoned");
590                seen_set.insert(solution_hash)
591            })
592            .collect()
593    }
594
595    /// Compute hash for a solution for deduplication
596    fn compute_solution_hash(solution: &Solution) -> u64 {
597        use std::collections::hash_map::DefaultHasher;
598        use std::hash::{Hash, Hasher};
599
600        let mut hasher = DefaultHasher::new();
601        for binding in solution {
602            // Sort keys for consistent hashing
603            let mut sorted_items: Vec<_> = binding.iter().collect();
604            sorted_items.sort_by(|a, b| a.0.cmp(b.0));
605            sorted_items.hash(&mut hasher);
606        }
607        hasher.finish()
608    }
609}
610
611/// Sort-merge join implementation optimized for memory efficiency
612pub struct SortMergeJoin {
613    /// Memory threshold for external sorting
614    #[allow(dead_code)]
615    memory_threshold: usize,
616    /// Temporary directory for spilling
617    #[allow(dead_code)]
618    temp_dir: Option<std::path::PathBuf>,
619}
620
621impl SortMergeJoin {
622    /// Create a new sort-merge join
623    pub fn new(memory_threshold: usize) -> Self {
624        Self {
625            memory_threshold,
626            temp_dir: None,
627        }
628    }
629
630    /// Create sort-merge join with custom temp directory
631    pub fn with_temp_dir(memory_threshold: usize, temp_dir: std::path::PathBuf) -> Self {
632        Self {
633            memory_threshold,
634            temp_dir: Some(temp_dir),
635        }
636    }
637
638    /// Perform sort-merge join between two solution sets
639    pub fn join(
640        &self,
641        left_solutions: Vec<Solution>,
642        right_solutions: Vec<Solution>,
643        join_variables: &[Variable],
644    ) -> Result<Vec<Solution>> {
645        // Sort both inputs by join key
646        let sorted_left = self.sort_solutions(left_solutions, join_variables)?;
647        let sorted_right = self.sort_solutions(right_solutions, join_variables)?;
648
649        // Merge sorted inputs
650        self.merge_sorted_solutions(sorted_left, sorted_right, join_variables)
651    }
652
653    /// Sort solutions by join key variables
654    fn sort_solutions(
655        &self,
656        mut solutions: Vec<Solution>,
657        join_variables: &[Variable],
658    ) -> Result<Vec<Solution>> {
659        solutions.sort_by(|a, b| self.compare_solutions_by_join_key(a, b, join_variables));
660        Ok(solutions)
661    }
662
663    /// Compare two solutions by their join key variables
664    fn compare_solutions_by_join_key(
665        &self,
666        left: &Solution,
667        right: &Solution,
668        join_variables: &[Variable],
669    ) -> std::cmp::Ordering {
670        use std::cmp::Ordering;
671
672        // For each solution, get the first binding (solutions are vectors of bindings)
673        let left_binding = left.first();
674        let right_binding = right.first();
675
676        match (left_binding, right_binding) {
677            (Some(l_binding), Some(r_binding)) => {
678                for var in join_variables {
679                    let left_term = l_binding.get(var);
680                    let right_term = r_binding.get(var);
681
682                    let cmp = match (left_term, right_term) {
683                        (Some(l), Some(r)) => self.compare_terms(l, r),
684                        (Some(_), None) => Ordering::Greater,
685                        (None, Some(_)) => Ordering::Less,
686                        (None, None) => Ordering::Equal,
687                    };
688
689                    if cmp != Ordering::Equal {
690                        return cmp;
691                    }
692                }
693                Ordering::Equal
694            }
695            (Some(_), None) => Ordering::Greater,
696            (None, Some(_)) => Ordering::Less,
697            (None, None) => Ordering::Equal,
698        }
699    }
700
701    /// Compare two terms for sorting
702    fn compare_terms(&self, left: &Term, right: &Term) -> std::cmp::Ordering {
703        use std::cmp::Ordering;
704
705        match (left, right) {
706            (Term::Literal(l), Term::Literal(r)) => {
707                // Try numeric comparison first
708                if let (Ok(l_num), Ok(r_num)) = (l.value.parse::<f64>(), r.value.parse::<f64>()) {
709                    l_num.partial_cmp(&r_num).unwrap_or(Ordering::Equal)
710                } else {
711                    // Fall back to string comparison
712                    l.value.cmp(&r.value)
713                }
714            }
715            (Term::Iri(l), Term::Iri(r)) => l.as_str().cmp(r.as_str()),
716            (Term::BlankNode(l), Term::BlankNode(r)) => l.as_str().cmp(r.as_str()),
717            (Term::QuotedTriple(l), Term::QuotedTriple(r)) => {
718                // Compare quoted triples by string representation
719                format!("{l}").cmp(&format!("{r}"))
720            }
721            (Term::PropertyPath(l), Term::PropertyPath(r)) => {
722                // Compare property paths by string representation
723                format!("{l}").cmp(&format!("{r}"))
724            }
725            // Mixed types: order by type precedence
726            // Order: Literal < Iri < BlankNode < QuotedTriple < PropertyPath < Variable
727            (
728                Term::Literal(_),
729                Term::Iri(_)
730                | Term::BlankNode(_)
731                | Term::QuotedTriple(_)
732                | Term::PropertyPath(_)
733                | Term::Variable(_),
734            ) => Ordering::Less,
735            (Term::Iri(_), Term::Literal(_)) => Ordering::Greater,
736            (
737                Term::Iri(_),
738                Term::BlankNode(_)
739                | Term::QuotedTriple(_)
740                | Term::PropertyPath(_)
741                | Term::Variable(_),
742            ) => Ordering::Less,
743            (Term::BlankNode(_), Term::Literal(_) | Term::Iri(_)) => Ordering::Greater,
744            (
745                Term::BlankNode(_),
746                Term::QuotedTriple(_) | Term::PropertyPath(_) | Term::Variable(_),
747            ) => Ordering::Less,
748            (Term::QuotedTriple(_), Term::Literal(_) | Term::Iri(_) | Term::BlankNode(_)) => {
749                Ordering::Greater
750            }
751            (Term::QuotedTriple(_), Term::PropertyPath(_) | Term::Variable(_)) => Ordering::Less,
752            (
753                Term::PropertyPath(_),
754                Term::Literal(_) | Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_),
755            ) => Ordering::Greater,
756            (Term::PropertyPath(_), Term::Variable(_)) => Ordering::Less,
757            (Term::Variable(_), _) => Ordering::Greater, // Variables should not appear in sorted data
758        }
759    }
760
761    /// Merge two sorted solution sets
762    fn merge_sorted_solutions(
763        &self,
764        left_solutions: Vec<Solution>,
765        right_solutions: Vec<Solution>,
766        join_variables: &[Variable],
767    ) -> Result<Vec<Solution>> {
768        let mut result = Vec::new();
769        let mut left_idx = 0;
770        let mut right_idx = 0;
771
772        while left_idx < left_solutions.len() && right_idx < right_solutions.len() {
773            let left_solution = &left_solutions[left_idx];
774            let right_solution = &right_solutions[right_idx];
775
776            let cmp =
777                self.compare_solutions_by_join_key(left_solution, right_solution, join_variables);
778
779            match cmp {
780                std::cmp::Ordering::Equal => {
781                    // Found matching join keys - merge all combinations
782                    let mut left_end = left_idx + 1;
783                    while left_end < left_solutions.len()
784                        && self.compare_solutions_by_join_key(
785                            left_solution,
786                            &left_solutions[left_end],
787                            join_variables,
788                        ) == std::cmp::Ordering::Equal
789                    {
790                        left_end += 1;
791                    }
792
793                    let mut right_end = right_idx + 1;
794                    while right_end < right_solutions.len()
795                        && self.compare_solutions_by_join_key(
796                            right_solution,
797                            &right_solutions[right_end],
798                            join_variables,
799                        ) == std::cmp::Ordering::Equal
800                    {
801                        right_end += 1;
802                    }
803
804                    // Cross product of matching solutions
805                    for left_solution in left_solutions
806                        .iter()
807                        .skip(left_idx)
808                        .take(left_end - left_idx)
809                    {
810                        for right_solution in right_solutions
811                            .iter()
812                            .skip(right_idx)
813                            .take(right_end - right_idx)
814                        {
815                            if let Ok(Some(merged_solution)) = self.merge_solutions_if_compatible(
816                                left_solution,
817                                right_solution,
818                                join_variables,
819                            ) {
820                                result.push(merged_solution);
821                            }
822                        }
823                    }
824
825                    left_idx = left_end;
826                    right_idx = right_end;
827                }
828                std::cmp::Ordering::Less => {
829                    left_idx += 1;
830                }
831                std::cmp::Ordering::Greater => {
832                    right_idx += 1;
833                }
834            }
835        }
836
837        Ok(result)
838    }
839
840    /// Merge two solutions if they are compatible on join variables
841    fn merge_solutions_if_compatible(
842        &self,
843        left: &Solution,
844        right: &Solution,
845        join_variables: &[Variable],
846    ) -> Result<Option<Solution>> {
847        let mut result = Vec::new();
848
849        for left_binding in left {
850            for right_binding in right {
851                // Check compatibility on join variables
852                let mut compatible = true;
853                for var in join_variables {
854                    if let (Some(left_term), Some(right_term)) =
855                        (left_binding.get(var), right_binding.get(var))
856                    {
857                        if left_term != right_term {
858                            compatible = false;
859                            break;
860                        }
861                    }
862                }
863
864                if compatible {
865                    // Merge bindings
866                    let mut merged_binding = left_binding.clone();
867                    for (var, term) in right_binding {
868                        // Only add if not already present (join variables will be the same)
869                        if !merged_binding.contains_key(var) {
870                            merged_binding.insert(var.clone(), term.clone());
871                        }
872                    }
873                    result.push(merged_binding);
874                }
875            }
876        }
877
878        if result.is_empty() {
879            Ok(None)
880        } else {
881            Ok(Some(result))
882        }
883    }
884}
885
886/// Cache-friendly data structures for intermediate results
887pub struct CacheFriendlyStorage {
888    /// Columnar storage for solution bindings
889    columns: HashMap<Variable, Vec<Term>>,
890    /// Row count
891    row_count: usize,
892}
893
894impl Default for CacheFriendlyStorage {
895    fn default() -> Self {
896        Self::new()
897    }
898}
899
900impl CacheFriendlyStorage {
901    /// Create new cache-friendly storage
902    pub fn new() -> Self {
903        Self {
904            columns: HashMap::new(),
905            row_count: 0,
906        }
907    }
908
909    /// Add solutions in columnar format
910    pub fn add_solutions(&mut self, solutions: &[Solution]) {
911        for solution in solutions {
912            for binding in solution {
913                for (var, term) in binding {
914                    self.columns
915                        .entry(var.clone())
916                        .or_default()
917                        .push(term.clone());
918                }
919            }
920            self.row_count += solution.len();
921        }
922    }
923
924    /// Get column for variable
925    pub fn get_column(&self, var: &Variable) -> Option<&Vec<Term>> {
926        self.columns.get(var)
927    }
928
929    /// Convert back to row-based format
930    pub fn to_solutions(&self) -> Vec<Solution> {
931        let mut solutions = Vec::new();
932
933        if self.row_count == 0 {
934            return solutions;
935        }
936
937        // This is a simplified conversion - a full implementation would
938        // properly reconstruct the original solution structure
939        for i in 0..self.row_count {
940            let mut binding = HashMap::new();
941            for (var, column) in &self.columns {
942                if let Some(term) = column.get(i) {
943                    binding.insert(var.clone(), term.clone());
944                }
945            }
946            if !binding.is_empty() {
947                solutions.push(vec![binding]);
948            }
949        }
950
951        solutions
952    }
953}
954
955#[cfg(test)]
956mod tests {
957    use super::*;
958    use crate::algebra::Variable;
959    use oxirs_core::model::NamedNode;
960
961    #[test]
962    fn test_lock_free_queue() {
963        let queue = LockFreeWorkStealingQueue::new(16);
964
965        // Test push and pop
966        queue.push(42).unwrap();
967        queue.push(43).unwrap();
968
969        assert_eq!(queue.pop(), Some(43));
970        assert_eq!(queue.pop(), Some(42));
971        assert_eq!(queue.pop(), None);
972    }
973
974    #[test]
975    fn test_memory_pool() {
976        let pool = MemoryPool::new(2, 10, HashMap::<String, i32>::new);
977
978        let mut obj1 = pool.acquire();
979        obj1.get_mut().insert("test".to_string(), 42);
980
981        let obj2 = pool.acquire();
982        assert_ne!(obj1.get().len(), obj2.get().len());
983    }
984
985    #[test]
986    fn test_cache_friendly_hash_join() {
987        let join = CacheFriendlyHashJoin::new(4);
988
989        // Create test solutions
990        let var_x = Variable::new("x").unwrap();
991        let var_y = Variable::new("y").unwrap();
992
993        let mut left_binding = HashMap::new();
994        left_binding.insert(
995            var_x.clone(),
996            Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
997        );
998        left_binding.insert(
999            var_y.clone(),
1000            Term::Iri(NamedNode::new("http://example.org/a").unwrap()),
1001        );
1002        let left_solutions = vec![vec![left_binding]];
1003
1004        let mut right_binding = HashMap::new();
1005        right_binding.insert(
1006            var_x.clone(),
1007            Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
1008        );
1009        let right_solutions = vec![vec![right_binding]];
1010
1011        let results = join
1012            .join_parallel(left_solutions, right_solutions, &[var_x])
1013            .unwrap();
1014        assert!(!results.is_empty());
1015    }
1016
1017    #[test]
1018    fn test_simd_ops() {
1019        let strings = vec![
1020            "hello world".to_string(),
1021            "foo bar".to_string(),
1022            "hello rust".to_string(),
1023        ];
1024
1025        let results = SIMDOptimizedOps::bulk_string_compare(&strings, "hello");
1026        assert_eq!(results, vec![true, false, true]);
1027    }
1028
1029    #[test]
1030    fn test_cache_friendly_storage() {
1031        let mut storage = CacheFriendlyStorage::new();
1032
1033        let var_x = Variable::new("x").unwrap();
1034        let mut binding = HashMap::new();
1035        binding.insert(
1036            var_x.clone(),
1037            Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
1038        );
1039        let solutions = vec![vec![binding]];
1040
1041        storage.add_solutions(&solutions);
1042        assert!(storage.get_column(&var_x).is_some());
1043
1044        let recovered = storage.to_solutions();
1045        assert_eq!(recovered.len(), 1);
1046    }
1047}