我有两个版本的合并排序实现。第一个是“正常”版本,第二个使用 goroutines 并行化在递归的每个步骤中对 slice 的每个子集完成的工作。
人们会假设能够并行化这项工作将使并发实现更快:如果我需要处理 slice A 和 slice B,那么同时处理它们应该比同步执行更快。
现在我假设我的理解的实现有问题,因为我的并发版本最终比同步版本慢 13-14 倍。
任何人都可以指出我所缺少的正确方向吗?
“正常”(同步实现):
// MergeSort sorts the slice s using Merge Sort Algorithm
func MergeSort(s []int) []int {
if len(s) <= 1 {
return s
}
n := len(s) / 2
var l []int
var r []int
l = MergeSort(s[:n])
r = MergeSort(s[n:])
return merge(l, r)
}
“并发”版本:
// MergeSortMulti sorts the slice s using Merge Sort Algorithm
func MergeSortMulti(s []int) []int {
if len(s) <= 1 {
return s
}
n := len(s) / 2
wg := sync.WaitGroup{}
wg.Add(2)
var l []int
var r []int
go func() {
l = MergeSortMulti(s[:n])
wg.Done()
}()
go func() {
r = MergeSortMulti(s[n:])
wg.Done()
}()
wg.Wait()
return merge(l, r)
}
两者都使用相同的merge 函数:
func merge(l, r []int) []int {
ret := make([]int, 0, len(l)+len(r))
for len(l) > 0 || len(r) > 0 {
if len(l) == 0 {
return append(ret, r...)
}
if len(r) == 0 {
return append(ret, l...)
}
if l[0] <= r[0] {
ret = append(ret, l[0])
l = l[1:]
} else {
ret = append(ret, r[0])
r = r[1:]
}
}
return ret
}
这是我的基准测试代码:
package msort
import "testing"
var a []int
func init() {
for i := 0; i < 1000000; i++ {
a = append(a, i)
}
}
func BenchmarkMergeSortMulti(b *testing.B) {
for n := 0; n < b.N; n++ {
MergeSortMulti(a)
}
}
func BenchmarkMergeSort(b *testing.B) {
for n := 0; n < b.N; n++ {
MergeSort(a)
}
}
它揭示了并发版本比普通同步版本慢很多:
BenchmarkMergeSortMulti-8 1 1711428093 ns/op
BenchmarkMergeSort-8 10 131232885 ns/op
最佳答案
这是因为您产生了大量的 goroutine,这些 goroutine 在调用 wg.Wait() 时会抢占。调度器不知道选择哪一个,它可以随机选择阻塞的直到它遇到一个最终可以返回并解锁另一个。当我限制对 MergeSortMulti 的并发调用数量时,它变得比同步版本快大约 3 倍。
这段代码并不漂亮,但它是一个证明。
// MergeSortMulti sorts the slice s using Merge Sort Algorithm
func MergeSortMulti(s []int) []int {
if len(s) <= 1 {
return s
}
n := len(s) / 2
wg := sync.WaitGroup{}
wg.Add(2)
var l []int
var r []int
const N = len(s)
const FACTOR = 8 // ugly but easy way to limit number of goroutines
go func() {
if n < N/FACTOR {
l = MergeSort(s[:n])
} else {
l = MergeSortMulti(s[:n])
}
wg.Done()
}()
go func() {
if n < N/FACTOR {
r = MergeSort(s[n:])
} else {
r = MergeSortMulti(s[n:])
}
wg.Done()
}()
wg.Wait()
return merge(l, r)
}
结果在您的机器上会有所不同,但是:
因子 = 4:
BenchmarkMergeSortMulti-8 50 33268370 ns/op
BenchmarkMergeSort-8 20 91479573 ns/op
因子 = 10000
BenchmarkMergeSortMulti-8 20 84822824 ns/op
BenchmarkMergeSort-8 20 103068609 ns/op
因子 = N/4
BenchmarkMergeSortMulti-8 3 352870855 ns/op
BenchmarkMergeSort-8 10 129107177 ns/op
奖励:您还可以使用信号量来限制 goroutine 的数量,这在我的机器上有点慢(select 用于避免死锁):
var sem = make(chan struct{}, 100)
// MergeSortMulti sorts the slice s using Merge Sort Algorithm
func MergeSortMulti(s []int) []int {
if len(s) <= 1 {
return s
}
n := len(s) / 2
wg := sync.WaitGroup{}
wg.Add(2)
var l []int
var r []int
select {
case sem <- struct{}{}:
go func() {
l = MergeSortMulti(s[:n])
<-sem
wg.Done()
}()
default:
l = MergeSort(s[:n])
wg.Done()
}
select {
case sem <- struct{}{}:
go func() {
r = MergeSortMulti(s[n:])
<-sem
wg.Done()
}()
default:
r = MergeSort(s[n:])
wg.Done()
}
wg.Wait()
return merge(l, r)
}
它产生:
BenchmarkMergeSortMulti-8 30 36741152 ns/op
BenchmarkMergeSort-8 20 90843812 ns/op
关于multithreading - 戈朗 : why using goroutines to parallelize calls ends up being slower?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41418210/
我试图完全理解Rack中并发请求处理的选项。我已经使用async_sinatra构建了一个长轮询应用程序,现在正在使用throw:async和/或Thin的--threaded标志试验裸机Rack。我对这个主题很满意,但有些事情我就是无法理解。(不,我没有将并发误认为是并行,是的,我确实理解GIL强加的限制)。Q1。我的测试表明thin--threaded(即rack.multithread=true)在单独的线程中同时运行请求(我假设使用EM),这意味着长时间运行的请求A将不阻止请求B(IO放在一边)。这意味着我的应用程序不需要任何特殊编码(例如回调)来实现并发(再次忽略阻塞数据库调
关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭3年前。Improvethisquestion告诉我如何使用Golang登录网站。下载xls文件是得到了,但是为了在Excel表格中有数据,需要登录网站。该站点位于公司的服务器上。如果你能告诉你怎么做。例如,我用来执行此操作的VBA代码。SetoFields=CreateObject("Scripting.Dictionary")WithoFields.Add"login","sdiscor".Add"password","sdiscor"EndWi
下面是我查询的代码:我有一个单维数组a当我打印a[0][0]时,我不明白为什么它返回字符a的ascii值:packagemainimport("fmt")funcmain(){a:=[3]string{"a","b","c"}fmt.Println(a[0][0])}输出:97 最佳答案 下面是如何打印ascii的代码示例a:=[3]string{"a","b","c"}for_,rune:=rangea{fmt.Println(rune)//Itwillprinta,b,c}因为你在你的代码中使用了[0][0],所以它是等价的fo
typepath[]bytefunc(ppath)ToUpper(){fori,b:=rangep{if'a'在上面(这个例子是从“TheGoBlog”复制过来的),如果ToUpper变成这样:func(ppath)ToUpper(){fori,_:=rangep{if'a'哪个会更有效率为什么?“TheGoBlog”对前一个说:“这里的ToUpper方法在forrange构造中使用两个变量来捕获索引和slice元素。这种形式的循环避免了在主体中多次写入p[i]。”什么意思? 最佳答案 前者有更多的内存操作,即在b上:它在循环的第一
我有一个用Golang编写的脚本,我不太明白。我想知道他为什么要在脚本里面写goserver.Start()?为什么不简单地编写server.Start?packagemainimport("github.com/miekg/dns""testing""time")constTEST_ADDR="127.0.0.1:9953"funcTestDNSResponse(t*testing.T){server:=NewDNSServer(&Config{dnsAddr:TEST_ADDR,})goserver.Start()//Allowsometimeforservertostarttim
关闭。这个问题需要更多focused。它目前不接受答案。想要改进这个问题?更新问题,使其只关注editingthispost的一个问题。关闭5年前。Improvethisquestion关于管理资源集合:可通过全局列表(例如HashMap)按名称访问从多个线程同时访问引用计数(Golang缺少“弱引用”;参见https://groups.google.com/forum/#!topic/golang-nuts/PYWxjT2v6ps)例子:vartheListtMap//global//inthreadA,B,CetcaThing:=theList.ref("aThing")//ife
我正在尝试在Go中做一些相对简单的事情——将字符串转换为整数,然后将其加倍:myInt,_:=strconv.Atoi(args[1])doubleArg:=myInt*2由于Atoi()返回两个参数(整数和err),我使用myInt,_:=来检索值的整数。我想将它加倍(因此是第二行)但不能在一行中完成所有操作:myInt,_:=strconv.Atoi(args[1])*2给我:multiple-valuestrconv.Atoi()insingle-valuecontext但是,根据我使用大多数其他语言的经验,必须在两行中执行此操作似乎有很多样板。这只是我必须处理的一个限制,还是有
我刚接触golang。尝试通过golang实现批量上传到Elasticsearch。我正在使用golang库->https://github.com/olivere/elastic用于与Elasticsearch通信。此外,我正在尝试一段示例代码,但出现以下错误...suresh@BLR-245:~/Desktop/tools/golang/src$goinstallgithub.com/crazyheart/elastic-bulk-upload#github.com/crazyheart/elastic-bulk-uploadgithub.com/crazyheart/elasti
我计划实现一个go-routine并有一个sync.WaitGroup同步创建的go-routine的结尾。我基本上使用go创建了一个线程.所以它是这样的:main(){varwgsync.WaitGroupfor{gomyThread(wg)wg.Add(1)}wg.wait()}myThread(wgsync.WaitGroup){deferwg.Done()}我之前曾与pthread_create合作过在某些情况下确实无法创建线程。在这种情况下,是否可能针对上述gomyThread(wg)无法启动和/或运行wg.Done()例程的其余部分是否正常运行?如果是这样,将报告什么以及如
Go的append()函数仅在给定slice的容量不足时分配新的slice数据(另请参见:https://stackoverflow.com/a/28143457/802833)。这可能会导致意外行为(至少对我这个golang新手来说):packagemainimport("fmt")funcmain(){a1:=make([][]int,3)a2:=make([][]int,3)b:=[][]int{{1,1,1},{2,2,2},{3,3,3}}common1:=make([]int,0)common2:=make([]int,0,12)//providesufficientcap