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

Viewed 77

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 < b.N; i++ {
        select {
        case <-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 < b.N; i++ {
        select {
        case <-ch:
        case <-ch2:
        default:
        }
    }
}

Why is it non-linear?

1 Answers

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.

Related