Skip to content

Commit 7de56dc

Browse files
committed
slices: use dual-pivot quicksort
Dual-pivot quicksort has smaller recursion depth. Some pattern-defeating technique is added to make this variant. And use double reversion instead of blockswap for symmerge. Benchmark results on Xeon-8372C name old time/op new time/op delta SlicesSortInts-4 7.01ms ± 0% 5.84ms ± 0% -16.64% (p=0.000 n=10+10) SlicesSortInts_Sorted-4 80.1µs ± 1% 69.1µs ± 0% -13.76% (p=0.000 n=8+10) SlicesSortInts_Reversed-4 116µs ± 1% 108µs ± 0% -6.72% (p=0.000 n=10+10) SlicesSortInts_Mixed-4 5.36ms ± 0% 4.31ms ± 0% -19.53% (p=0.000 n=10+9) SlicesSortStrings-4 22.5ms ± 0% 20.9ms ± 1% -7.12% (p=0.000 n=10+10) SortFuncStructs-4 13.4ms ± 1% 12.5ms ± 1% -7.00% (p=0.000 n=10+10) SortFuncStructs_Stable-4 31.1ms ± 1% 30.7ms ± 1% -1.25% (p=0.000 n=10+10) For golang/go#52789
1 parent 39d4317 commit 7de56dc

File tree

5 files changed

+696
-814
lines changed

5 files changed

+696
-814
lines changed

