性能评测
协程评测
上下文切换
上下文切换,即当某个并发进程承载的某些内容必须保存其状态以切换到其他进程时。如果我们有太多的并发进程,上下文切换可能花费所有的 CPU 时间,并且无法完成任何实际工作。在操作系统级别,使用线程,这样做代价可能会非常高昂。操作系统线程必须保存寄存器值,查找表和内存映射等内容,才能在操作成功后切换回当前线程。然后它必须为传入线程加载相同的信息。在软件中的上下文切换代价相对小得多。在软件定义的调度程序下,运行时可以更具选择性地进行持久检索,例如如何持久化以及何时发生持续化。我们来看看操作系统线程和 goroutines 之间上下文切换的相对性能。首先,我们将利用 Linux 内置的基准测试套件来测量在同一内核的两个线程之间发送消息需要多长时间:
$ taskset -c 0 perf bench sched pipe -T
# Running 'sched/pipe' benchmark:
# Executed 1000000 pipe operations between two threads
Total time: 2.935 [sec]
2.935784 usecs/op
340624 ops/sec
这个基准测量实际上是衡量在一个线程上发送和接收消息所需的时间,所以我们将把结果分成两部分。每个上下文切换 1.467 微秒。这看起来不算太坏,但让我们先别急着下判断,再来比较下 goroutine 之间的上下文切换。
func BenchmarkContextSwitch(b *testing.B) {
var wg sync.WaitGroup
begin := make(chan struct{})
c := make(chan struct{})
var token struct{}
sender := func() {
defer wg.Done()
<-begin //1
for i := 0; i < b.N; i++ {
c <- token //2
}
}
receiver := func() {
defer wg.Done()
<-begin //1
for i := 0; i < b.N; i++ {
<-c //3
}
}
wg.Add(2)
go sender()
go receiver()
b.StartTimer() //4
close(begin) //5
wg.Wait()
}
我们运行该基准测试,指定只使用一个 CPU,以便与之前的 Linux 基准测试想比较,我们来看看结果:
$ go test -bench=. -cpu=1 /src/gos-concurrency-building-blocks/goroutines/fig-ctx-switch_test.go
| BenchmarkContextSwitch | 5000000 | 225ns/op |
| :--------------------- | :--------------------- | :------- |
| PASS | | |
| ok | command-line-arguments | 1.393s |
Links
- https://zhuanlan.zhihu.com/p/80037638 协程究竟比线程能省多少开销?
- https://mp.weixin.qq.com/s/uWP2X6iFu7BtwjIv5H55vw Goroutine 数量控制在多少合适,会影响 GC 和调度?