gpu sorting algorithms
**GPU Sorting Algorithms** are **the parallel implementations of sorting that leverage thousands of GPU threads to achieve 100-300 GB/s throughput** — where radix sort (optimal for integers and fixed-point) achieves 200-300 GB/s by processing multiple bits per pass and exploiting warp-level primitives, merge sort (optimal for general comparisons) achieves 100-200 GB/s through hierarchical merging, and bitonic sort (optimal for power-of-2 sizes) achieves 150-250 GB/s with fixed communication patterns, making GPU sorting 10-50× faster than CPU sorting (5-20 GB/s) and essential for applications like database operations, graph algorithms, and data preprocessing where sorting is bottleneck (20-60% of runtime) and proper algorithm selection based on data characteristics (integer vs float, key-only vs key-value, size) determines whether applications achieve 40% or 90% of theoretical peak bandwidth.
**Radix Sort:**
- **Algorithm**: sorts by processing k bits per pass; typically k=4-8 bits; requires ceil(32/k) passes for 32-bit integers; stable sort
- **Performance**: 200-300 GB/s on A100; 60-80% of peak memory bandwidth; optimal for integers, fixed-point; 10-50× faster than CPU
- **Implementation**: histogram per block → prefix sum → scatter; uses shared memory for local histogram; warp primitives for reduction
- **Use Cases**: integer keys, fixed-point values; uniform distribution; large arrays (>1M elements); 80-95% of peak bandwidth
**Merge Sort:**
- **Algorithm**: hierarchical merging; bottom-up or top-down; log2(N) passes; stable sort; comparison-based
- **Performance**: 100-200 GB/s on A100; 40-60% of peak bandwidth; optimal for general comparisons, small arrays
- **Implementation**: warp-level merge → block-level merge → global merge; uses shared memory for local merging
- **Use Cases**: general comparisons, custom comparators; small-medium arrays (10K-1M elements); stable sort required
**Bitonic Sort:**
- **Algorithm**: comparison network; fixed communication pattern; log2(N) × (log2(N)+1) / 2 comparisons; not stable
- **Performance**: 150-250 GB/s on A100; 50-70% of peak bandwidth; optimal for power-of-2 sizes; predictable performance
- **Implementation**: warp-level bitonic → block-level bitonic → global bitonic; uses shuffle for warp-level, shared memory for block-level
- **Use Cases**: power-of-2 sizes, predictable latency; small-medium arrays; GPU-friendly communication pattern
**Thrust Sort:**
- **API**: thrust::sort(d_vec.begin(), d_vec.end()); automatic algorithm selection; radix sort for integers, merge sort for general
- **Performance**: 100-300 GB/s; 60-80% of hand-tuned; 1 line of code vs 100-200 for custom implementation
- **Customization**: thrust::sort(d_vec.begin(), d_vec.end(), thrust::greater()); custom comparators supported
- **Use Cases**: rapid development, general-purpose sorting; acceptable 10-30% performance gap vs hand-tuned
**CUB Sort:**
- **API**: cub::DeviceRadixSort::SortKeys(d_temp_storage, temp_storage_bytes, d_keys, d_sorted, N); explicit control
- **Performance**: 200-300 GB/s; 70-90% of peak bandwidth; 10-30% faster than Thrust; lower-level API
- **Features**: key-only or key-value pairs; ascending or descending; segmented sort; double-buffer for in-place
- **Use Cases**: performance-critical sorting; fine-grained control; production systems
**Key-Value Sorting:**
- **Radix Sort**: cub::DeviceRadixSort::SortPairs(); sorts keys, reorders values; 150-250 GB/s; 2× slower than key-only
- **Merge Sort**: stable sort preserves value order; 80-150 GB/s; 40-60% of peak bandwidth
- **Performance**: 2-3× slower than key-only; memory bandwidth limited; 2× data movement
- **Use Cases**: sorting with associated data; database operations; graph algorithms
**Segmented Sort:**
- **Concept**: sort multiple independent segments; each segment sorted separately; useful for batched operations
- **Implementation**: cub::DeviceSegmentedRadixSort::SortKeys(); segment offsets specify boundaries
- **Performance**: 150-250 GB/s; 50-70% of peak; depends on segment sizes; small segments have overhead
- **Use Cases**: batched sorting, per-group sorting; graph algorithms; database operations
**Optimization Techniques:**
- **Warp-Level Primitives**: use __shfl for warp-level sorting; 2-5× faster than shared memory; 500-1000 GB/s for small arrays
- **Shared Memory**: use for block-level sorting; 100× faster than global memory; 164KB per SM on A100
- **Coalesced Access**: ensure coalesced memory access; 128-byte aligned; achieves 100% bandwidth; stride-1 access optimal
- **Occupancy**: balance shared memory usage and occupancy; 50-100% occupancy typical; 256 threads per block optimal
**Radix Sort Optimization:**
- **Bits Per Pass**: 4-8 bits typical; more bits = fewer passes but larger histogram; 4 bits optimal for most cases
- **Histogram**: per-block histogram in shared memory; warp primitives for reduction; 300-600 GB/s
- **Prefix Sum**: exclusive scan of histogram; 400-800 GB/s; CUB provides optimized implementation
- **Scatter**: write sorted elements to output; coalesced writes; 200-300 GB/s; double-buffering eliminates copies
**Merge Sort Optimization:**
- **Warp-Level Merge**: use __shfl for merging within warp; 2-5× faster than shared memory; 500-1000 GB/s
- **Block-Level Merge**: shared memory for merging within block; 100-200 GB/s; minimize global memory accesses
- **Global Merge**: hierarchical merging; multiple passes; 80-150 GB/s; memory-bound
- **Path Decomposition**: parallel merge path algorithm; better load balancing; 20-40% speedup
**Bitonic Sort Optimization:**
- **Warp-Level**: use __shfl_xor for butterfly exchanges; 2-5× faster than shared memory; 500-1000 GB/s
- **Block-Level**: shared memory for larger bitonic networks; 150-250 GB/s; minimize bank conflicts
- **Padding**: add padding to avoid bank conflicts; 1-2 elements typical; 10-30% improvement
- **Unrolling**: unroll inner loops; reduces overhead; 10-20% speedup; compiler often does automatically
**Performance Comparison:**
- **Radix Sort**: 200-300 GB/s; best for integers; 60-80% of peak; 10-50× faster than CPU
- **Merge Sort**: 100-200 GB/s; best for general comparisons; 40-60% of peak; 5-20× faster than CPU
- **Bitonic Sort**: 150-250 GB/s; best for power-of-2 sizes; 50-70% of peak; 10-30× faster than CPU
- **Thrust Sort**: 100-300 GB/s; automatic selection; 60-80% of hand-tuned; easiest to use
**Size Considerations:**
- **Small Arrays (<10K)**: bitonic sort or warp-level sort; 150-250 GB/s; low overhead; predictable latency
- **Medium Arrays (10K-1M)**: merge sort or radix sort; 150-250 GB/s; good balance; algorithm depends on data type
- **Large Arrays (>1M)**: radix sort for integers, merge sort for general; 200-300 GB/s; amortizes overhead; optimal performance
- **Very Large (>100M)**: out-of-core sorting; multiple passes; 100-200 GB/s; memory-limited
**Stability:**
- **Stable**: radix sort, merge sort; preserves relative order of equal elements; required for some applications
- **Unstable**: bitonic sort, quicksort; may reorder equal elements; faster but less predictable
- **Use Cases**: stable required for multi-key sorting, database operations; unstable acceptable for unique keys
**Custom Comparators:**
- **Merge Sort**: supports custom comparators; thrust::sort(d_vec.begin(), d_vec.end(), my_comparator()); flexible
- **Radix Sort**: limited to integer keys; can use custom bit extraction; less flexible but faster
- **Performance**: custom comparators 10-30% slower; function call overhead; inline when possible
**Profiling and Tuning:**
- **Nsight Compute**: shows memory bandwidth, occupancy, warp efficiency; identifies bottlenecks
- **Metrics**: achieved bandwidth / peak bandwidth; target 60-80% for sorting; memory-bound operation
- **Bottlenecks**: uncoalesced access, bank conflicts, low occupancy; optimize access patterns
- **Tuning**: adjust block size, bits per pass, shared memory usage; profile to find optimal
**Best Practices:**
- **Use Libraries**: Thrust or CUB for most cases; 60-90% of hand-tuned; 10-100× less code
- **Algorithm Selection**: radix sort for integers, merge sort for general; bitonic for power-of-2; profile to verify
- **Batch Operations**: sort multiple arrays together; amortizes overhead; 20-40% improvement
- **Profile**: measure actual bandwidth; compare with peak; optimize only if bottleneck
- **Pre-Allocate**: allocate temporary storage once; reuse across sorts; eliminates allocation overhead
**Performance Targets:**
- **Radix Sort**: 200-300 GB/s; 60-80% of peak (1.5-3 TB/s); optimal for integers
- **Merge Sort**: 100-200 GB/s; 40-60% of peak; optimal for general comparisons
- **Bitonic Sort**: 150-250 GB/s; 50-70% of peak; optimal for power-of-2 sizes
- **Key-Value**: 150-250 GB/s; 50-70% of peak; 2× slower than key-only
**Real-World Applications:**
- **Database Operations**: sorting query results; 200-300 GB/s with radix sort; 10-50× faster than CPU
- **Graph Algorithms**: sorting edges by source/destination; 150-250 GB/s; 20-40% of graph processing time
- **Data Preprocessing**: sorting features for ML; 200-300 GB/s; 10-30% of preprocessing time
- **Rendering**: sorting primitives by depth; 150-250 GB/s; 5-20% of rendering time
GPU Sorting Algorithms represent **the essential building block for data-intensive applications** — by leveraging thousands of parallel threads and optimized memory access patterns, GPU sorting achieves 100-300 GB/s throughput (10-50× faster than CPU) through algorithms like radix sort for integers (200-300 GB/s), merge sort for general comparisons (100-200 GB/s), and bitonic sort for power-of-2 sizes (150-250 GB/s), making GPU sorting critical for applications where sorting is bottleneck and proper algorithm selection based on data characteristics determines whether applications achieve 40% or 90% of theoretical peak bandwidth.