sinomoe / sino.moe

just a simple issue blog
http://sino.moe
0 stars 1 forks source link

golang中select default的性能陷阱 #11

Open sinomoe opened 5 years ago

sinomoe commented 5 years ago

channel是golang非常好用的特性之一,使用channel时常常会与下面的代码:

for {
  select {
    case <-ch1:
      // some codes
    case <-ch2:
      // some codes
    default:
  }
}

通过for select的方法从两个channel中读取数据,其代码本身是没问题的,但是由于加入了default却引入了性能问题,如果defalut分支中不做任何事,或者无必要逻辑,其实现效果和下面的代码别无二致,但是性能却会大打折扣。

for {
  select {
    case <-ch1:
      // some codes
    case <-ch2:
      // some codes
  }
}

下面通过benchmark来说明其具体影响,测试代码如下:

func BenchmarkSelectDefault(b *testing.B) {
    queue := make(chan struct{}, 1)
    var consumersReady, allWorkFinished sync.WaitGroup
    Nconsumer := b.N

    producer := func() {
        defer allWorkFinished.Done()
        consumersReady.Wait()
        for i := 0; i < Nconsumer; i++ {
            queue <- struct{}{}
        }
        close(queue)
    }

    consumer := func(id int) {
        defer allWorkFinished.Done()
        consumersReady.Done()
        for {
            select {
            case <-queue:
                return
            default:
            }
        }
    }

    consumersReady.Add(Nconsumer)
    allWorkFinished.Add(Nconsumer + 1)
    for i := 0; i < Nconsumer; i++ {
        go consumer(i)
    }
    go producer()
    allWorkFinished.Wait()
}

func BenchmarkSelect(b *testing.B) {
    queue := make(chan struct{}, 1)
    var consumersReady, allWorkFinished sync.WaitGroup
    Nconsumer := b.N

    producer := func() {
        defer allWorkFinished.Done()
        consumersReady.Wait()
        for i := 0; i < Nconsumer; i++ {
            queue <- struct{}{}
        }
        close(queue)
    }

    consumer := func(id int) {
        defer allWorkFinished.Done()
        consumersReady.Done()
        for {
            select {
            case <-queue:
                return
                // default:
            }
        }
    }

    consumersReady.Add(Nconsumer)
    allWorkFinished.Add(Nconsumer + 1)
    for i := 0; i < Nconsumer; i++ {
        go consumer(i)
    }
    go producer()
    allWorkFinished.Wait()
}

两个benchmark的差别仅在于select块中是否有空defalut分支,性能测试结果如下:

$ go test -bench=. -v                  
goos: darwin
goarch: amd64
BenchmarkSelect-4                1000000              2465 ns/op
BenchmarkSelectDefault-4             100          11492657 ns/op

由于defalut语句为空或者其逻辑很短,相当于引入了一个死循环占用了绝大多数CPU时间,导致性能骤降,所以在使用select时,能避免使用defalut就应尽量避免,如果必须要使用defalut`,其内部的逻辑也不应该太少,以免导不必要的致性能损失。