【发布时间】:2021-05-07 09:58:39
【问题描述】:
我在Uber's style guide 上读到,最多应该使用 1 的通道长度。
虽然我很清楚使用 100 或 1000 的通道大小是非常糟糕的做法,但 我想知道为什么 10 的通道大小不被视为有效选项。我遗漏了一些部分来得出正确的结论。
下面,您可以按照我的一些基准测试支持的论点(和反论点)。
我了解,如果您负责从该通道写入或读取的两个 go-routines 在顺序写入或读取到/从通道之间被某些其他 IO 操作中断,则不会预期任何收益更高的通道缓冲区,我同意 1 是最佳选择。
但是,可以说除了由通道写入/读取引起的隐式锁定和解锁之外,不需要其他重要的 go-routine 切换。然后我会得出以下结论:
考虑在通道缓冲区大小为 1 和 10(GR = go-routine)的通道上处理 100 个值时上下文切换的数量
- Buffer=1:(GR1 插入 1 个值,GR2 读取 1 个值)X 100 ~ 200 个 go-routine 切换
- Buffer=10:(GR1 插入 10 个值,GR2 读取 10 个值)X 10 ~ 20 个 go-routine 切换
我做了一些基准测试来证明这实际上更快:
package main
import (
"testing"
)
type a struct {
b [100]int64
}
func BenchmarkBuffer1(b *testing.B) {
count := 0
c := make(chan a, 1)
go func() {
for i := 0; i < b.N; i++ {
c <- a{}
}
close(c)
}()
for v := range c {
for i := range v.b {
count += i
}
}
}
func BenchmarkBuffer10(b *testing.B) {
count := 0
c := make(chan a, 10)
go func() {
for i := 0; i < b.N; i++ {
c <- a{}
}
close(c)
}()
for v := range c {
for i := range v.b {
count += i
}
}
}
简单读写+非阻塞处理对比结果:
BenchmarkBuffer1-12 5072902 266 ns/op
BenchmarkBuffer10-12 6029602 179 ns/op
PASS
BenchmarkBuffer1-12 5228782 256 ns/op
BenchmarkBuffer10-12 5392410 216 ns/op
PASS
BenchmarkBuffer1-12 4806208 287 ns/op
BenchmarkBuffer10-12 4637842 233 ns/op
PASS
但是,如果我每 10 次读取添加一次睡眠,则不会产生更好的结果。
import (
"testing"
"time"
)
func BenchmarkBuffer1WithSleep(b *testing.B) {
count := 0
c := make(chan int, 1)
go func() {
for i := 0; i < b.N; i++ {
c <- i
}
close(c)
}()
for a := range c {
count++
if count%10 == 0 {
time.Sleep(time.Duration(a) * time.Nanosecond)
}
}
}
func BenchmarkBuffer10WithSleep(b *testing.B) {
count := 0
c := make(chan int, 10)
go func() {
for i := 0; i < b.N; i++ {
c <- i
}
close(c)
}()
for a := range c {
count++
if count%10 == 0 {
time.Sleep(time.Duration(a) * time.Nanosecond)
}
}
}
每 10 次读取添加睡眠时的结果:
BenchmarkBuffer1WithSleep-12 856886 53219 ns/op
BenchmarkBuffer10WithSleep-12 929113 56939 ns/op
仅供参考:我也只用一个 CPU 再次进行了测试,得到了以下结果:
BenchmarkBuffer1 5831193 207 ns/op
BenchmarkBuffer10 6226983 180 ns/op
BenchmarkBuffer1WithSleep 556635 35510 ns/op
BenchmarkBuffer10WithSleep 984472 61434 ns/op
【问题讨论】:
-
一个使用大通道缓冲区有问题的例子:你不想在通道上发送阻塞,所以你给它一个足够大的缓冲区,这样它“不应该”得到填满了。
-
大小为 10 的通道是一个完全有效的选项——对于某些操作。事实上,它是某些操作的唯一选择。
标签: performance go optimization benchmarking channels