如何减少 golang tcp 服务器中的 cpu 使用率?

Posted

技术标签:

【中文标题】如何减少 golang tcp 服务器中的 cpu 使用率?【英文标题】:how can I reduce cpu usage in a golang tcp server? 【发布时间】:2017-12-15 03:35:18 【问题描述】:

我尝试实现一个golang tcp服务器,发现并发对我来说是满意的,但是CPU使用率太高(并发15W+/s,但是在24核的linux机器中CPU使用率在800%左右)。同时,一个 C++ tcp 服务器的使用率只有 200% 左右,具有相似的并发性(使用 libevent)。

以下代码是golang的demo:

func main() 
    listen, err := net.Listen("tcp", "0.0.0.0:17379")
    if err != nil 
        fmt.Errorf(err.Error())
    
    go acceptClient(listen)
    var channel2 = make(chan bool)
    <-channel2


func acceptClient(listen net.Listener) 
    for 
        sock, err := listen.Accept()
        if err != nil 
            fmt.Errorf(err.Error())
        
        tcp := sock.(*net.TCPConn)
        tcp.SetNoDelay(true)
        var channel = make(chan bool, 10)
        go read(channel, sock.(*net.TCPConn))
        go write(channel, sock.(*net.TCPConn))
    


func read(channel chan bool, sock *net.TCPConn) 
    count := 0
    for 
        var buf = make([]byte, 1024)
        n, err := sock.Read(buf)
        if err != nil 
            close(channel)
            sock.CloseRead()
            return
        
        count += n
        x := count / 58
        count = count % 58
        for i := 0; i < x; i++ 
            channel <- true
        
   


func write(channel chan bool, sock *net.TCPConn) 
    buf := []byte("+OK\r\n")
    defer func() 
        sock.CloseWrite()
        recover()
    ()
    for 
        _, ok := <-channel
        if !ok 
            return
        
        _, writeError := sock.Write(buf)
        if writeError != nil 
            return
        
    

我通过 redis-benchmark 使用多客户端测试了这个 tcp 服务器:

redis-benchmark -h 10.100.45.2  -p 17379 -n 1000 -q script load "redis.call('set','aaa','aaa')"

我还通过 pprof 分析了我的 golang 代码,据说 CPU 在系统调用上花费了很多时间: enter image description here

【问题讨论】:

这可能更适合代码审查。一些想法:您使用的通道比需要的多,并且您错过了对套接字类型断言的检查 我对此毫无用处(我只能建议添加睡眠....),但很感兴趣。你介意在 ml 上发帖吗groups.google.com/forum/#!forum/golang-nuts 大部分时间花在 syscall for io 上是你所期望的。你制造了很多垃圾,也许 GC 占用了额外的时间。请注意,Go 程序中的总 CPU 使用率预计会更高,因为 Go 程序可能只是做得更多,但您应该能够获得相当接近的吞吐量。 Go toolchain includes a strong profiler. @PasserBy 是的,我为每个socket连接创建了一个通道,我这样做是为了提高socket连接的并发性,这个demo只是为了测试golang socket性能,这个例子我没有做套接字类型断言。您认为过多的频道会导致 CPU 使用率过高吗? 【参考方案1】:

我认为在这种情况下,将读写与通道并行化不会为您提供更好的性能。你应该尽量减少内存分配和系统调用(写函数可能会做很多系统调用)

你可以试试这个版本吗?

package main

import (
    "bytes"
    "fmt"
    "net"
)

func main() 
    listen, err := net.Listen("tcp", "0.0.0.0:17379")
    if err != nil 
        fmt.Errorf(err.Error())
    
    acceptClient(listen)


func acceptClient(listen net.Listener) 
    for 
        sock, err := listen.Accept()
        if err != nil 
            fmt.Errorf(err.Error())
        
        tcp := sock.(*net.TCPConn)
        tcp.SetNoDelay(true)
        go handleConn(tcp) // less go routine creation but no concurrent read/write on the same conn
    


var respPattern = []byte("+OK\r\n")

// just one goroutine per conn
func handleConn(sock *net.TCPConn) 
    count := 0
    buf := make([]byte, 4098) // Do not create a new buffer each time & increase the buff size
    defer sock.Close()

    for 
        n, err := sock.Read(buf)
        if err != nil 
            return
        
        count += n
        x := count / 58
        count = count % 58
        resp := bytes.Repeat(respPattern, x) // can be optimize
        _, writeError := sock.Write(resp) // do less syscall
        if writeError != nil 
            return
        
    

【讨论】:

以上是关于如何减少 golang tcp 服务器中的 cpu 使用率?的主要内容,如果未能解决你的问题,请参考以下文章

golang中解决tcp传输中的粘包问题

如何找出 Go 语言中的 CPU 数量?

golang协程调度模式解密

如何不从 curl-multi 返回数据并减少 CPU 使用率?

如何减少程序的CPU使用率?

PHP与Golang如何通信?