slices/sort.go

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ import (
1616
// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))})
1717
// instead if the input may contain NaNs.
1818
func Sort[E constraints.Ordered](x []E) {
19-
n := len(x)
20-
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
19+
sortFast(x)
20+
}
21+
22+
// SortStable sorts the slice x while keeping the original order of equal
23+
func SortStable[E constraints.Ordered](x []E) {
24+
sortStable(x)
2125
}
2226

2327
// SortFunc sorts the slice x in ascending order as determined by the less function.
@@ -26,14 +30,13 @@ func Sort[E constraints.Ordered](x []E) {
2630
// SortFunc requires that less is a strict weak ordering.
2731
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
2832
func SortFunc[E any](x []E, less func(a, b E) bool) {
29-
n := len(x)
30-
pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less)
33+
lessFunc[E](less).sortFast(x)
3134
}
3235

3336
// SortStable sorts the slice x while keeping the original order of equal
3437
// elements, using less to compare elements.
3538
func SortStableFunc[E any](x []E, less func(a, b E) bool) {
36-
stableLessFunc(x, len(x), less)
39+
lessFunc[E](less).sortStable(x)
3740
}
3841

3942
// IsSorted reports whether x is sorted in ascending order.
@@ -104,24 +107,27 @@ func search(n int, f func(int) bool) int {
104107
return i
105108
}
106109

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
110+
func log2Ceil(num uint) int {
111+
return bits.Len(num)
112+
}
117113

118-
func (r *xorshift) Next() uint64 {
119-
*r ^= *r << 13
120-
*r ^= *r >> 17
121-
*r ^= *r << 5
122-
return uint64(*r)
114+
func reverse[E any](list []E) {
115+
for l, r := 0, len(list)-1; l < r; {
116+
list[l], list[r] = list[r], list[l]
117+
l++
118+
r--
119+
}
123120
}
124121

125-
func nextPowerOfTwo(length int) uint {
126-
return 1 << bits.Len(uint(length))
122+
// With small E, double reversion is faster than the BlockSwap rotation.
123+
// BlockSwap rotation needs less swaps, but more branches.
124+
func rotate[E any](list []E, border int) {
125+
reverse(list[:border])
126+
reverse(list[border:])
127+
reverse(list)
127128
}
129+
130+
const (
131+
hintSorted uint8 = 1 << iota
132+
hintRevered
133+
)

slices/sort_benchmark_test.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,50 @@ import (
1414
// These benchmarks compare sorting a large slice of int with sort.Ints vs.
1515
// slices.Sort
1616
func makeRandomInts(n int) []int {
17-
rand.Seed(42)
1817
ints := make([]int, n)
19-
for i := 0; i < n; i++ {
18+
fillRandomInts(ints)
19+
return ints
20+
}
21+
22+
func fillRandomInts(ints []int) {
23+
rand.Seed(42)
24+
n := len(ints)
25+
for i := 0; i < len(ints); i++ {
2026
ints[i] = rand.Intn(n)
2127
}
22-
return ints
2328
}
2429

2530
func makeSortedInts(n int) []int {
2631
ints := make([]int, n)
27-
for i := 0; i < n; i++ {
32+
fillSortedInts(ints)
33+
return ints
34+
}
35+
36+
func fillSortedInts(ints []int) {
37+
for i := 0; i < len(ints); i++ {
2838
ints[i] = i
2939
}
30-
return ints
3140
}
3241

3342
func makeReversedInts(n int) []int {
3443
ints := make([]int, n)
44+
fillReversedInts(ints)
45+
return ints
46+
}
47+
48+
func fillReversedInts(ints []int) {
49+
n := len(ints)
3550
for i := 0; i < n; i++ {
3651
ints[i] = n - i
3752
}
53+
}
54+
55+
func makeMixedInts(n int) []int {
56+
ints := make([]int, n)
57+
m := n / 3
58+
fillSortedInts(ints[:m])
59+
fillRandomInts(ints[m : n-m])
60+
fillReversedInts(ints[n-m:])
3861
return ints
3962
}
4063

@@ -76,6 +99,15 @@ func BenchmarkSlicesSortInts_Reversed(b *testing.B) {
7699
}
77100
}
78101

102+
func BenchmarkSlicesSortInts_Mixed(b *testing.B) {
103+
for i := 0; i < b.N; i++ {
104+
b.StopTimer()
105+
ints := makeMixedInts(N)
106+
b.StartTimer()
107+
Sort(ints)
108+
}
109+
}
110+
79111
// Since we're benchmarking these sorts against each other, make sure that they
80112
// generate similar results.
81113
func TestIntSorts(t *testing.T) {
@@ -191,12 +223,31 @@ func BenchmarkSortStructs(b *testing.B) {
191223
}
192224
}
193225

226+
func BenchmarkSortStructs_Stable(b *testing.B) {
227+
for i := 0; i < b.N; i++ {
228+
b.StopTimer()
229+
ss := makeRandomStructs(N)
230+
b.StartTimer()
231+
sort.Stable(ss)
232+
}
233+
}
234+
194235
func BenchmarkSortFuncStructs(b *testing.B) {
195-
lessFunc := func(a, b *myStruct) bool { return a.n < b.n }
236+
less := func(a, b *myStruct) bool { return a.n < b.n }
237+
for i := 0; i < b.N; i++ {
238+
b.StopTimer()
239+
ss := makeRandomStructs(N)
240+
b.StartTimer()
241+
SortFunc(ss, less)
242+
}
243+
}
244+
245+
func BenchmarkSortFuncStructs_Stable(b *testing.B) {
246+
less := func(a, b *myStruct) bool { return a.n < b.n }
196247
for i := 0; i < b.N; i++ {
197248
b.StopTimer()
198249
ss := makeRandomStructs(N)
199250
b.StartTimer()
200-
SortFunc(ss, lessFunc)
251+
SortStableFunc(ss, less)
201252
}
202253
}

slices/sort_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ func TestSortLarge_Random(t *testing.T) {
9090
}
9191
}
9292

93+
// It's hard to run heapSort from API, test it alone
94+
func TestHeapSort(t *testing.T) {
95+
n := 100
96+
data := make([]int, n)
97+
for i := 0; i < len(data); i++ {
98+
data[i] = rand.Intn(n)
99+
}
100+
if IsSorted(data) {
101+
t.Fatalf("terrible rand.rand")
102+
}
103+
heapSort(data)
104+
if !IsSorted(data) {
105+
t.Errorf("heapsort didn't sort - 100 ints")
106+
}
107+
}
108+
93109
type intPair struct {
94110
a, b int
95111
}

0 commit comments

Comments
 (0)