注意:“Go-lang并行片段比系列片段运行速度慢”处理竞争条件的问题,这个问题有另一个问题,所以imho它不是重复的。
我正在尝试找到以下情况的解释: 使用go例程运行并行快速排序会导致运行时间显着延长。
基准在代码之后:
package c9sort
import (
"time"
)
var runInParllel bool
func Quicksort(nums []int, parallel bool) ([]int, int) {
started := time.Now()
ch := make(chan int)
runInParllel = parallel
go quicksort(nums, ch)
sorted := make([]int, len(nums))
i := 0
for next := range ch {
sorted[i] = next
i++
}
return sorted, int(time.Since(started).Nanoseconds() / 1000000)
}
func quicksort(nums []int, ch chan int) {
// Choose first number as pivot
pivot := nums[0]
// Prepare secondary slices
smallerThanPivot := make([]int, 0)
largerThanPivot := make([]int, 0)
// Slice except pivot
nums = nums[1:]
// Go over slice and sort
for _, i := range nums {
switch {
case i <= pivot:
smallerThanPivot = append(smallerThanPivot, i)
case i > pivot:
largerThanPivot = append(largerThanPivot, i)
}
}
var ch1 chan int
var ch2 chan int
// Now do the same for the two slices
if len(smallerThanPivot) > 1 {
ch1 = make(chan int, len(smallerThanPivot))
if runInParllel {
go quicksort(smallerThanPivot, ch1)
} else {
quicksort(smallerThanPivot, ch1)
}
}
if len(largerThanPivot) > 1 {
ch2 = make(chan int, len(largerThanPivot))
if runInParllel {
go quicksort(largerThanPivot, ch2)
} else {
quicksort(largerThanPivot, ch2)
}
}
// Wait until the sorting finishes for the smaller slice
if len(smallerThanPivot) > 1 {
for i := range ch1 {
ch <- i
}
} else if len(smallerThanPivot) == 1 {
ch <- smallerThanPivot[0]
}
ch <- pivot
if len(largerThanPivot) > 1 {
for i := range ch2 {
ch <- i
}
} else if len(largerThanPivot) == 1 {
ch <- largerThanPivot[0]
}
close(ch)
}
随机烫发500000个整数的基准:
跑了100次
非平行平均值 - 1866ms
平行平均值 - 2437毫秒
任何解释都将不胜感激。我知道goroutines对于这种并行性可能不是最好的,但我试图理解其中的原因。
提前谢谢。
答案 0 :(得分:6)
一般的答案是线程间协调有成本,因此将任务发送到另一个goroutine只能在任务至少达到一定大小时加快速度。所以不要发送单品。
对于像quicksort这样的分而治之算法,并行化的方法可能很有趣。一般来说:当你递归时,你可以开始对阵列的一半进行排序&#34;如果它足够大,可以在goroutine中完成任务。 &#34;如果它足够大&#34;部分是如何减少协调开销。
更详细地说,看起来像:
撰写非平行qsort(data []int)
更改qsort
以选择性地接听*sync.WaitGroup
我们会致电wg
以表示已完成此操作。在每个if wg != nil { wg.Done() }
之前添加return
。
qsort
递归,让它检查它要排序的数据的一半是不是很大(比如超过500项?),
wg.Add(1); go qsort(half, wg)
qsort(half, nil)
包装qsort
以隐藏用户的并行机制:例如,让quicksort(data)
执行wg := new(sync.WaitGroup)
,执行初始wg.Add(1)
,调用{{1然后执行qsort(data, wg)
等待所有后台排序完成。
这不是一个最佳策略,因为即使在有足够的任务来保持核心忙碌之后,它也会继续分离新的goroutine。只有如何将一些任务交给后台,才能做很多调整。但重要的是,使用另一个线程仅用于大型子任务是一种并行化快速排序的方法,而不会受到协调开销的影响。
这里有一个demo with an embarrassingly slapdash quicksort(你已经在本地运行以获得时间) - 很有可能出现错误,绝对是已排序数据的二次方;关键是要实现并行分治的想法。
还有一种自下而上的策略 - 排序然后合并 - 例如,用于this parallel sort package。但是,包使用的就地合并代码很棘手。
(如果您还没有设置GOMAXPROCS,请在wg.Wait()
中使用runtime.GOMAXPROCS(runtime.NumCPU())
设置它。)
最后,look at Go's sort package。有很多代码,但它非常清楚,一旦你得到它,你就可以用真正的&#34;,充实的排序实现进行实验。
答案 1 :(得分:2)
原来这很简单。当我在新机器上时,GOMAXPROCS变量没有设置。
正如预测的那样,新基准有利于并行实施: 设置为核心数量的两倍:
Using 16 goroutines
Ran 100 times
Non parallel average - 1980
Parallel average - 1133
设置为核心数:
Using 8 goroutines
Ran 100 times
Non parallel average - 2004
Parallel average - 1197
顺便说一句,这是相当一致的。内核数量增加一倍的平均值总是好一点。
更大集合的基准(1000000):
Using 8 goroutines
Ran 100 times
Non parallel average - 3748
Parallel average - 2265
双击:
Using 16 goroutines
Ran 100 times
Non parallel average - 3817
Parallel average - 2012