【问题标题】:performance of golang select statement in a for loopgolang select语句在for循环中的表现
【发布时间】:2017-02-06 03:03:46
【问题描述】:

我做个测试看看select的表现,发现结果不是 好的。 go 版本是 1.7.3

package main

import (
    "fmt"
    "log"
    "os"
    "runtime/pprof"
    "time"
)

var serverDone = make(chan struct{})
var serverDone1 = make(chan struct{})
var serverDone2 = make(chan struct{})
var serverDone3 = make(chan struct{})
var serverDone4 = make(chan struct{})
var serverDone5 = make(chan struct{})

func main() {
    f, err := os.Create("cpu.pprof")
    if err != nil {
        log.Fatal(err)
    }
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    for i := 0; i < 1000; i++ {
        go messageLoop()
    }
    <-time.After(10 * time.Second)
    close(serverDone)
    fmt.Println("finished")
}

func messageLoop() {
    var ticker = time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()
    var counter = 0
    for {
        select {
        case <-serverDone:
            return
        case <-serverDone1:
            return
        // case <-serverDone2:
        //  return
        // case <-serverDone3:
        //  return
        // case <-serverDone4:
        //  return
        // case <-serverDone5:
        //  return
        case <-ticker.C:
            counter += 1
        }
    }
}

运行上述代码时,每次添加 serverDone 案例时,您会发现 CPU 会上升(在我的书中,大约 5%)。
当所有 serverDone 的情况都被移除时,CPU 大约是 5%,这并不好。
如果我将全局锁定的对象(如 serverDone)转到本地,性能会更好,但仍然不够好。

谁知道我的情况有什么问题,或者select语句的正确用法是什么?

【问题讨论】:

  • 无法在我的 Linux 环境中重现,也许你应该编写你的操作系统。
  • @ymonad 你的 cpu 使用率是多少,你使用多少 serverDone 案例。我在一个centeros docker上测试过,结果一样
  • 也许您可以详细说明困扰您的问题。您正在跨越 1000 个 goroutine,它们在 1 到 5 个通道上进行忙碌等待。您预计会发生什么,什么“性能”会是“好”,为什么?
  • @fwang2002 背后的原因是渠道实现。查看下面的答案
  • @Volker 因为 goroutine 切换,或者等待频道花费过多的 CPU 时间。 messageLoop 的典型用途是在网络处理程序中,它将创建一个包含 messageLoop 的对象实例,一个用于等待连接完成的通道,一个用于等待服务器关闭的通道,一个用于网络的通道消息,也许还有一个特定计时器的通道。如果最多 4 个通道,比在我的电脑中,它可能会花费 20% 的 CPU。而 1000 个 go 例程在网络应用程序中很常见。

标签: go


【解决方案1】:

简答:频道使用互斥锁。更多频道意味着更多futex系统调用

这里是关于程序的 strace。

有 7 个 select 语句等待 7 个 channels 的代码

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 98.20    0.424434          13     33665      6061 futex
  1.09    0.004731          10       466           sched_yield
  0.47    0.002038          30        67           select
  0.11    0.000484           4       114           rt_sigaction
  0.05    0.000203           5        41         8 rt_sigreturn
  0.03    0.000128           9        15           mmap
  0.02    0.000081          27         3           clone
  0.01    0.000052           7         8           rt_sigprocmask
  0.01    0.000032          32         1           openat
  0.00    0.000011           4         3           setitimer
  0.00    0.000009           5         2           sigaltstack
  0.00    0.000008           8         1           munmap
  0.00    0.000006           6         1           execve
  0.00    0.000006           6         1           sched_getaffinity
  0.00    0.000004           4         1           arch_prctl
  0.00    0.000004           4         1           gettid
  0.00    0.000000           0         2         2 restart_syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.432231                 34392      6071 total

带有 3 个 select 语句的代码等待 3 个 channels

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 90.47    0.118614          11     10384      1333 futex
  6.64    0.008704          11       791           sched_yield
  2.06    0.002706          23       120           select
  0.39    0.000512           4       114           rt_sigaction
  0.14    0.000181           8        22         2 rt_sigreturn
  0.10    0.000131           9        15           mmap
  0.05    0.000060          60         1           openat
  0.04    0.000057          19         3           setitimer
  0.04    0.000051          17         3           clone
  0.03    0.000045           6         8           rt_sigprocmask
  0.01    0.000009           9         1           execve
  0.01    0.000009           5         2           sigaltstack
  0.01    0.000009           9         1           sched_getaffinity
  0.01    0.000008           8         1           munmap
  0.01    0.000007           7         1           arch_prctl
  0.00    0.000005           5         1           gettid
------ ----------- ----------- --------- --------- ----------------
100.00    0.131108                 11468      1335 total

很明显,futex 调用的数量与通道数量成正比,而 futex 系统调用是这种性能的原因。

这里是解释

您可以在以下文件 src/runtime/chan.go 中找到通道实现。

这是hchan 频道的结构

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters
    lock     mutex
}

在 runtime2.go 中定义了一个 Lock 嵌入式结构,根据操作系统用作互斥锁 (futex) 或信号量。

所以随着频道数量的增加,会有更多的futex system call 呼叫出现,这会影响性能

您可以阅读更多关于这些的内容:futex(2),Channels in steroids

【讨论】:

  • 非常感谢。也许我必须非常小心地使用频道!
猜你喜欢
  • 2019-08-31
  • 2019-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 2016-08-29
  • 2011-08-17
  • 2014-08-28
相关资源
最近更新 更多