Skip to content

Commit

Permalink
sort: forward fixed-type slice sorting to slices package
Browse files Browse the repository at this point in the history
Forwards the following functions to the slices package:

    sort.Ints
    sort.Strings
    sort.Float64s
    sort.IntsAreSorted
    sort.StringsAreSorted
    sort.Float64sAreSorted

benchstat results on the sort package's benchmarks:

goos: linux
goarch: amd64
pkg: sort
cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
SearchWrappers-8        58.10n ± 0%   58.43n ± 1%   +0.57% (p=0.004 n=10)
SortString1K-8          76.53µ ± 1%   66.04µ ± 2%  -13.71% (p=0.000 n=10)
SortString1K_Slice-8    71.99µ ± 1%   72.32µ ± 2%        ~ (p=0.481 n=10)
StableString1K-8        92.66µ ± 1%   92.10µ ± 2%   -0.61% (p=0.019 n=10)
SortInt1K-8             34.31µ ± 0%   11.49µ ± 2%  -66.50% (p=0.000 n=10)
SortInt1K_Sorted-8     2699.5n ± 1%   959.0n ± 3%  -64.47% (p=0.000 n=10)
SortInt1K_Reversed-8    3.990µ ± 1%   1.429µ ± 4%  -64.19% (p=0.000 n=10)
SortInt1K_Mod8-8       13.695µ ± 1%   5.129µ ± 2%  -62.55% (p=0.000 n=10)
StableInt1K-8           46.22µ ± 1%   46.80µ ± 1%        ~ (p=0.109 n=10)
StableInt1K_Slice-8     44.12µ ± 1%   44.32µ ± 2%        ~ (p=0.315 n=10)
SortInt64K-8            3.848m ± 0%   1.857m ± 2%  -51.76% (p=0.000 n=10)
SortInt64K_Slice-8      3.690m ± 0%   3.740m ± 0%   +1.36% (p=0.002 n=10)
StableInt64K-8          3.901m ± 0%   3.917m ± 0%   +0.42% (p=0.003 n=10)
Sort1e2-8               32.22µ ± 2%   32.40µ ± 2%        ~ (p=0.529 n=10)
Stable1e2-8             54.11µ ± 1%   54.11µ ± 1%        ~ (p=0.796 n=10)
Sort1e4-8               5.998m ± 1%   5.993m ± 1%        ~ (p=0.579 n=10)
Stable1e4-8             15.23m ± 0%   15.32m ± 0%   +0.59% (p=0.000 n=10)
Sort1e6-8               902.8m ± 0%   904.3m ± 0%        ~ (p=0.075 n=10)
Stable1e6-8              3.089 ± 0%    3.089 ± 0%        ~ (p=0.971 n=10)
geomean                 259.8µ        200.0µ       -22.99%

Most of the benchmarks are unaffected. The ones with significant reductions
are precisely for the functions that were forwarded.

This CL has to move some things around to avoid a circular dependency
between sort and slices. Since sort depends on slices now, nothing in
slices can depend on sort - not even in tests.

Fixes #61180

Change-Id: Ic0e5f519863d96a139fada08aefb1bcdf4c7a9a3
Reviewed-on: https://go-review.googlesource.com/c/go/+/508135
Run-TryBot: Ian Lance Taylor <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
Reviewed-by: Eli Bendersky <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
eliben committed Jul 21, 2023
1 parent 12f3d68 commit c3da3bc
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 301 deletions.
16 changes: 9 additions & 7 deletions src/go/build/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,15 @@ var depsRules = `
< internal/oserror, math/bits
< RUNTIME;
RUNTIME
< sort
# slices depends on unsafe for overlapping check, cmp for comparison
# semantics, and math/bits for # calculating bitlength of numbers.
unsafe, cmp, math/bits
< slices;
RUNTIME, slices
< sort;
sort
< container/heap;
RUNTIME
Expand Down Expand Up @@ -223,11 +230,6 @@ var depsRules = `
< hash
< hash/adler32, hash/crc32, hash/crc64, hash/fnv;
# slices depends on unsafe for overlapping check, cmp for comparison
# semantics, and math/bits for # calculating bitlength of numbers.
unsafe, cmp, math/bits
< slices;
# math/big
FMT, encoding/binary, math/rand
< math/big;
Expand Down
22 changes: 2 additions & 20 deletions src/slices/slices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package slices
package slices_test

