性能:使用Sort实现对Slice进行排序vs(Slice)的排序类型

时间:2019-01-20 12:10:35

标签: sorting go slice

我在处理一些代码挑战时,发现自定义排序(排序接口的实现)的工作速度远远快于切片的原始结构。这是为什么?切片转换为类型是否会产生一些麻烦(例如转换为指向结构的指针切片)?

我编写了一些代码来测试我的肝功能病

package sortingexample

import (
    "sort"
    "testing"
)

// Example of struct we going to sort.

type Point struct {
    X, Y int
}

// --- Struct / Raw Data
var TestCases = []Point{
    {10, 3},
    {10, 4},
    {10, 35},
    {10, 5},
    {10, 51},
    {10, 25},
    {10, 59},
    {10, 15},
    {10, 22},
    {10, 91},
}

// Example One - Sorting Slice Directly
// somehow - slowest way to sort it.
func SortSlice(points []Point) {
    sort.Slice(points, func(i, j int) bool {
        return points[i].Y < points[j].Y
    })
}

func BenchmarkSlice(b *testing.B) {
    tmp := make([]Point, len(TestCases))
    for i := 0; i < b.N; i++ {
        copy(tmp, TestCases)
        SortSlice(tmp)
    }
}

// Example Two - Sorting Slice Directly
// much faster performance
type Points []Point

// Sort interface implementation
func (p Points) Less(i, j int) bool { return p[i].Y < p[j].Y }
func (p Points) Len() int           { return len(p) }
func (p Points) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func SortStruct(points []Point) {
    sort.Sort(Points(points))
}

func BenchmarkStruct(b *testing.B) {
    tmp := make([]Point, len(TestCases))
    for i := 0; i < b.N; i++ {
        copy(tmp, TestCases)
        SortStruct(tmp)
    }
}

// --- Pointers
var TestCasesPoints = []*Point{
    &Point{10, 3},
    &Point{10, 4},
    &Point{10, 35},
    &Point{10, 5},
    &Point{10, 51},
    &Point{10, 25},
    &Point{10, 59},
    &Point{10, 15},
    &Point{10, 22},
    &Point{10, 91},
}

// Example Three - Sorting Slice of Pointers

func SortSlicePointers(points []*Point) {
    sort.Slice(points, func(i, j int) bool {
        return points[i].Y < points[j].Y
    })
}

func BenchmarkSlicePointers(b *testing.B) {
    tmp := make([]*Point, len(TestCasesPoints))
    for i := 0; i < b.N; i++ {
        copy(tmp, TestCasesPoints)
        SortSlicePointers(tmp)
    }
}

// Example Four - Sorting Struct (with Slice of pointers beneath it)
type PointsPointer []*Point

func (pp PointsPointer) Less(i, j int) bool { return pp[i].Y < pp[j].Y }
func (pp PointsPointer) Len() int           { return len(pp) }
func (pp PointsPointer) Swap(i, j int)      { pp[i], pp[j] = pp[j], pp[i] }

func SortStructOfSlicePointers(points []*Point) {
    sort.Sort(PointsPointer(points))
}

func BenchmarkStructOfSlicePointers(b *testing.B) {
    tmp := make([]*Point, len(TestCasesPoints))

    for i := 0; i < b.N; i++ {
        copy(tmp, TestCasesPoints)
        SortStructOfSlicePointers(tmp)
    }
}

这是结果...

> go test -bench=.
goos: darwin
goarch: amd64
BenchmarkSlice-4                     3000000           542 ns/op
BenchmarkStruct-4                    5000000           318 ns/op
BenchmarkSlicePointers-4             5000000           280 ns/op
BenchmarkStructOfSlicePointers-4     5000000           321 ns/op

很显然,对切片指针进行排序会更快,但是为什么自定义排序实现会更快?有什么我可以阅读的资源吗?

1 个答案:

答案 0 :(得分:5)

常规sort.Slice()sort.SliceStable()函数可在任何片上使用。您必须将slice值作为interface{}值进行传递,并且实现必须使用反射(reflect包)来访问其元素和长度,并执行元素交换。

相反,当您自己实现sort.Interface类型时,在实现中您可以访问切片的静态类型,并且可以提供sort.Interface的实现而无需依赖,这是什么会使它更快。

因此,如果性能至关重要/很重要,请始终自己提供sort.Interface实现。如果切片很小或性能不重要,则可以使用更方便的sort.Slice()函数。