无分支快速排序:性能超越 std::sort 和 pdqsort,提供 C 和 C++ API

Hacker News Top 工具

摘要

一种新的无分支快速排序实现(blqsort)借助排序网络技术,在 Apple M1 和 AMD Ryzen 系统上的性能超越了 std::sort 和 pdqsort,以单头文件形式提供 C 和 C++ 库。其性能提升得益于无分支分区、中位数之中位数枢轴选择以及针对小数组的自定义排序网络。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/05 02:10

# 无分支快速排序 来源:https://tiki.li/blog/blqsort ## 使用排序网络实现的高性能无分支快速排序(支持 C 和 C++ 接口) 性能测试结果自然取决于底层硬件。以下基准测试展示了使用不同排序实现对 5000 万个 `double` 值进行排序的执行时间。测试分别在 *Apple M1* 系统(使用 *Clang*)和 *AMD Ryzen 3 Linux* 系统(使用 *GCC*)上进行,均以 `-O3` 选项编译。 实现方式 | Apple M1 | AMD Ryzen --- | --- | --- std::sort | 1.33s | 5.56s pdqsort | 1.33s | 2.81s **blqsort**(单线程) | 0.97s | 2.06s 为了公平比较,此处使用了 `blqs` 的[单线程版本](https://github.com/chkas/blqsort/blob/main/cpp/test_double.cpp)。在 M1 上,多线程版本还能再快 3 到 4 倍。C++ 版本与 C 版本在运行时间上差异极小。 ### blqsort 完整源码托管在 [Github](https://github.com/chkas/blqsort) 上。**blqsort** 共有四个实现,每个实现均以单头文件形式提供。 文件 | 描述 --- | --- [blqs.h](https://github.com/chkas/blqsort/blob/main/cpp/blqs.h) | C++ 单线程版 [blqs\_thr.h](https://github.com/chkas/blqsort/blob/main/cpp/blqs_thr.h) | C++ 多线程版 [blqsort.h](https://github.com/chkas/blqsort/blob/main/c/blqsort.h) | C 单线程版 [blqsort\_thr.h](https://github.com/chkas/blqsort/blob/main/c/blqsort_thr.h) | C 多线程版 ### 无分支编程 在现代 CPU 上,**避免分支预测失败**是加速程序的关键技术。以下无分支写法: ``` for (int i = 0; i < 1000; i++) { small_numbers[smlen] = numbers[i]; smlen += (numbers[i] < 500); } ``` 比含条件分支的传统写法快得多: ``` for (int i = 0; i < 1000; i++) { if (numbers[i] < 500) { small_numbers[smlen] = numbers[i]; smlen += 1; } } ``` *Edelkamp* 和 *Weiß* 的[这篇论文](https://arxiv.org/abs/1604.06697)展示了如何通过避免条件分支来提升快速排序中分区操作的性能。使用辅助缓冲区进行**无分支分区**的策略受到了 [fluxsort](https://github.com/scandum/fluxsort) 的启发。这里的"辅助缓冲区"指的是一个 1024 个元素的栈数组,而非堆内存。 首先,从一侧将 1024 个元素复制到辅助缓冲区中以腾出空间。然后,以无分支方式交替向左右各复制一个 1024 元素的块。当元素小于枢轴时,左指针递增;否则右指针递增——当然,这一过程也是无分支的。 这涉及超过必要复制操作两倍以上的拷贝次数。但对于拷贝代价较低的数据类型而言,这比由此引发的分支预测失败要廉价得多。 ### 枢轴策略、异常输入与排序网络 为避免因异常输入数据导致 `O(n²)` 的运行时间,程序会将相同元素归组,并在检测到分区严重不均衡时,对该部分切换为*堆排序*。程序还会检查某个分区是否已经有序。 对于较大的分区,程序采用中位数的中位数策略来寻找良好的枢轴。此外,关键分区循环被显式展开。 对于 2 到 12 个元素,算法使用自定义排序网络。该方法需要为每种大小单独设计代码路径,但通过无分支的 *sort-2* 原语,以极少的交换次数完成小规模排序。[排序网络来源](https://bertdobbelaere.github.io/sorting_networks.html) ## C++ 对于拷贝代价较高且不满足 `is_trivially_copyable` 的类型(如字符串),基于缓冲区的无分支方法效率较低。在这种情况下,会改用 **BlockQuicksort**(*Edelkamp* 和 *Weiß*)变体。该变体仅以无分支方式处理元素索引,再以较少的交换次数移动实际数据。部分思路来源于 [pdqsort](https://github.com/orlp/pdqsort)。 ### 使用方式 只需引入 `blqs.h`,使用方式与 *std::sort* 完全相同。 ``` #include "blqs.h" double data[SIZE]; blqs::sort(data, data + SIZE); ``` 对于使用 C++ 线程的多线程变体,将 `blqs.h` 替换为 `blqs_thr.h` 即可,函数调用方式保持不变。 ## C 在 C 中,通过 `#define` 生成针对特定数据类型的专用代码。 ### 使用方式 ``` #define BLQS_CMP(a, b) ((a) < (b)) #define BLQS_TYPE double #include "blqsort.h" double data[SIZE]; blqsort(data, SIZE); ``` 对于使用 POSIX 线程的 C 多线程变体,将 `blqsort.h` 替换为 `blqsort_thr.h` 即可,函数调用方式同样保持不变。 ## 排序自定义数据结构 实际使用中,我们经常需要对自定义数据结构进行排序。这正是 *SIMD* 库(如 *Google Highway*)的局限所在——尽管它们处理简单数值时非常快速,但面对自定义结构时使用起来相当繁琐。 使用 *std::sort* 或 *blqsort* 则能获得更大的灵活性。 ### C++ ``` #include "blqs.h" struct entry { int id; int value; bool operator<(const entry& other) const { return id < other.id; } }; struct entry data[SIZE]; blqs::sort(data, data + SIZE); ``` ### C ``` struct entry { int id; int value; }; #define BLQS_CMP(a, b) (((a).id) < ((b).id)) #define BLQS_TYPE struct entry #include "blqsort.h" struct entry data[SIZE]; blqsort(data, SIZE); ``` 对 5000 万个上述 `struct` 进行排序的执行时间: 实现方式 | Apple M1 | AMD Ryzen --- | --- | --- std::sort | 3.46s | 4.75s pdqsort | 3.46s | 4.72s **blqsort** | 0.96s | 2.20s ### 相关链接 [当 'if' 拖慢你的速度时,避开它。](https://tiki.li/blog/branchless) [交互式排序演示](https://tiki.li/sorting) --- [email protected]

