From c3da3bcd8ed7b9024ac9e753a0bbd055e009e5e8 Mon Sep 17 00:00:00 2001
From: Eli Bendersky <eliben@golang.org>
Date: Thu, 6 Jul 2023 08:19:25 -0700
Subject: [PATCH] sort: forward fixed-type slice sorting to slices package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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 <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Eli Bendersky <eliben@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
---
 src/go/build/deps_test.go              |  16 +-
 src/slices/slices_test.go              |  22 +--
 src/slices/sort_benchmark_test.go      | 251 +------------------------
 src/slices/sort_test.go                |  22 +--
 src/sort/slice.go                      |   9 +
 src/sort/sort.go                       |  24 +--
 src/sort/sort_impl_120.go              |  15 ++
 src/sort/sort_impl_go121.go            |  22 +++
 src/sort/sort_slices_benchmark_test.go | 201 ++++++++++++++++++++
 src/sort/sort_test.go                  |  16 ++
 10 files changed, 297 insertions(+), 301 deletions(-)
 create mode 100644 src/sort/sort_impl_120.go
 create mode 100644 src/sort/sort_impl_go121.go
 create mode 100644 src/sort/sort_slices_benchmark_test.go

diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 2f335068b8a..bdb09737b03 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 e6da3b0e039..8ea93c66d76 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 0f088425949..d73a3182c91 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 af0585935df..7aaf954214f 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 d0b21020138..73ba548a474 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 1760e12c256..8ea62a5e6a5 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 00000000000..5980da67e78
--- /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 00000000000..0a6a6a62e76
--- /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 00000000000..37f3b1bc7e4
--- /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 862bba2d441..62f51ba6398 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:])
-- 
GitLab