【问题标题】:Golang custom sort is faster than native sortGolang 自定义排序比原生排序快
【发布时间】:2014-06-10 04:22:15
【问题描述】:

我只是在 golang 中玩弄排序,我在 stackoverflow 上找到了一个 qsort 函数。它的运行速度似乎是 golang 中原生排序功能的两倍。我已经尝试了不同的输入大小并测试它是否有效。

谁能解释为什么会这样?

这是你可以在你的电脑上测试的代码:

package main

import (
    "fmt"
    "math/rand"
    "sort"
    "time"
)

func qsort(a []int) []int {
    if len(a) < 2 {
        return a
    }

    left, right := 0, len(a)-1

    // Pick a pivot
    pivotIndex := rand.Int() % len(a)

    // Move the pivot to the right
    a[pivotIndex], a[right] = a[right], a[pivotIndex]

    // Pile elements smaller than the pivot on the left
    for i := range a {
        if a[i] < a[right] {
            a[i], a[left] = a[left], a[i]
            left++
        }
    }

    // Place the pivot after the last smaller element
    a[left], a[right] = a[right], a[left]

    // Go down the rabbit hole
    qsort(a[:left])
    qsort(a[left+1:])

    return a
}

func main() {
    // Create an array with random integers
    rand.Seed(30)
    size := 1000000
    array1 := make([]int, size)
    start := time.Now()

    for i, _ := range array1 {
        array1[i] = rand.Int()
    }

    fmt.Println("Creating array with ", size, " elements...")
    fmt.Println("--- ", time.Since(start), " ---")
    // Create a copy of the unsorted array
    array2 := make([]int, size)
    copy(array2, array1)

    // Short using native function
    start = time.Now()
    sort.Ints(array1)

    fmt.Println("Sorting with the native sort...")
    fmt.Println("--- ", time.Since(start), " ---")

    // Sort using custom qsort
    start = time.Now()
    qsort(array2)

    fmt.Println("Sorting with custom qsort...")
    fmt.Println("--- ", time.Since(start), " ---")

}

【问题讨论】:

  • 内置是否使用qsort? qsort 可能快一点,但也可能慢得令人难以置信(例如,在对已经排序或几乎已经排序的数组进行排序时(这在实践中很常见)。 qsort 的最坏情况是 O(N^2),但对于许多其他类型来说是 O(N log N)。请参阅 this post 了解 Perl 中的类似实验。
  • 你应该在 println 之前计算时间增量,然后打印出来,因为 println 可能会干扰你的时间结果。
  • 我现在正在编写一个真正的基准测试来尝试回答。首先让我印象深刻的是sort 使用sort.Interface,因此必须在很多使用内置函数的地方调用方法。

标签: sorting go native qsort


【解决方案1】:

差异似乎很大程度上是由于您的 Quicksort 使用了内置函数。它切片并使用len。请记住,sort.Sort 包含 sort.Interface。因此,每次调用 len 时,它都会调用 slice.Len,而每次调用 array[i],array[j] = array[j],array[i] 时,它都必须调用 Swap(i,j)

我编写了一个可用于任意qsort.Interface 的类似版本:

func Qsort(a Interface, prng *rand.Rand) Interface {
    if a.Len() < 2 {
        return a
    }

    left, right := 0, a.Len()-1

    // Pick a pivot
    pivotIndex := prng.Int() % a.Len()
    // Move the pivot to the right
    a.Swap(pivotIndex, right)

    // Pile elements smaller than the pivot on the left
    for i := 0; i < a.Len(); i++ {
        if a.Less(i, right) {

            a.Swap(i, left)
            left++
        }
    }

    // Place the pivot after the last smaller element
    a.Swap(left, right)

    // Go down the rabbit hole
    leftSide, rightSide := a.Partition(left)
    Qsort(leftSide, prng)
    Qsort(rightSide, prng)

    return a
}

然后我使用了Go's benchmark functionality(您应该在可能的情况下始终将其用于基准测试)。

为了参考和透明,qsort.Interface 定义为:

type Interface interface {
    sort.Interface
    // Partition returns slice[:i] and slice[i+1:]
    // These should references the original memory
    // since this does an in-place sort
    Partition(i int) (left Interface, right Interface)
}

qsort 的实际 IntSlice 实现是:

type IntSlice []int

func (is IntSlice) Less(i, j int) bool {
    return is[i] < is[j]
}

func (is IntSlice) Swap(i, j int) {
    is[i], is[j] = is[j], is[i]
}

func (is IntSlice) Len() int {
    return len(is)
}

func (is IntSlice) Partition(i int) (left Interface, right Interface) {
    return IntSlice(is[:i]), IntSlice(is[i+1:])
}

最后,这是qsort_test.go 文件:

package qsort_test

import (
    "math/rand"
    "qsort"
    "sort"
    "testing"
    "time"
)

const size int = 1000000

var list = make([]int, size)
var prng = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))

func BenchmarkQsort(b *testing.B) {
    for n := 0; n < b.N; n++ {
        b.StopTimer()
        for i := range list {
            list[i] = prng.Int()
        }
        b.StartTimer()

        qsort.Qsort(qsort.IntSlice(list), prng)
    }
}

func BenchmarkNativeQsort(b *testing.B) {
    for n := 0; n < b.N; n++ {
        b.StopTimer()
        for i := range list {
            list[i] = prng.Int()
        }
        b.StartTimer()

        qsort.NativeQsort(list, prng)
    }
}

func BenchmarkSort(b *testing.B) {
    for n := 0; n < b.N; n++ {
        b.StopTimer()
        for i := range list {
            list[i] = prng.Int()
        }
        b.StartTimer()

        sort.Sort(sort.IntSlice(list))
    }
}

结果(格式化我的):

PASS

BenchmarkQsort             5     513629360 ns/op
BenchmarkNativeQsort       10    160609180 ns/op
BenchmarkSort              5     292416760 ns/op

如您所见,标准库的排序在随机数据方面的平均性能大大优于您的 qsort。 NativeQsort 指的是您在实际问题中发布的 qsort 函数,它的性能优于两者。与Qsort 之间唯一不同的是我将内置函数替换为qsort.Interface。因此,通用性可能是一个比另一个慢的原因。

编辑:由于排序的成本很高,样本并不多,因此这里是带有-benchtime 10s 的结果,只是为了更具代表性的结果。

BenchmarkQsort                50     524389994 ns/op
BenchmarkNativeQsort         100     161199217 ns/op
BenchmarkSort                 50     302037284 ns/op

【讨论】:

  • sort.Sort 恕我直言,它不是合并排序的变体,而是快速排序/插入排序组合。
  • @Volker 是这样吗?我记得文档说它是 Mergesort。完全有可能我弄错了。
  • @Volker 我必须离开我的摇杆。我刚刚看了源码,你是对的。我在答案中删除了有关 Mergesort 的内容。
  • 快速排序。见tip.golang.org/src/pkg/sort/sort.go?s=4433:4458#L182。稳定排序是由mergesort完成的。​​
  • golang 标准库使用 Intro 排序,这是一种基于条件从快速排序切换到堆排序的混合排序。
猜你喜欢
  • 2016-12-23
  • 2014-01-06
  • 1970-01-01
  • 2011-11-01
  • 2017-07-23
  • 2023-03-11
  • 2013-11-04
  • 2021-06-06
  • 2010-10-04
相关资源
最近更新 更多