为什么在两个通道上进行选择比在单个通道上选择的时间慢得多?

huangapple go评论95阅读模式
英文:

Why is select on two channel much slower than 2x of the time to select on single channel?

问题

这个基准测试中,只有一个通道的情况下,每次操作需要2.545纳秒:

func BenchmarkSingleSelect(b *testing.B) {
    ch := make(chan interface{}, 1)
    for i := 0; i < b.N; i++ {
        select {
        case <-ch:
        default:
        }
    }
}

但是,当有两个通道的情况下,每次操作需要35.85纳秒:

func BenchmarkMultiSelect(b *testing.B) {
    ch := make(chan interface{}, 1)
    ch2 := make(chan interface{}, 1)
    for i := 0; i < b.N; i++ {
        select {
        case <-ch:
        case <-ch2:
        default:
        }
    }
}

为什么会出现非线性的情况呢?

英文:

This benchmark with single channel only take 2.545 ns/op:

func BenchmarkSingleSelect(b *testing.B) {
	ch := make(chan interface{}, 1)
	for i := 0; i &lt; b.N; i++ {
		select {
		case &lt;-ch:
		default:
		}
	}
}

But this benchmark with two channel takes 35.85 ns/op:

func BenchmarkMultiSelect(b *testing.B) {
	ch := make(chan interface{}, 1)
	ch2 := make(chan interface{}, 1)
	for i := 0; i &lt; b.N; i++ {
		select {
		case &lt;-ch:
		case &lt;-ch2:
		default:
		}
	}
}

Why is it non-linear?

答案1

得分: 4

通常,"one"(一个)和"multiple"(多个)之间的区别是很大的。你的第一个构造可能涉及到只有在监视单个通道时才可能进行的优化。如果你添加了进一步针对3个和4个通道的基准测试,你会发现基准测试时间大致呈线性增长,但是只有一个通道是特殊的。

这实际上是一个实现细节,要了解具体情况,你必须检查/分析Go的源代码。从规范:选择语句中可以得到一些信息:

"select"语句的执行分为几个步骤:

  1. 对于语句中的所有case,接收操作的通道操作数和发送语句的通道和右侧表达式将在进入"select"语句时按照源代码顺序进行一次性评估。结果是一组要接收或发送的通道,以及要发送的相应值。在该评估中的任何副作用都将发生,无论选择了哪个(如果有)通信操作来继续。带有短变量声明或赋值的RecvStmt左侧的表达式尚未评估。

[...]

每个case的通道都会被评估,结果是一组要接收或发送的通道。由于存在default情况,编译器必须检查是否有任何通信操作准备好继续执行。在一般情况下,当涉及多个通道时,这需要一个循环和一些数据结构(例如切片或位图)来收集准备好的case。而在只有一个通道的情况下,编译器可以生成更高效的代码,不需要循环,也不需要集合来收集准备好的case,生成的代码只需检查一个通道即可。

英文:

Usually the difference between one and multiple is a big jump. Your first construct may involve optimizations that are only possible when only a single channel is monitored. You'll see this if you add further benchmarks for 3 and 4 channels involved: benchmark time will roughly go linear, but having a single channel is special.

This is really an implementation detail, for specifics, you must check / analyze Go's source code. One thing from the Spec: Select statements:

> Execution of a "select" statement proceeds in several steps:
> 1. For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
>
> [...]

Channels of each cases are evaluated, and the result is a set of channels to receive from or send to. Since there is a default case, the compiler has to check if any of the communication ops are ready to proceed. In the general case when there are multiple channels involved, this requires a loop, and some data structure (e.g. a slice or a bitmap) that gathers the ready cases. In case of a single channel, the compiler may generate a more efficient code that doesn't involve a loop, and doesn't need a set to gather the ready cases, the generated code just has to check a single channel.

huangapple
  • 本文由 发表于 2022年8月21日 23:03:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/73435595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定