优化CPU密集型Golang WebApp的方法

时间:2015-05-05 20:30:46

标签: go webserver

我有一个非常强大的玩具网络应用程序

func PerfServiceHandler(w http.ResponseWriter, req *http.Request) 
{
   start := time.Now()
   w.Header().Set("Content-Type", "application/json")

   x := 0
   for i := 0; i < 200000000; i++ {
       x = x + 1
       x = x - 1
    }
    elapsed := time.Since(start)    
    w.Write([]byte(fmt.Sprintf("Time Elapsed %s", elapsed)))
}

func main() 
{
    http.HandleFunc("/perf", PerfServiceHandler)
    http.ListenAndServe(":3000", nil)
}

上述功能需要大约120 ms才能执行。但当我用500个并发用户(siege -t30s -i -v -c500 http://localhost:3000/perf)对这个应用程序进行负载测试时得到的结果

  • 每个请求的平均响应时间2.51秒
  • 每秒交易率160.57笔交易

有人可以回答我的问题: -

  • 当我与100,200,500个并发用户一起运行时,我看到了没有。当应用程序刚刚启动时,上述应用程序使用的操作系统线程从7开始停留在35。增加no.of并发连接不会更改此数字。即使500个并发请求到达服务器,OS线程的数量仍然停留在35个OS线程上(应用程序是使用runtime.GOMAXPROCS(runtime.NumCPU())启动的)。当测试停止时,数字仍为35。
    • 有人能解释一下这种行为吗?
    • 可以吗? OS线程会以某种方式增加(来自OS或来自GOlang)?
    • 如果没有,这会改善表现吗? OS线程增加了吗?
  • 有人可以建议其他一些优化此应用的方法吗?

环境: -

Go - go1.4.1 linux/amd64
OS - Linux 3.2.0-4-amd64 #1 SMP Debian 3.2.65-1+deb7u2 x86_64 GNU/Linux
Processor - 2.6Ghz (Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz)
RAM - 64 GB

操作系统参数 -

nproc - 32
cat /proc/sys/kernel/threads-max - 1031126
ulimit -u - 515563
ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 515563
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 65536
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 515563
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited

1 个答案:

答案 0 :(得分:4)

多个goroutine可以对应一个os线程。此处描述了设计:https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit,它引用了本文:http://supertech.csail.mit.edu/papers/steal.pdf

问题:

  

即使有500个并发请求到达服务器,操作系统线程的数量仍然停留在35个OS线程上[...]有人可以解释一下这种行为吗?

由于您将GOMAXPROCS设置为CPU数量,因此一次只运行那么多goroutine。

可能有点令人困惑的一件事是goroutine并不总是在运行(有时候它们“很忙”)。例如,如果您读取文件,而操作系统正在执行该工作,则goroutine正忙,调度程序将选择另一个goroutine运行(假设有一个)。一旦文件读取完成,goroutine就会回到“runnable”goroutines列表中。

OS级别线程的创建由调度程序处理,并且系统级调用之间存在其他复杂性。 (有时候你需要一个真实的专用线程。参见:LockOSThread)但你不应该期待大量的线程。

  

可以吗? OS线程会以某种方式增加(从OS或GOlang)?

我认为使用LockOSThread可能会导致新线程的创建,但这并不重要:

  

如果没有,这会改善性能吗? OS线程的数量增加了吗?

没有。您的CPU从根本上限制了它可以同时执行的操作数量。 Goroutines工作是因为事实证明大多数操作都是以某种方式绑定IO,但如果你真的在做一些CPU限制,那么在这个问题上投入更多线程将无济于事。事实上,它可能会使情况变得更糟,因为线程之间切换涉及开销。

换句话说,Go在这里做出了正确的决定。

  

有人可以建议其他一些优化此应用的方法吗?

for i := 0; i < 200000000; i++ {
   x = x + 1
   x = x - 1
}

我认为你编写这段代码只是为了让CPU做很多工作?实际代码是什么样的?

您最好的选择是找到一种优化代码的方法,以便减少CPU时间。如果那是不可能的(它已经高度优化),那么你需要添加更多的计算机/ CPU。获得更好的计算机,或更多的计算机。

对于多台计算机,您可以将负载均衡器放在所有计算机的前面,并且可以轻松扩展。

您也可以通过将此工作从Web服务器上移除并将其移至某个后端系统来获益。考虑使用工作队列。