import (
"cmp"
"internal/race"
"internal/testenv"
"math"
. "slices"
"strings"
"testing"
)
Expand Down Expand Up @@ -999,25 +1000,6 @@ func BenchmarkReplace(b *testing.B) {

}

func TestRotate(t *testing.T) {
const N = 10
s := make([]int, 0, N)
for n := 0; n < N; n++ {
for r := 0; r < n; r++ {
s = s[:0]
for i := 0; i < n; i++ {
s = append(s, i)
}
rotateLeft(s, r)
for i := 0; i < n; i++ {
if s[i] != (i+r)%n {
t.Errorf("expected n=%d r=%d i:%d want:%d got:%d", n, r, i, (i+r)%n, s[i])
}
}
}
}
}

func TestInsertGrowthRate(t *testing.T) {
b := make([]byte, 1)
maxCap := cap(b)
Expand Down
251 changes: 9 additions & 242 deletions src/slices/sort_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,252 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package slices
package slices_test

import (
"fmt"
"math/rand"
"sort"
"strconv"
"strings"
"slices"
"testing"
)

// These benchmarks compare sorting a large slice of int with sort.Ints vs.
// slices.Sort
func makeRandomInts(n int) []int {
rand.Seed(42)
ints := make([]int, n)
for i := 0; i < n; i++ {
ints[i] = rand.Intn(n)
}
return ints
}

func makeSortedInts(n int) []int {
ints := make([]int, n)
for i := 0; i < n; i++ {
ints[i] = i
}
return ints
}

func makeReversedInts(n int) []int {
ints := make([]int, n)
for i := 0; i < n; i++ {
ints[i] = n - i
}
return ints
}

const N = 100_000

func BenchmarkSortInts(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeRandomInts(N)
b.StartTimer()
sort.Ints(ints)
}
}

func makeSortedStrings(n int) []string {
x := make([]string, n)
for i := 0; i < n; i++ {
x[i] = strconv.Itoa(i)
}
Sort(x)
return x
}

func BenchmarkSlicesSortInts(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeRandomInts(N)
b.StartTimer()
Sort(ints)
}
}

func BenchmarkSlicesSortInts_Sorted(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeSortedInts(N)
b.StartTimer()
Sort(ints)
}
}

func BenchmarkSlicesSortInts_Reversed(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeReversedInts(N)
b.StartTimer()
Sort(ints)
}
}

func BenchmarkIntsAreSorted(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeSortedInts(N)
b.StartTimer()
sort.IntsAreSorted(ints)
}
}

func BenchmarkIsSorted(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ints := makeSortedInts(N)
b.StartTimer()
IsSorted(ints)
}
}

// Since we're benchmarking these sorts against each other, make sure that they
// generate similar results.
func TestIntSorts(t *testing.T) {
ints := makeRandomInts(200)
ints2 := Clone(ints)

sort.Ints(ints)
Sort(ints2)

for i := range ints {
if ints[i] != ints2[i] {
t.Fatalf("ints2 mismatch at %d; %d != %d", i, ints[i], ints2[i])
}
}
}

// The following is a benchmark for sorting strings.

// makeRandomStrings generates n random strings with alphabetic runes of
// varying lengths.
func makeRandomStrings(n int) []string {
rand.Seed(42)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
ss := make([]string, n)
for i := 0; i < n; i++ {
var sb strings.Builder
slen := 2 + rand.Intn(50)
for j := 0; j < slen; j++ {
sb.WriteRune(letters[rand.Intn(len(letters))])
}
ss[i] = sb.String()
}
return ss
}

