Go 1.19 引入新型排序算法 pdqsort

冰岩作坊 April 2, 2023

经典排序算法

Insertion Sort 插入排序

Time complexity

| Best | Avg | Worst |
| O(n) | O(n^2) | O(n^2) |

优点

最好情况时间复杂度为 O(n)

缺点

平均和最坏情况时间复杂度高达O(n^2)

Quick Sort 快速排序

•选定一个 pivot•使用 pivot 分割序列,分成元素比 pivot 大和元素比 pivot 小两个序列,不断重复Time complexity

| Best | Avg | Worst |
| O(nlogn) | O(nlogn) | O(n^2) |

最好情况: pivot 为中位数

最坏情况: pivot 为最大/小值

优点

平均情况时间复杂度为 O(nlogn)

缺点

最坏情况时间复杂度 O(n^2)

Heap Sort 堆排序

• 构造一个大顶堆• 将根节点(最大元素)交换到最后一个位置,使堆大小 -1,调整整个堆,如此反复Time complexity

| Best | Avg | Worst |
| O(nlogn) | O(nlogn) | O(nlogn) |

优点

最坏情况时间复杂度为 O(nlogn)

缺点

最好情况时间复杂度为 O(nlogn)

实际场景 Benchmark

| | Best | Avg | Worst |
| Insertion Sort | O(n) | O(n^2) | O(n^2) |
| Quick Sort | O(nlogn) | O(nlogn) | O(n^2) |
| Heap Sort | O(nlogn) | O(nlogn) | O(nlogn) |

• 插入排序平均和最坏情况都是 O(n^2),性能不好• 快速排序整体性能处于中间• 堆排序性能比较稳定,各种情况均稳定为 O(nlogn)序列元素排列情况

• 完全随机 (random)• 有序/逆序 (sorted/reverse)• 元素重复度较高 (mod8)在此基础上,还需要根据序列长度划分(16/128/1024)

random

• 插入排序在短序列中速度最快• 快速排序在其他情况中速度最快• 堆排序速度与最快算法差距不大sorted

• 插入排序在序列已经有序的情况下最快

结论

• 所有短序列和元素有序情况下,插入排序性能最好• 在大部分的情况下,快速排序有较好的综合性能• 几乎在任何情况下,堆排序的表现都比较稳定pdqsort 算法

pdqsort (pattern-defeating-quicksort) 是一种不稳定的混合排序算法,它的不同版本被应用在C++ BOOST、Rust 以及Go 1.19中。它对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能。

Version 1

• 对于短序列 (<=24) 我们使用插入排序• 其他情况,使用快速排序 (选择首个元素作为 pivot) 来保证整体性能• 当快速排序表现不佳时 (limit==0),使用堆排序来保证最坏情况下时间复杂度仍然为 O(nlogn)> 当最终 pivot 的位置离序列两端很接近时 (距离小于 length/8) 判定其表现不佳,当这种情况的次数达到 limit (即 bits.Len(length))时,切换到堆排序。

Version 2

如何改进?

• 尽量使得 QuickSort 的 pivot 为序列的中位数 -> 改进 choose pivot

根据序列长度的不同,来决定选择策略

优化Pivot的选择

• 短序列 (<=8),选择固定元素

• 中序列 (<=50),采样三个元素,median of three

• 长序列 (>50),采样九个元素,median of medians

同时,pivot 的这种采样方式使得我们有探知序列当前状态的能力

采样的元素都是逆序排列 -> 序列可能已经逆序 -> 翻转整个序列

采样的元素都是顺序排列 -> 序列可能已经有序 -> 使用插入排序

插入排序实际使用 partiallnsertionSort,即有限制次数的插入排序

Final version

还有一种场景,元素重复较多的场景。

如果两次 partition 生成的 pivot 相同,即 partition 进行了无效分割,此时认为 pivot 的值为重复元素。

优化:当检测到此时的 pivot 和上次相同时(发生在 leftSubArray),使用 partitionEqual 将重复元素排列在一起,减少重复元素对于 pivot 选择的干扰。

其他优化:pivot 选择策略表现不佳时,随机交换元素。

避免一些极端情况使得 QuickSort 总是表现不佳,以及一些黑客攻击情况。

Reference

[1] sort: use pdqsort · Issue #50154 · golang/go

    https://github.com/golang/go/issues/50154

[2] Pattern-defeating Quicksort

    https://arxiv.org/abs/2106.05123

[3] go/zsortinterface.go at master · golang/go

    https://github.com/golang/go/blob/master/src/sort/zsortinterface.go