自 Go 1.5开始, Go的GOMAXPROCS
默认值已经设置为 CPU的核数, 这允许我们的Go程序充分使用机器的每一个CPU,最大程度的提高我们程序的并发性能, 而且,在大部分情况下, 我们并不会去设置这个参数。因为默认值已经足够好了, 以至于fasthttp的作者valyala提议禁止runtime.GOMAXPROCS
设置这个数值, 对于所有的case, GOMAXPROCS
默认值是否是最好的值呢?
badger的作者Manish Rai Jain就遇到了这样一个问题。
Manish Rai Jain 写了一段Go代码, 用来测试Go的读写SSD的性能,看看是否能打到Linux的I/O测试工具fio的读写性能。 使用fio,可以在 AWS(Amazon i3.large instance with NVMe SSD)达到100K IOPS, 但是使用这个Go程序,怎么也无法接近这个IOPS。
他尝试了三个case:
- 单个goroutine随机读
- 使用一定数量的goroutine随机读
- 类似#2,但是使用一个channel
明显#1, #3比不上#2的性能,但是#2怎么也达不到fio的吞吐率。如果大家都使用小于CPU的核数, Go和Fio的吞吐率接近,但是如果把goroutine设置为大于CPU的核数,Fio性能提升明显,直到达到最大的IOPS,但是Go程序却没有显著变化。
|
|
通过将GOMAXPROCS
设置更大的数(64/128, 数倍CPU核数), Go 程序可以取得几乎和Fio一样的吞吐率。
在今年的Gophercon上,Manish Rai Jain碰到了Russ Cox,问到了这个问题:
If File::Read blocks goroutines, which then spawn new OS threads, in a long running job, there should be plenty of OS threads created already, so the random read throughput should increase over time and stabilize to the maximum possible value. But, that's not what I see in my benchmarks.
大意是如果文件读取倍 block, Go会产生新的OS Thread,因为有很多的OS Thread,所以随机读的吞吐率应该也会上升才对,但是实际却不是。
Russ Cox解释说GOMAXPROCS
就像一个多路复用器,所以GOMAXPROCS
就会是一个瓶颈。
The GOMAXPROCS in a way acts like a multiplexer. From docs, "the GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously." Which basically means, all reads must first be run only via GOMAXPROCS number of goroutines, before switching over to some OS thread (not really a switch, but conceptually speaking). This introduces a bottleneck for throughput.
最后Manish Rai Jain在他的高性能的K/V数据库调大了这个参数。
这是一个有趣的讨论,很明显默认的GOMAXPROCS
在某些情况下也不是最好的值,特别是在Manish Rai Jain这种写block的情况下,设置GOMAXPROCS
更大一些会提高I/O的吞吐率。