In benchmarks conducted on an Apple M1 system, the blqsort algorithm sorted 50 million random doubles in 0.68 seconds, outperforming the standard C++ library's std::sort, which took 1.33 seconds. This performance gain is driven by a series of optimizations designed to minimize CPU pipeline stalls and handle specific data distributions more efficiently than general-purpose sorting implementations.

A data table showing benchmark results for blqsort and std::sort on different hardware.
Benchmark results comparing blqsort performance against std::sort across various platforms and data types.tiki.li

This table provides the empirical evidence for the performance gains mentioned in the introduction.

A performance comparison table showing execution times for different sorting algorithms.
Comparative analysis of sorting algorithm execution times for various input sizes.tiki.li

This visual supports the discussion on how blqsort handles different data distributions and sizes.

Eliminating Branch Mispredictions

Standard quicksort implementations often rely on 'if' conditions to partition data. On modern CPUs, if the data is randomly distributed, these conditions become unpredictable for the branch predictor, forcing the processor to flush its pipeline and restart execution.

blqsort addresses this by using a branchless approach. Instead of a conditional jump, it uses a conditional increment instruction to move elements. While this introduces an unconditional memory write for every element, the author notes that the overhead of the write is significantly lower than the performance loss caused by frequent branch mispredictions.

A code listing comparing a basic quicksort implementation with the blqsort approach.
Code comparison highlighting the structural differences between standard quicksort and the blqsort implementation.tiki.li

This code snippet illustrates the transition from standard conditional logic to the optimized blqsort structure.

“The key concepts of blqsort are branchless partitioning, sorting networks for small partitions, and good pivot selection.”

— A Fast Quicksort

Sorting Networks for Small Partitions

For very small partitions, such as those with fewer than 20 elements, traditional quicksort is less efficient than simpler methods. blqsort replaces these with sorting networks, which use a fixed sequence of comparisons and swaps to order elements.

The implementation includes specific logic for partition sizes up to 16. By using these networks, the algorithm avoids the overhead of recursive calls and provides a highly optimized path for the 'base cases' of the sorting process.

Pivot Selection and Edge Case Handling

To maintain high performance across different data types, blqsort employs a multi-step pivot selection process. It determines several medians from the partition to calculate a final median, ensuring a more even distribution of data during the partitioning step.

The algorithm also includes safeguards for difficult inputs. If the data distribution remains uneven—often due to many identical elements—the system switches to heap sort. Additionally, it identifies and groups duplicate keys to maintain efficiency.

Performance Trade-offs and Limitations

The primary trade-off for blqsort's speed is the use of branchless logic, which requires the compiler to perform writes to memory even when the condition is not met. The compiler cannot automatically optimize this into a conditional write because it cannot guarantee that such a write is safe for the allocated buffer.

While blqsort outperforms std::sort on random data, its relative advantage varies by architecture. For example, on x86 systems, the performance gap between blqsort and std::sort was observed to be approximately 10%.

A table summarizing the performance characteristics of blqsort on different architectures.
Summary of blqsort performance metrics across different CPU architectures.tiki.li

This table illustrates the performance trade-offs and architectural variations discussed in the final section.

Execution times for sorting 50 million random doubles (Apple M1): std::sort 1.33s; ipnsort ClickHouse C++ port 1.00s; blqsort 0.68s
Execution times for sorting 50 million random doubles (Apple M1)tiki.li

Chart drawn by HN Digest from figures reported by tiki.li; values are shown as written in the source.

What Hacker News readers are saying

  • Alternative: One commenter suggested that radix sort can be exceptionally fast for specific orderings because it performs simple scans over the data with excellent locality of reference. comment ↗
  • Skeptical: A commenter expressed skepticism regarding the performance gains, arguing that for many problems, input/output operations dominate the execution time more than the sorting algorithm itself. comment ↗
  • Technical note: Another user pointed out that the efficiency of quicksort is largely due to its O(log n) space requirements, distinguishing it from algorithms like merge sort. comment ↗

Summarized opinions from the Hacker News thread, not verified facts. Read the full discussion ↗