# go 协程实现快排 V2
上一期:https://blog.twelveeee.top/2023/Go/go goroutine quicksort/
使用了 sync.WaitGroup
对 p 的左右两边进行有序的排序
测试出来 使用协程进行排序的效果差于单线程。
现在重新优化了几个版本。
- 每次递归之后,到最后对于只有几个元素的情况下,再开辟协程进行快排,会大量的创建资源和调用 Goroutine,资源消耗主要不是在排序,而是在创建 Goroutine。所以只有几个元素的情况,会直接使用单线程进行排序。
- 使用
sync.WaitGroup
的 进行阻塞,在这种情况下不如使用 chan
进行阻塞 - 测试机器性能有问题,之前在 4 核的机器上进行测试,goroutine 开启数量只有两个,后续换到本地电脑,开启了 20 个 goroutine ,和单线程模式进行比较,效率明显提升了不少。
# 代码
# 单线程
| package quicksort |
| |
| type v1 struct{} |
| |
| func QuickSortV1(list []int) []int { |
| if len(list) <= 1 { |
| return list |
| } |
| |
| v1{}.quicksort(list, 0, len(list)-1) |
| return list |
| } |
| |
| func (v v1) quicksort(list []int, low, high int) { |
| if low >= high { |
| return |
| } |
| |
| p := v.partion(list, low, high) |
| |
| v.quicksort(list, low, p-1) |
| v.quicksort(list, p+1, high) |
| } |
| |
| func (v1) partion(list []int, low, high int) int { |
| p := list[low] |
| |
| |
| for low < high { |
| |
| |
| for low < high && p <= list[high] { |
| high-- |
| } |
| list[low] = list[high] |
| |
| |
| |
| for low < high && p >= list[low] { |
| low++ |
| } |
| list[high] = list[low] |
| |
| } |
| |
| |
| list[low] = p |
| |
| |
| return low |
| } |
# 使用 WaitGroup
| package quicksort |
| |
| import "sync" |
| |
| type V2 struct { |
| minLen int |
| } |
| |
| |
| func QuickSortV2(list []int, minLen int) []int { |
| if len(list) <= 1 { |
| return list |
| } |
| v := V2{minLen} |
| v.quicksort(list) |
| return list |
| } |
| |
| func (v V2) quicksort(list []int) { |
| |
| low := 0 |
| high := len(list) - 1 |
| |
| if low >= high { |
| return |
| } |
| |
| p := v.partion(list) |
| |
| if len(list) < v.minLen { |
| v.quicksort(list[:p]) |
| v.quicksort(list[p+1:]) |
| return |
| } |
| wg := sync.WaitGroup{} |
| |
| wg.Add(2) |
| go func() { |
| v.quicksort(list[:p]) |
| wg.Done() |
| }() |
| |
| go func() { |
| v.quicksort(list[p+1:]) |
| wg.Done() |
| }() |
| |
| wg.Wait() |
| } |
| |
| func (v V2) partion(list []int) int { |
| low := 0 |
| high := len(list) - 1 |
| |
| p := list[low] |
| |
| |
| for low < high { |
| |
| for low < high && p <= list[high] { |
| high-- |
| } |
| list[low] = list[high] |
| |
| for low < high && p >= list[low] { |
| low++ |
| } |
| list[high] = list[low] |
| } |
| |
| list[low] = p |
| |
| return low |
| } |
# 使用 channel
| package quicksort |
| |
| type V4 struct { |
| minLen int |
| } |
| |
| |
| func QuickSortV4(list []int, minLen int) []int { |
| if len(list) <= 1 { |
| return list |
| } |
| v := V4{minLen} |
| v.quicksort(list) |
| return list |
| } |
| |
| func (v V4) quicksort(list []int) { |
| |
| low := 0 |
| high := len(list) - 1 |
| |
| if low >= high { |
| return |
| } |
| |
| p := v.partion(list) |
| |
| if len(list) < v.minLen { |
| v.quicksort(list[:p]) |
| v.quicksort(list[p+1:]) |
| return |
| } |
| |
| chanReceive := make(chan bool) |
| go func() { |
| v.quicksort(list[:p]) |
| chanReceive <- true |
| }() |
| |
| go func() { |
| v.quicksort(list[p+1:]) |
| chanReceive <- true |
| }() |
| |
| <-chanReceive |
| <-chanReceive |
| } |
| |
| func (v V4) partion(list []int) int { |
| low := 0 |
| high := len(list) - 1 |
| |
| p := list[low] |
| |
| |
| for low < high { |
| |
| for low < high && p <= list[high] { |
| high-- |
| } |
| list[low] = list[high] |
| |
| for low < high && p >= list[low] { |
| low++ |
| } |
| list[high] = list[low] |
| } |
| |
| list[low] = p |
| |
| return low |
| } |
# 测试
| func TestQuickSortV4_2(t *testing.T) { |
| listLen := 1000000 |
| |
| var startTime, endTime time.Time |
| var list []int |
| |
| list = rand.Perm(listLen) |
| startTime = time.Now() |
| list = QuickSortV1(list) |
| endTime = time.Now() |
| |
| fmt.Println("V1 耗时", endTime.Sub(startTime)) |
| fmt.Println("V1 是否有序", sort.IntsAreSorted(list)) |
| time.Sleep(20 * time.Millisecond) |
| |
| list = rand.Perm(listLen) |
| startTime = time.Now() |
| list = QuickSortV2(list, listLen/100) |
| endTime = time.Now() |
| fmt.Println("V2 耗时", endTime.Sub(startTime)) |
| fmt.Println("V2 是否有序", sort.IntsAreSorted(list)) |
| time.Sleep(20 * time.Millisecond) |
| |
| list = rand.Perm(listLen) |
| startTime = time.Now() |
| list = QuickSortV4(list, 0) |
| endTime = time.Now() |
| fmt.Println("V4 所有分支都走协程", endTime.Sub(startTime)) |
| fmt.Println("V4 所有分支都走协程", sort.IntsAreSorted(list)) |
| time.Sleep(20 * time.Millisecond) |
| |
| list = rand.Perm(listLen) |
| startTime = time.Now() |
| list = QuickSortV4(list, listLen/100) |
| endTime = time.Now() |
| fmt.Println("V4 耗时", endTime.Sub(startTime)) |
| fmt.Println("V4 是否有序", sort.IntsAreSorted(list)) |
| time.Sleep(20 * time.Millisecond) |
| } |
| $ go test |
| === RUN TestQuickSortV4_2 |
| V1 耗时 68.2698ms |
| V1 是否有序 true |
| V2 耗时 15.4163ms |
| V2 是否有序 true |
| V4 所有分支都走协程 225.362ms |
| V4 所有分支都走协程 true |
| V4 耗时 10.517ms |
| V4 是否有序 true |
| --- PASS: TestQuickSortV4_2 (0.50s) |
| PASS |
# 性能测试
| func BenchmarkQuickSortV1(b *testing.B) { |
| minLen := 1000000 |
| for i := 0; i < b.N; i++ { |
| b.StopTimer() |
| list := rand.Perm(minLen) |
| b.StartTimer() |
| list = QuickSortV1(list) |
| } |
| } |
| |
| func BenchmarkQuickSortV2(b *testing.B) { |
| minLen := 1000000 |
| for i := 0; i < b.N; i++ { |
| b.StopTimer() |
| list := rand.Perm(minLen) |
| b.StartTimer() |
| list = QuickSortV2(list, 100) |
| } |
| } |
| |
| func BenchmarkQuickSortV4(b *testing.B) { |
| minLen := 1000000 |
| for i := 0; i < b.N; i++ { |
| b.StopTimer() |
| list := rand.Perm(minLen) |
| b.StartTimer() |
| list = QuickSortV4(list, 100) |
| } |
| } |
# 切片长度一百万
i7-12700 20 线程
| $ go test -bench . |
| goos: windows |
| goarch: amd64 |
| pkg: quickSort/quickSort |
| cpu: 12th Gen Intel(R) Core(TM) i7-12700 |
| BenchmarkQuickSortV1-20 18 63610539 ns/op |
| BenchmarkQuickSortV2-20 102 11763008 ns/op |
| BenchmarkQuickSortV4-20 100 11560688 ns/op |
| PASS |
| ok quickSort/quickSort 10.140s |
N100 4 线程
| $ go test -bench . |
| goos: linux |
| goarch: amd64 |
| pkg: twelveeee.top/leetcode/v2/quickSort |
| cpu: Intel(R) N100 |
| BenchmarkQuickSortV1-4 12 96833933 ns/op |
| BenchmarkQuickSortV2-4 28 42158544 ns/op |
| BenchmarkQuickSortV4-4 27 43368713 ns/op |
| PASS |
| ok twelveeee.top/leetcode/v2/quickSort 6.194s |
# 切片长度十万
i7-12700 20 线程
| $ go test -bench . |
| goos: windows |
| goarch: amd64 |
| pkg: quickSort/quickSort |
| cpu: 12th Gen Intel(R) Core(TM) i7-12700 |
| BenchmarkQuickSortV1-20 222 5239432 ns/op |
| BenchmarkQuickSortV2-20 901 1282839 ns/op |
| BenchmarkQuickSortV4-20 860 1405047 ns/op |
| PASS |
| ok quickSort/quickSort 7.900s |
N100 4 线程
| $ go test -bench . |
| goos: linux |
| goarch: amd64 |
| pkg: twelveeee.top/leetcode/v2/quickSort |
| cpu: Intel(R) N100 |
| BenchmarkQuickSortV1-4 150 8074418 ns/op |
| BenchmarkQuickSortV2-4 201 5572431 ns/op |
| BenchmarkQuickSortV4-4 196 5903751 ns/op |
| PASS |
| ok twelveeee.top/leetcode/v2/quickSort 7.424s |
# 切片长度一万
i7-12700 20 线程
| $ go test -bench . |
| goos: windows |
| goarch: amd64 |
| pkg: quickSort/quickSort |
| cpu: 12th Gen Intel(R) Core(TM) i7-12700 |
| BenchmarkQuickSortV1-20 2738 439358 ns/op |
| BenchmarkQuickSortV2-20 6169 177758 ns/op |
| BenchmarkQuickSortV4-20 6097 187862 ns/op |
| PASS |
| ok quickSort/quickSort 6.318s |
N100 4 线程
| $ go test -bench . |
| goos: linux |
| goarch: amd64 |
| pkg: twelveeee.top/leetcode/v2/quickSort |
| cpu: Intel(R) N100 |
| BenchmarkQuickSortV1-4 1761 663315 ns/op |
| BenchmarkQuickSortV2-4 2185 569286 ns/op |
| BenchmarkQuickSortV4-4 1965 579525 ns/op |
| PASS |
| ok twelveeee.top/leetcode/v2/quickSort 6.136s |
明显看到,在机器负载不高,跑大量数据的时候,启用了协程的速度会大于不启用协程的快排。使用 sync.WaitGroup
还是 chan
进行阻塞影响因素会相对小点