Skip to content

Commit 3bcf042

Browse files
zhangyunhao116eliben
authored andcommitted
slices: use pdqsort
Sync with CL 371574. - pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf - C++ implementation: https://github.com/orlp/pdqsort - Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ For golang/go#50154 name old time/op new time/op delta SortInts-16 10.8ms ± 3% 10.8ms ± 3% ~ (p=0.461 n=20+20) SlicesSortInts-16 6.14ms ± 3% 6.29ms ± 3% +2.43% (p=0.000 n=20+19) SlicesSortInts_Sorted-16 1.78ms ± 4% 0.09ms ± 2% -95.01% (p=0.000 n=18+20) SlicesSortInts_Reversed-16 1.82ms ± 5% 0.13ms ± 2% -92.67% (p=0.000 n=20+20) SortStrings-16 23.0ms ± 2% 23.1ms ± 1% ~ (p=0.253 n=20+20) SlicesSortStrings-16 19.1ms ± 2% 19.0ms ± 2% ~ (p=0.270 n=20+19) SortStructs-16 15.7ms ± 4% 15.7ms ± 3% ~ (p=0.989 n=20+20) SortFuncStructs-16 13.6ms ± 1% 12.7ms ± 3% -6.39% (p=0.000 n=19+20) Change-Id: Ia563fe1c70d6d2ac098a97e1f870040d038cf61c Reviewed-on: https://go-review.googlesource.com/c/exp/+/399315 Reviewed-by: Keith Randall <[email protected]> Run-TryBot: Keith Randall <[email protected]> Auto-Submit: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Eli Bendersky <[email protected]>
1 parent bcd2187 commit 3bcf042

File tree

5 files changed

+610
-259
lines changed

5 files changed

+610
-259
lines changed

slices/sort.go

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@
44

55
package slices
66

7-
import "golang.org/x/exp/constraints"
7+
import (
8+
"math/bits"
9+
10+
"golang.org/x/exp/constraints"
11+
)
812

913
// Sort sorts a slice of any ordered type in ascending order.
14+
// Sort may fail to sort correctly when sorting slices of floating-point
15+
// numbers containing Not-a-number (NaN) values.
16+
// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))})
17+
// instead if the input may contain NaNs.
1018
func Sort[E constraints.Ordered](x []E) {
1119
n := len(x)
12-
quickSortOrdered(x, 0, n, maxDepth(n))
20+
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
1321
}
1422

15-
// Sort sorts the slice x in ascending order as determined by the less function.
23+
// SortFunc sorts the slice x in ascending order as determined by the less function.
1624
// This sort is not guaranteed to be stable.
25+
//
26+
// SortFunc requires that less is a strict weak ordering.
27+
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
1728
func SortFunc[E any](x []E, less func(a, b E) bool) {
1829
n := len(x)
19-
quickSortLessFunc(x, 0, n, maxDepth(n), less)
30+
pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less)
2031
}
2132

2233
// SortStable sorts the slice x while keeping the original order of equal
@@ -76,16 +87,6 @@ func BinarySearchFunc[E any](x []E, target E, cmp func(E, E) int) (int, bool) {
7687
}
7788
}
7889

79-
// maxDepth returns a threshold at which quicksort should switch
80-
// to heapsort. It returns 2*ceil(lg(n+1)).
81-
func maxDepth(n int) int {
82-
var depth int
83-
for i := n; i > 0; i >>= 1 {
84-
depth++
85-
}
86-
return depth * 2
87-
}
88-
8990
func search(n int, f func(int) bool) int {
9091
// Define f(-1) == false and f(n) == true.
9192
// Invariant: f(i-1) == false, f(j) == true.
@@ -102,3 +103,25 @@ func search(n int, f func(int) bool) int {
102103
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
103104
return i
104105
}
106+
107+
type sortedHint int // hint for pdqsort when choosing the pivot
108+
109+
const (
110+
unknownHint sortedHint = iota
111+
increasingHint
112+
decreasingHint
113+
)
114+
115+
// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
116+
type xorshift uint64
117+
118+
func (r *xorshift) Next() uint64 {
119+
*r ^= *r << 13
120+
*r ^= *r >> 17
121+
*r ^= *r << 5
122+
return uint64(*r)
123+
}
124+
125+
func nextPowerOfTwo(length int) uint {
126+
return 1 << bits.Len(uint(length))
127+
}

slices/sort_benchmark_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ func makeRandomInts(n int) []int {
2222
return ints
2323
}
2424

25+
func makeSortedInts(n int) []int {
26+
ints := make([]int, n)
27+
for i := 0; i < n; i++ {
28+
ints[i] = i
29+
}
30+
return ints
31+
}
32+
33+
func makeReversedInts(n int) []int {
34+
ints := make([]int, n)
35+
for i := 0; i < n; i++ {
36+
ints[i] = n - i
37+
}
38+
return ints
39+
}
40+
2541
const N = 100_000
2642

2743
func BenchmarkSortInts(b *testing.B) {
@@ -42,6 +58,24 @@ func BenchmarkSlicesSortInts(b *testing.B) {
4258
}
4359
}
4460

61+
func BenchmarkSlicesSortInts_Sorted(b *testing.B) {
62+
for i := 0; i < b.N; i++ {
63+
b.StopTimer()
64+
ints := makeSortedInts(N)
65+
b.StartTimer()
66+
Sort(ints)
67+
}
68+
}
69+
70+
func BenchmarkSlicesSortInts_Reversed(b *testing.B) {
71+
for i := 0; i < b.N; i++ {
72+
b.StopTimer()
73+
ints := makeReversedInts(N)
74+
b.StartTimer()
75+
Sort(ints)
76+
}
77+
}
78+
4579
// Since we're benchmarking these sorts against each other, make sure that they
4680
// generate similar results.
4781
func TestIntSorts(t *testing.T) {

slices/sort_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ package slices
77
import (
88
"math"
99
"math/rand"
10+
"sort"
1011
"strconv"
1112
"strings"
1213
"testing"
1314
)
1415

1516
var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
16-
var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8}
17+
var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8, 74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3}
18+
var float64sWithNaNs = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8}
1719
var strs = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"}
1820

1921
func TestSortIntSlice(t *testing.T) {
@@ -43,6 +45,24 @@ func TestSortFloat64Slice(t *testing.T) {
4345
}
4446
}
4547

48+
func TestSortFloat64SliceWithNaNs(t *testing.T) {
49+
data := float64sWithNaNs[:]
50+
input := make([]float64, len(float64sWithNaNs))
51+
for i := range input {
52+
input[i] = float64sWithNaNs[i]
53+
}
54+
// Make sure Sort doesn't panic when the slice contains NaNs.
55+
Sort(data)
56+
// Check whether the result is a permutation of the input.
57+
sort.Float64s(data)
58+
sort.Float64s(input)
59+
for i, v := range input {
60+
if data[i] != v && !(math.IsNaN(data[i]) && math.IsNaN(v)) {
61+
t.Fatalf("the result is not a permutation of the input\ngot %v\nwant %v", data, input)
62+
}
63+
}
64+
}
65+
4666
func TestSortStringSlice(t *testing.T) {
4767
data := strs[:]
4868
Sort(data)

0 commit comments

Comments
 (0)