惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Heimdal Security Blog
A
Arctic Wolf
K
Kaspersky official blog
V
Vulnerabilities – Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
L
LINUX DO - 热门话题
MongoDB | Blog
MongoDB | Blog
T
Threat Research - Cisco Blogs
D
Docker
爱范儿
爱范儿
T
Tenable Blog
C
Check Point Blog
B
Blog
C
Cisco Blogs
Vercel News
Vercel News
The Cloudflare Blog
T
Threatpost
NISL@THU
NISL@THU
T
Tor Project blog
V2EX - 技术
V2EX - 技术
P
Palo Alto Networks Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
SecWiki News
SecWiki News
博客园 - 司徒正美
S
Security @ Cisco Blogs
GbyAI
GbyAI
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
The Register - Security
The Register - Security
Recorded Future
Recorded Future
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
N
News and Events Feed by Topic
Y
Y Combinator Blog
博客园_首页
T
Troy Hunt's Blog
The Hacker News
The Hacker News
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
U
Unit 42
AWS News Blog
AWS News Blog
PCI Perspectives
PCI Perspectives
V
Visual Studio Blog
博客园 - 聂微东
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 【Linux 网络子系统深度拆解】内核网络调优方法论:从基准测试到生产验证
排序基准测试:用数据说话
2025-07-15 · via 土法炼钢兴趣小组的算法知识备份

“不要猜,去测。” ——每一个严肃的性能工程师

排序是计算机科学里最经典的问题之一,但“哪种排序最快”从来没有一个脱离上下文的答案。数据规模、数据分布、元素类型、缓存行为、标准库实现、编译器版本,都会影响最后结果。

本文只做一件事:把仓库里的排序 benchmark 变成可以直接执行、可以重新出 CSV、可以重新画图的版本,然后在当前环境下重跑一次,用真实结果回答“什么时候该用什么排序”。

先说结论:这篇文章的旧版本并不是当前仓库里可直接复现的实测稿。 当时仓库只有文章中的代码片段和手工绘制的 SVG,没有独立的 benchmark 源码、没有原始 CSV,也没有一条能直接执行的构建命令。所以旧稿里的具体数字,不能算当前仓库内可验证的真实数据。

这次我把可执行代码补到了 examples/sort-bench/,并在当前环境重跑了一遍:x86-64 Linux,12th Gen Intel Core i9-12900K,GCC 15.2.1,单线程 taskset -c 4 绑核。文中的表格和图都来自这次重新生成的 CSV。

排序基准测试结果总览

代码与原始结果:bench.cpp | Makefile | README.md | results-current-env.csv | plot.py

一、基准测试方法论

1.1 micro-benchmark 的几个基本陷阱

陷阱一:编译器把你的工作优化掉了

如果排序结果没有被观测到,编译器可能直接把整个调用删掉。常见做法是加汇编栅栏:

inline void do_not_optimize(const void* ptr) {
    asm volatile("" : : "g"(ptr) : "memory");
}

inline void clobber_memory() {
    asm volatile("" : : : "memory");
}

陷阱二:小数组太快,计时噪声大于算法本身

几十个元素的排序可能只有几十到几百纳秒,单次测量几乎没有意义。正确做法是 batch 化,把一批相同规模的排序放到同一个计时窗口里,再除以 batch。

陷阱三:只测一次,拿偶然值当结论

这次套件对每个点都做多次采样,并记录中位数、p5、p95。文章里主要引用中位数,因为它比单次最小值更抗调度抖动。

1.2 这次重跑的测试口径

这次套件覆盖的是:

  • 数据类型:int32_t
  • 分布:uniformnormalsortedreversesawtoothpipe_organhigh_dup
  • 规模:641024655361000000
  • 算法:insertionheapmergequickintrosortpdqsortTimSortradix_lsdradix_msdska_sortstd::sortstd::stable_sort

套件使用固定随机种子 42,小数组走 batch 测量,大数组直接单次计时并重复采样。每轮排序结束后都会检查:

  1. 输出是否有序。
  2. 输出是否和输入保持同一个 multiset。

二、这次补齐了什么