相似文章

Metal-Sci:用于 Apple Silicon 上 LLM 驱动演化内核搜索的科学计算基准

Hugging Face Daily Papers

Metal-Sci 推出了一项包含 10 个任务的基准测试,用于优化 Apple Silicon 上的科学计算内核,并配套了由大语言模型驱动的演化搜索框架。该研究评估了 Claude Opus 4.7、Gemini 3.1 Pro 和 GPT 5.5 等模型,在实现显著加速的同时,利用分布外测试来捕获静默的性能退化问题。

我在 MacBook Air M5 上对 21 款本地大模型进行了代码质量与速度的性能评测

Reddit r/LocalLLaMA

一位开发者在 MacBook Air M5 上使用 HumanEval+ 对 21 款本地大模型进行了基准测试,发现 Qwen 3.6 35B-A3B (MoE) 以 89.6% 的得分和 16.9 tok/s 的速度位居榜首,而 Qwen 2.5 Coder 7B 仅需 4.5 GB 内存即可达到 84.2% 的性能,拥有最佳的内存性价比。值得注意的是,Gemma 4 系列的表现远低于预期(31B 版本仅得 31.1%),这可能是受 Q4_K_M 量化策略的影响。

@no_stp_on_snek: 如果你想试试,可以在这里找到:

X AI KOLs Following

这是一个 llama.cpp 的分支,集成了 TurboQuant+,用于先进的 KV 缓存和权重量化,支持跨后端内核(Apple Silicon、NVIDIA CUDA、AMD ROCm、Vulkan),并被 LocalAI、Chronara 和 AtomicChat 用于生产环境。

双GPU llama.cpp加速

Reddit r/LocalLLaMA

llama.cpp的一个分支修复了量化KV缓存中的--split-mode tensor问题,在双GPU配置上实现高达40%的速度提升,且无质量损失。