bigywater.tech
Go · 99

sort 与 slices.Sort:pdqsort 与稳定排序的差别

Go 的 sort 和 slices 包用的是 pdqsort,它会识别已排序、逆序、全相等这些模式。这篇用七种输入形状、四种排序函数各排 100 万个整数,看速度差别,再验证“不稳定”到底有多不稳定,并读源码里的 12 个元素阈值与堆排序兜底。

0.6 ms vs 60.8 ms
slices.Sort 排 100 万个已排序的 int 与随机 int
5.5 倍
sort.SliceStable 比 slices.Sort 排随机数据慢的倍数(334.0 对 60.8 ms)
49962 对
10 万条记录、100 种键,不稳定排序后相等键之间顺序被颠倒的相邻对数

识别模式,以及选哪个函数

实测 · 本机执行输出
  输入形状(100 万个 int)        slices.Sort    sort.Ints   sort.Slice    SliceStable
  随机                          60.8 ms      61.1 ms     102.5 ms       334.0 ms
  已排序                          0.6 ms       0.6 ms       2.1 ms         2.9 ms
  逆序                           0.9 ms       0.9 ms       3.2 ms        58.9 ms
  全部相等                         0.6 ms       0.6 ms       2.0 ms         2.9 ms
  只有 10 种取值                    9.8 ms       9.8 ms      18.9 ms       129.0 ms
  管风琴形(先升后降)                  17.9 ms      17.8 ms      48.7 ms        47.8 ms
  已排序但末尾 1% 乱序                37.9 ms      37.8 ms      89.7 ms        17.5 ms

已排序、逆序、全部相等的输入,slices.Sort 只要 0.6–0.9 ms,接近线性扫描一遍;只有 10 种取值也只要 9.8 ms,约为随机的六分之一。这就是 pdqsort 的“模式击败”。同样排随机数据:slices.Sort 60.8 ms 与 sort.Ints 61.1 ms 一样快,sort.Slice 要 102.5 ms(比较函数通过闭包调用),sort.SliceStable 要 334.0 ms。能用 slices.Sort 就用它。

稳定性

实测 · 本机执行输出
  10 万条记录,100 种键:sort.Slice(不稳定)之后,键相等的相邻记录里原始顺序颠倒的有 49962 对
  sort.SliceStable 之后:0 对
  slices.SortFunc(不稳定)之后:49962 对
  slices.SortStableFunc 之后:0 对

不稳定排序之后,相等键的相邻记录有 49962 对(接近一半)顺序被颠倒;稳定排序是 0 对。需要“同键按原顺序”就必须明确用稳定版本。有意思的是几乎有序的输入(末尾 1% 乱序)稳定排序只要 17.5 ms,比不稳定的 37.9 ms 还快。

源码

实测 · 本机执行输出
  // limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
  func pdqsort_func(data lessSwap, a, b, limit int) {
  const maxInsertion = 12
  if length <= maxInsertion {
  heapSort_func(data, a, b)
  slices/zsortordered.go:The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
  Slice sorts the slice x given the provided less function. It panics if x is not a slice. The sort is not guaranteed to be stable: equal elements may be reversed from their original order. For a stable sort, use SliceStable.

pdqsort 的短片段阈值是 12 个元素(用插入排序),“坏枢轴”用完 limit 就回退到堆排序,保证最坏情况不会退化成平方级。slices 的注释说明它没有 BlockQuicksort 的优化。我没有构造专门击败枢轴选择的输入来验证堆排序回退。

分步演示

排序
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。每种形状 100 万个 int,随机数用固定种子的 math/rand/v2 PCG;每格 5 次取最小;稳定性实验 10 万条 {key, seq} 记录;读过的源码与文档:sort/zsortfunc.go 的 pdqsort_func,slices/zsortordered.go 的注释,go doc sort.Slice
    • 没有验证:字符串或结构体排序、对抗性输入下的堆排序回退、更小(几十个元素)的数据、sort.Sort(Interface 版)的成本、Linux 与其它架构的差别、并行排序
    京ICP备2026067502号-1京公网安备11010502063487号