为了把文章从“描述性 benchmark”变成“可复现 benchmark”,仓库里新增了这些文件:

也就是说,现在不是“文章里贴了点代码”,而是仓库里真的有一套可以执行的实验。

三、怎么构建与重跑

真正可执行的命令如下:

cd examples/sort-bench
make

正式跑数:

taskset -c 4 ./sort_bench --csv results-current-env.csv

用当前 CSV 重画文章里的图:

python3 plot.py \
    --csv results-current-env.csv \
    --output ../../post/algorithms/06-sort-bench/benchmark-comparison.svg

如果想换规模:

./sort_bench --sizes 64,4096,262144 --csv custom.csv

四、当前环境与边界

这次重跑环境固定如下:

项目 本次设置
CPU 12th Gen Intel Core i9-12900K
架构 x86-64 Linux
编译器 GCC 15.2.1
Python 3.14.3
绑核方式 taskset -c 4 单线程
数据类型 int32_t
原始结果 results-current-env.csv

这里有两个边界必须说清楚:

  1. 这次图和表是真实数据,但它们比较的是这套仓库内实现。std::sort / std::stable_sort 是标准库实现;pdqsortTimSortradixska_sort 是本文配套的参考实现。所以这些结果应理解为“这套实现的表现”,而不是整个算法家族在所有实现下的绝对排名。
  2. 当前环境没有 perf,所以旧稿里那些 cache miss、branch miss、IPC 表格全部不保留。没有重测的数据,就不应该继续出现在正文里。

五、当前环境的真实结果

5.1 uniform 随机输入下,不同规模的前三名

n 第一名 第二名 第三名
64 std::sort 159.9 ns quick 212.9 ns introsort 215.0 ns
1024 std::sort 4.458 us std::stable_sort 5.584 us radix_lsd 5.831 us
65536 radix_lsd 0.327 ms radix_msd 0.994 ms ska_sort 2.029 ms
1000000 radix_lsd 6.019 ms radix_msd 15.428 ms ska_sort 31.125 ms

这张表足够说明两件事:

  • 小规模时,libstdc++ 的 std::sort 是最强 baseline。
  • 当数据真的是大规模 int32_t 且分布接近随机时,基数排序会把比较排序甩开一个量级。

5.2 n=1,000,000 时,各分布前三名

分布 第一名 第二名 第三名
uniform radix_lsd 6.019 ms radix_msd 15.428 ms ska_sort 31.125 ms
normal radix_lsd 5.839 ms radix_msd 9.878 ms ska_sort 18.810 ms
sorted TimSort 0.210 ms pdqsort 0.329 ms std::sort 4.609 ms
reverse TimSort 0.518 ms std::sort 3.416 ms pdqsort 5.139 ms
sawtooth TimSort 4.120 ms std::stable_sort 5.304 ms radix_msd 9.142 ms
pipe_organ TimSort 1.107 ms std::stable_sort 6.654 ms radix_lsd 9.676 ms
high_dup radix_lsd 4.371 ms radix_msd 6.945 ms ska_sort 11.568 ms

这里最值得注意的是:这次真实重跑里,TimSort 对结构化输入非常强,而当前仓库这版 pdqsort 并没有出现“全分布通吃”的表现。

5.3 几个最值得看的对照

下面只挑最常见的几类输入,看几个代表性算法:

分布,n=1,000,000 std::sort pdqsort TimSort radix_lsd 观察
uniform 47.153 ms 76.158 ms 73.883 ms 6.019 ms 随机 int32_t 上基数排序碾压比较排序
sorted 4.609 ms 0.329 ms 0.210 ms 11.772 ms 有结构输入时自适应排序巨大受益
reverse 3.416 ms 5.139 ms 0.518 ms 12.138 ms TimSort 能有效利用 run 和反转结构
high_dup 26.954 ms 43.675 ms 52.429 ms 4.371 ms 高重复整数上基数排序仍然最稳

另外,heap_sort 在 uniform 1e6 上是 88.122 ms,在 sorted 上是 37.808 ms,在 reverse 上是 42.824 ms,几乎在所有大规模输入里都靠后。这个方向判断和旧稿一致:堆排序更像保底策略,而不是默认首选。

