diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 2f335068b8..bdb09737b0 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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 @@ -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; diff --git a/src/slices/slices_test.go b/src/slices/slices_test.go index e6da3b0e03..8ea93c66d7 100644 --- a/src/slices/slices_test.go +++ b/src/slices/slices_test.go @@ -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" ) @@ -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) diff --git a/src/slices/sort_benchmark_test.go b/src/slices/sort_benchmark_test.go index 0f08842594..d73a3182c9 100644 --- a/src/slices/sort_benchmark_test.go +++ b/src/slices/sort_benchmark_test.go @@ -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) { @@ -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) { @@ -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) } }) } diff --git a/src/slices/sort_test.go b/src/slices/sort_test.go index af0585935d..7aaf954214 100644 --- a/src/slices/sort_test.go +++ b/src/slices/sort_test.go @@ -2,14 +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" "fmt" "math" "math/rand" - "sort" + . "slices" "strconv" "strings" "testing" @@ -17,7 +17,6 @@ import ( var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586} 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} -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} var strs = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"} func TestSortIntSlice(t *testing.T) { @@ -47,23 +46,6 @@ func TestSortFloat64Slice(t *testing.T) { } } -func TestSortFloat64SliceWithNaNs(t *testing.T) { - data := float64sWithNaNs[:] - data2 := Clone(data) - - Sort(data) - sort.Float64s(data2) - - if !IsSorted(data) { - t.Error("IsSorted indicates data isn't sorted") - } - - // Compare for equality using cmp.Compare, which considers NaNs equal. - if !EqualFunc(data, data2, func(a, b float64) bool { return cmp.Compare(a, b) == 0 }) { - t.Errorf("mismatch between Sort and sort.Float64: got %v, want %v", data, data2) - } -} - func TestSortStringSlice(t *testing.T) { data := Clone(strs[:]) Sort(data) diff --git a/src/sort/slice.go b/src/sort/slice.go index d0b2102013..73ba548a47 100644 --- a/src/sort/slice.go +++ b/src/sort/slice.go @@ -18,6 +18,9 @@ import ( // // The less function must satisfy the same requirements as // the Interface type's Less method. +// +// Note: in many situations, the newer slices.SortFunc function is more +// ergonomic and runs faster. func Slice(x any, less func(i, j int) bool) { rv := reflectlite.ValueOf(x) swap := reflectlite.Swapper(x) @@ -32,6 +35,9 @@ func Slice(x any, less func(i, j int) bool) { // // The less function must satisfy the same requirements as // the Interface type's Less method. +// +// Note: in many situations, the newer slices.SortStableFunc function is more +// ergonomic and runs faster. func SliceStable(x any, less func(i, j int) bool) { rv := reflectlite.ValueOf(x) swap := reflectlite.Swapper(x) @@ -40,6 +46,9 @@ func SliceStable(x any, less func(i, j int) bool) { // SliceIsSorted reports whether the slice x is sorted according to the provided less function. // It panics if x is not a slice. +// +// Note: in many situations, the newer slices.IsSortedFunc function is more +// ergonomic and runs faster. func SliceIsSorted(x any, less func(i, j int) bool) bool { rv := reflectlite.ValueOf(x) n := rv.Len() diff --git a/src/sort/sort.go b/src/sort/sort.go index 1760e12c25..8ea62a5e6a 100644 --- a/src/sort/sort.go +++ b/src/sort/sort.go @@ -161,35 +161,35 @@ func (x StringSlice) Sort() { Sort(x) } // Ints sorts a slice of ints in increasing order. // -// Note: consider using the newer slices.Sort function, which runs faster. -func Ints(x []int) { Sort(IntSlice(x)) } +// Note: as of Go 1.22, this function simply calls slices.Sort. +func Ints(x []int) { intsImpl(x) } // Float64s sorts a slice of float64s in increasing order. // Not-a-number (NaN) values are ordered before other values. // -// Note: consider using the newer slices.Sort function, which runs faster. -func Float64s(x []float64) { Sort(Float64Slice(x)) } +// Note: as of Go 1.22, this function simply calls slices.Sort. +func Float64s(x []float64) { float64sImpl(x) } // Strings sorts a slice of strings in increasing order. // -// Note: consider using the newer slices.Sort function, which runs faster. -func Strings(x []string) { Sort(StringSlice(x)) } +// Note: as of Go 1.22, this function simply calls slices.Sort. +func Strings(x []string) { stringsImpl(x) } // IntsAreSorted reports whether the slice x is sorted in increasing order. // -// Note: consider using the newer slices.IsSorted function, which runs faster. -func IntsAreSorted(x []int) bool { return IsSorted(IntSlice(x)) } +// Note: as of Go 1.22, this function simply calls slices.IsSorted. +func IntsAreSorted(x []int) bool { return intsAreSortedImpl(x) } // Float64sAreSorted reports whether the slice x is sorted in increasing order, // with not-a-number (NaN) values before any other values. // -// Note: consider using the newer slices.IsSorted function, which runs faster. -func Float64sAreSorted(x []float64) bool { return IsSorted(Float64Slice(x)) } +// Note: as of Go 1.22, this function simply calls slices.IsSorted. +func Float64sAreSorted(x []float64) bool { return float64sAreSortedImpl(x) } // StringsAreSorted reports whether the slice x is sorted in increasing order. // -// Note: consider using the newer slices.IsSorted function, which runs faster. -func StringsAreSorted(x []string) bool { return IsSorted(StringSlice(x)) } +// Note: as of Go 1.22, this function simply calls slices.IsSorted. +func StringsAreSorted(x []string) bool { return stringsAreSortedImpl(x) } // Notes on stable sorting: // The used algorithms are simple and provable correct on all input and use diff --git a/src/sort/sort_impl_120.go b/src/sort/sort_impl_120.go new file mode 100644 index 0000000000..5980da67e7 --- /dev/null +++ b/src/sort/sort_impl_120.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.21 + +package sort + +func intsImpl(x []int) { Sort(IntSlice(x)) } +func float64sImpl(x []float64) { Sort(Float64Slice(x)) } +func stringsImpl(x []string) { Sort(StringSlice(x)) } + +func intsAreSortedImpl(x []int) bool { return IsSorted(IntSlice(x)) } +func float64sAreSortedImpl(x []float64) bool { return IsSorted(Float64Slice(x)) } +func stringsAreSortedImpl(x []string) bool { return IsSorted(StringSlice(x)) } diff --git a/src/sort/sort_impl_go121.go b/src/sort/sort_impl_go121.go new file mode 100644 index 0000000000..0a6a6a62e7 --- /dev/null +++ b/src/sort/sort_impl_go121.go @@ -0,0 +1,22 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 + +// Starting with Go 1.21, we can leverage the new generic functions from the +// slices package to implement some `sort` functions faster. However, until +// the bootstrap compiler uses Go 1.21 or later, we keep a fallback version +// in sort_impl_120.go that retains the old implementation. + +package sort + +import "slices" + +func intsImpl(x []int) { slices.Sort(x) } +func float64sImpl(x []float64) { slices.Sort(x) } +func stringsImpl(x []string) { slices.Sort(x) } + +func intsAreSortedImpl(x []int) bool { return slices.IsSorted(x) } +func float64sAreSortedImpl(x []float64) bool { return slices.IsSorted(x) } +func stringsAreSortedImpl(x []string) bool { return slices.IsSorted(x) } diff --git a/src/sort/sort_slices_benchmark_test.go b/src/sort/sort_slices_benchmark_test.go new file mode 100644 index 0000000000..37f3b1bc7e --- /dev/null +++ b/src/sort/sort_slices_benchmark_test.go @@ -0,0 +1,201 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sort_test + +import ( + "math/rand" + "slices" + . "sort" + "strconv" + stringspkg "strings" + "testing" +) + +// Benchmarks comparing sorting from the slices package with functions from +// the sort package (avoiding functions that are just forwarding to the slices +// package). + +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 +} + +func makeSortedStrings(n int) []string { + x := make([]string, n) + for i := 0; i < n; i++ { + x[i] = strconv.Itoa(i) + } + Strings(x) + return x +} + +const N = 100_000 + +func BenchmarkSortInts(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeRandomInts(N) + b.StartTimer() + Sort(IntSlice(ints)) + } +} + +func BenchmarkSlicesSortInts(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeRandomInts(N) + b.StartTimer() + slices.Sort(ints) + } +} + +func BenchmarkSortIsSorted(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeSortedInts(N) + b.StartTimer() + IsSorted(IntSlice(ints)) + } +} + +func BenchmarkSlicesIsSorted(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ints := makeSortedInts(N) + b.StartTimer() + slices.IsSorted(ints) + } +} + +// 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 stringspkg.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 BenchmarkSortStrings(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStrings(N) + b.StartTimer() + Sort(StringSlice(ss)) + } +} + +func BenchmarkSlicesSortStrings(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + ss := makeRandomStrings(N) + b.StartTimer() + slices.Sort(ss) + } +} + +func BenchmarkSortStrings_Sorted(b *testing.B) { + ss := makeSortedStrings(N) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Sort(StringSlice(ss)) + } +} + +func BenchmarkSlicesSortStrings_Sorted(b *testing.B) { + ss := makeSortedStrings(N) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + slices.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(ss) + slices.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(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() + slices.SortFunc(ss, cmpFunc) + } +} diff --git a/src/sort/sort_test.go b/src/sort/sort_test.go index 862bba2d44..62f51ba639 100644 --- a/src/sort/sort_test.go +++ b/src/sort/sort_test.go @@ -5,10 +5,12 @@ package sort_test import ( + "cmp" "fmt" "internal/testenv" "math" "math/rand" + "slices" . "sort" "strconv" stringspkg "strings" @@ -39,6 +41,20 @@ func TestSortFloat64Slice(t *testing.T) { } } +// Compare Sort with slices.Sort sorting a float64 slice containing NaNs. +func TestSortFloat64sCompareSlicesSort(t *testing.T) { + slice1 := slices.Clone(float64s[:]) + slice2 := slices.Clone(float64s[:]) + + Sort(Float64Slice(slice1)) + slices.Sort(slice2) + + // Compare for equality using cmp.Compare, which considers NaNs equal. + if !slices.EqualFunc(slice1, slice1, func(a, b float64) bool { return cmp.Compare(a, b) == 0 }) { + t.Errorf("mismatch between Sort and slices.Sort: got %v, want %v", slice1, slice2) + } +} + func TestSortStringSlice(t *testing.T) { data := strings a := StringSlice(data[0:])