Go: 使用 GODEBUG 改善 Goroutine 的使用

用户头像
陈思敏捷
关注
发布于: 2020 年 05 月 30 日
Go: 使用GODEBUG改善Goroutine的使用

goroutine轻量的特点往往被认为是改善程序的解决方案。不幸的是,由于goroutine上下文切换消耗,goroutine的不当使用反而会降低程序的性能。

背景&测试

背景:对超过10k的数据文档进行计算

下面给出第一个待测试的算法逻辑

算法1
listings <- GetListings()
For each chunk of 1000 in listings
Start goroutine
For each listing in chunk
score <- CalculateMatching(listing, lead)
Add the score to the bulk object
Bulk insert the 1000 scores of the chunk



其中每1000个文档启动一个goroutine,基准测试如下

name time/op
LeadMatchingGenerationFor10000Matches-4 626ms ± 6%



让我们在计算中使用更多的goroutine对每个文档单独goroutine计算。修改如下

算法2
// we get all listing that are around of this kind of profile
listings <- GetListings()
For each chunk of 1000 in listings
Start goroutine
For each listing in chunk
Start goroutine
score <- CalculateMatching(listing, lead)
Add the score to the bulk object
Bulk insert the 1000 scores



基准测试如下:

name time/op
LeadMatchingGenerationFor10000Matches-4 698ms ± 4%



现在结果慢了11%,但是这是预期之中的,实际上,以上场景是纯数学计算,因此导致go调度器没有机会在新goroutine中发挥作用。



由于上下文切换而导致的Goroutine延迟

我们需要分析go调度器如何运行goroutine。我们首先分析”算法1“。我们将使用GODEBUG运行基准测试。

GODEBUG=schedtrace=1 go test ./... -run=^$ -bench=LeadMatchingGenerationFor10000Matches -benchtime=1ns



schedtrace=1 将会打印go调度器每ms的调度事件。这里是一部分trace信息

gomaxprocs=2 idleprocs=1 runqueue=0 [0 0]
gomaxprocs=2 idleprocs=1 runqueue=0 [0 0]
gomaxprocs=2 idleprocs=1 runqueue=0 [0 0]
gomaxprocs=2 idleprocs=0 runqueue=1 [0 0]
gomaxprocs=2 idleprocs=2 runqueue=0 [0 0]
gomaxprocs=2 idleprocs=2 runqueue=0 [0 0]



gomaxprocs 显示可用的处理器数量,idleprocs显示空闲处理器数量, runqueue显示全局队列中等待的goroutine数量,([0 0]) 显示了每个单独处理器本地队列中等待的goroutine数量。

我们可以发现这里goroutine的使用率不高,处理器也不繁忙,我们想知道我们是否需要增加更多的goroutine来利用这些闲置资源。让我们尝试对”算法2“进行相同的分析,并为每个文档使用单独的goroutine来计算。

gomaxprocs=2 idleprocs=0 runqueue=645 [116 186]
gomaxprocs=2 idleprocs=0 runqueue=514 [77 104]
gomaxprocs=2 idleprocs=0 runqueue=382 [57 64]
gomaxprocs=2 idleprocs=0 runqueue=124 [57 88]
gomaxprocs=2 idleprocs=0 runqueue=0 [28 17]
gomaxprocs=2 idleprocs=1 runqueue=0 [0 0]



现在我们可以发现大量goroutine在全局和本地队列中,处理器也处于繁忙状态。但是很快处理器就再次恢复空闲状态。我们可以通过tracer分析goroutine。



tracer goroutines 分析



goroutine 209 等待服务器返回



如我们所见,大多数goroutine在批量记录( bulk record)时等待服务器的响应。 这是我们应该集中精力改进并利用这一等待时间的地方。 这就是为什么我们为批量记录文档单独创建goroutines的原因。

我们现在也可以理解在计算中增加goroutine并没有产生收益,因为计算是没有等待的(不同于上面提到的批量记录时存在网络等待),系统无法让当前goroutine暂停来让另一个goroutine运行。

总结

综上在存在io等待的场景可以通过基准测试的方式增加goroutine数量来提升程序性能,纯计算场景建议配合基准测试基于或低于cpu数量来设置goroutine数量以获得最佳性能。



编译整理自  Go: Improve the Usage of Your Goroutines with GODEBUG

博客地址:https://www.chenjie.info/2398



本文首发于我的公众号:



发布于: 2020 年 05 月 30 日 阅读数: 39
用户头像

陈思敏捷

关注

多动脑不痴呆 2017.12.21 加入

gopher

评论

发布
暂无评论
Go: 使用GODEBUG改善Goroutine的使用