六、这次重跑后,哪些结论还能站住

6.1 还站得住的结论

  1. 没有一个算法在所有分布上都最快。 这次环境里,random/high-dup 是 radix 家族赢,sorted/reverse/pipe_organ 是 TimSort 赢。
  2. 对大规模 int32_tradix_lsd 的优势非常实。 在 uniform 1e6 上,它比 std::sort 快约 \(47.153 / 6.019 \approx 7.8\) 倍。
  3. TimSort 对有结构的数据极其敏感。 在 sorted 1e6 上,TimSort 0.210 ms,std::sort 4.609 ms,差了约 22 倍。
  4. std::sort 仍然是最可靠的比较排序 baseline。 在 uniform 的 64 和 1024 上,它都是第一;在 uniform 1e6 上,它也仍然是比较排序里最快的一档。
  5. heapsort 依旧不适合作为默认选择。 它存在的主要工程价值还是给 introsort 兜底最坏情况。

6.2 站不住,或者不能这么写的结论

  1. “pdqsort 是通用场景最佳选择” 这句话不能继续保留。 这次真实重跑里,当前仓库这版 pdqsort 在 uniform / normal / high_dup 上都不占优,只在 sorted 上表现很好。
  2. 旧稿里的 perf / cache / branch 数字必须删。 当前环境没有 perf,所以这些数字不是这次 bench 产物。
  3. 旧稿里关于大对象、并行排序、1e8 规模的具体数字也不该保留。 这次并没有按那个口径重跑,就不要伪装成实测结果。

七、怎么在你的机器上复现

最短路径就是这三条命令:

cd examples/sort-bench
make
taskset -c 4 ./sort_bench --csv results-current-env.csv

重画文章里的 SVG:

python3 plot.py \
    --csv results-current-env.csv \
    --output ../../post/algorithms/06-sort-bench/benchmark-comparison.svg

如果你要修改规模:

./sort_bench --sizes 64,4096,262144 --csv custom.csv

八、当前环境下的选型建议

如果你的问题和这次 bench 的口径接近,也就是单线程、int32_t、内存内排序,那我会给出更保守、也更诚实的一版建议:

  1. 随机或接近随机的大规模整数:优先看 radix_lsd
  2. 明显有序、部分有序、run 很长的输入:优先看 TimSort
  3. 只是想要一个稳妥的通用 baseline:先用 std::sort,不要先入为主切到手写 pdqsort
  4. 必须稳定排序,但输入又不是强结构化:先用 std::stable_sort;如果数据本身就带 run,再测 TimSort
  5. heapsort 不要直接选。 让 introsort 在需要时退回堆排序就够了。

最后还是那句老话:永远实测。 但这次至少不再是“文章里说能跑”,而是仓库里真的有代码、真的能跑、真的能产出 CSV 和 SVG 了。

参考文献

  1. Musser, D.R. “Introspective Sorting and Selection Algorithms.” Software: Practice and Experience, 27(8), 1997.
  2. Peters, O. “Pattern-defeating Quicksort.” arXiv:2106.05123, 2021.
  3. McIlroy, P.M. “Optimistic Sorting and Information Theoretic Complexity.” SODA, 1993.
  4. Skarupke, M. “ska_sort: A Radix Sort Implementation.” 2016.
  5. Peters, T. “TimSort.” CPython source, 2002.
  6. Drepper, U. “What Every Programmer Should Know About Memory.” 2007.
  7. Fog, A. “Optimizing Software in C++.” 2023.
  8. Edelkamp, S., Weiss, A. “BlockQuicksort.” ESA 2016.

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-10 · algorithms

排序算法专题:从 TimSort 到并行排序

把 TimSort、pdqsort、radix sort、external sort、parallel sort 与 benchmark 串成一条阅读路径。先读哪篇、什么时候选哪种排序,这一页讲清。

2025-07-15 · algorithms

基数排序:打破比较下界的正确姿势

比较排序有 O(n log n) 的理论下界,基数排序如何绕过这个限制?它在什么场景下真正有优势,又为什么没有成为通用排序的首选?