func TestStringSorts(t *testing.T) {
ss := makeRandomStrings(200)
ss2 := Clone(ss)

sort.Strings(ss)
Sort(ss2)

for i := range ss {
if ss[i] != ss2[i] {
t.Fatalf("ss2 mismatch at %d; %s != %s", i, ss[i], ss2[i])
}
}
}

func BenchmarkSortStrings(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ss := makeRandomStrings(N)
b.StartTimer()
sort.Strings(ss)
}
}

func BenchmarkSortStrings_Sorted(b *testing.B) {
ss := makeSortedStrings(N)
b.ResetTimer()

for i := 0; i < b.N; i++ {
sort.Strings(ss)
}
}

func BenchmarkSlicesSortStrings(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ss := makeRandomStrings(N)
b.StartTimer()
Sort(ss)
}
}

func BenchmarkSlicesSortStrings_Sorted(b *testing.B) {
ss := makeSortedStrings(N)
b.ResetTimer()

for i := 0; i < b.N; i++ {
Sort(ss)
}
}

// These benchmarks compare sorting a slice of structs with sort.Sort vs.
// slices.SortFunc.
type myStruct struct {
a, b, c, d string
n int
}

type myStructs []*myStruct

func (s myStructs) Len() int { return len(s) }
func (s myStructs) Less(i, j int) bool { return s[i].n < s[j].n }
func (s myStructs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func makeRandomStructs(n int) myStructs {
rand.Seed(42)
structs := make([]*myStruct, n)
for i := 0; i < n; i++ {
structs[i] = &myStruct{n: rand.Intn(n)}
}
return structs
}

func TestStructSorts(t *testing.T) {
ss := makeRandomStructs(200)
ss2 := make([]*myStruct, len(ss))
for i := range ss {
ss2[i] = &myStruct{n: ss[i].n}
}

sort.Sort(ss)
SortFunc(ss2, func(a, b *myStruct) int { return a.n - b.n })

for i := range ss {
if *ss[i] != *ss2[i] {
t.Fatalf("ints2 mismatch at %d; %v != %v", i, *ss[i], *ss2[i])
}
}
}

func BenchmarkSortStructs(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
ss := makeRandomStructs(N)
b.StartTimer()
sort.Sort(ss)
}
}

func BenchmarkSortFuncStructs(b *testing.B) {
cmpFunc := func(a, b *myStruct) int { return a.n - b.n }
for i := 0; i < b.N; i++ {
b.StopTimer()
ss := makeRandomStructs(N)
b.StartTimer()
SortFunc(ss, cmpFunc)
}
}

func BenchmarkBinarySearchFloats(b *testing.B) {
for _, size := range []int{16, 32, 64, 128, 512, 1024} {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
Expand All @@ -259,12 +21,17 @@ func BenchmarkBinarySearchFloats(b *testing.B) {
needle := (floats[midpoint] + floats[midpoint+1]) / 2
b.ResetTimer()
for i := 0; i < b.N; i++ {
BinarySearch(floats, needle)
slices.BinarySearch(floats, needle)
}
})
}
}

type myStruct struct {
a, b, c, d string
n int
}

func BenchmarkBinarySearchFuncStruct(b *testing.B) {
for _, size := range []int{16, 32, 64, 128, 512, 1024} {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
Expand All @@ -277,7 +44,7 @@ func BenchmarkBinarySearchFuncStruct(b *testing.B) {
lessFunc := func(a, b *myStruct) int { return a.n - b.n }
b.ResetTimer()
for i := 0; i < b.N; i++ {
BinarySearchFunc(structs, needle, lessFunc)
slices.BinarySearchFunc(structs, needle, lessFunc)
}
})
}
Expand Down
Loading

0 comments on commit c3da3bc

Please sign in to comment.