3 回答
TA贡献1844条经验 获得超8个赞
我修改了你的程序,如下所示:
package main
import (
"math/rand"
"runtime"
"sync"
"time"
)
var workerWG sync.WaitGroup
func worker(fibNum chan int) {
for tgt := range fibNum {
var a, b float64 = 0, 1
for i := 0; i < tgt; i++ {
a, b = a+b, a
}
}
workerWG.Done()
}
func main() {
rand.Seed(time.Now().UnixNano())
runtime.GOMAXPROCS(1) // LINE IN QUESTION
var fibNum = make(chan int)
for i := 0; i < 4; i++ {
go worker(fibNum)
workerWG.Add(1)
}
for i := 0; i < 500000; i++ {
fibNum <- rand.Intn(100000)
}
close(fibNum)
workerWG.Wait()
}
我清理了等待组的使用情况。
我改rand.Intn(1000)到rand.Intn(100000)
在我的机器上产生:
$ time go run threading.go (GOMAXPROCS=1)
real 0m20.934s
user 0m20.932s
sys 0m0.012s
$ time go run threading.go (GOMAXPROCS=8)
real 0m10.634s
user 0m44.184s
sys 0m1.928s
这意味着在您的原始代码中,执行的工作与同步(通道读/写)相比可以忽略不计。速度减慢来自于必须跨线程而不是一个线程进行同步,并且只在其间执行非常少量的工作。
本质上,与计算高达 1000 的斐波那契数相比,同步是昂贵的。这就是人们倾向于不鼓励微基准测试的原因。增加这个数字可以提供更好的视角。但更好的想法是对正在完成的实际工作进行基准测试,即包括 IO、系统调用、处理、处理、写入输出、格式化等。
编辑:作为一项实验,我将 GOMAXPROCS 设置为 8 的工人数量增加到 8,结果是:
$ time go run threading.go
real 0m4.971s
user 0m35.692s
sys 0m0.044s
TA贡献1859条经验 获得超6个赞
由于sync.WaitGroup 的原子性,您的代码正在被序列化。双方workerWG.Add(1)
并workerWG.Done()
会阻塞,直到他们能够更新原子内部计数器。
由于工作负载在 0 到 1000 次递归调用之间,单个内核的瓶颈足以将等待组计数器上的数据竞争降至最低。
在多核上,处理器花费大量时间旋转来修复等待组调用的冲突。再加上等待组计数器保留在一个核心上,您现在已经添加了核心之间的通信(占用更多周期)。
一些简化代码的提示:
对于少量的、固定数量的 goroutine,使用完整的通道(
chan struct{}
以避免分配)更便宜。使用发送通道关闭作为 goroutine 的终止信号,并让它们发出信号,表明它们已退出(等待组或通道)。然后,关闭完成通道以释放它们以供 GC 使用。
如果您需要等待组,请尽量减少对其的调用次数。这些调用必须在内部序列化,因此额外的调用会强制添加同步。
TA贡献1831条经验 获得超4个赞
您的主要计算例程worker不允许调度程序运行。手动调用调度程序,如
for i := 0; i < tgt; i++ {
a, b = a+b, a
if i%300 == 0 {
runtime.Gosched()
}
}
从一个线程切换到两个线程时,挂钟减少 30%。
这种人工微基准测试真的很难做到。
- 3 回答
- 0 关注
- 135 浏览
添加回答
举报