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

问题描述 投票:4回答:1

我尝试实现一个golang tcp服务器,我发现并发性对我来说很满意,但CPU使用率太高(并发性为15W + / s,但24核linux机器的CPU使用率约为800%)。与此同时,C ++ tcp服务器只有大约200%的使用率和类似的并发性(使用libevent)。

以下代码是golang的演示:

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
        }
    }
}

我用red-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

c++ go tcp socket.io libevent
1个回答
0
投票

我不认为并行读取和写入通道将在这种情况下为您提供更好的性能。您应该尝试减少内存分配和减少系统调用(写入函数可能会执行很多系统调用)

你能试试这个版本吗?

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
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.