GO的TCP效能測試,最佳化結果

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

之前做過一次測試,沒有任何最佳化的情況下C++(16Gbps)是GO(4Gbps)的4倍效能,參考http://blog.csdn.net/win_lin/article/details/40744175

這次針對TCP部分對go做了最佳化,測試結果令人滿意。GO單進程(7Gbps)不輸c++(8Gbps),是c++使用writev(16Gbps)的一半,GO多進程(59Gbps)完勝c++是c++的好幾倍。

測試代碼參考:https://github.com/winlinvip/srs.go/tree/master/research/tcp

備忘:之前的測試是在虛擬機器上,這次在物理機上,結果可能會略有不同。

Why TCP

TCP是網路通訊的基礎,而web則是基於HTTP架構,HTTP又基於TCP。RTMP也是基於TCP。

TCP的吞吐率能達標,那麼就奠定了這個語言能開發高效能伺服器的基礎。

之前srs1.0時,調研過go,寫了一個go版本的srs,但是效能和red5差不多就刪除了。

現在srs2.0單進程單線程網路吞吐能達到4Gbps(6千用戶端,碼率522Kbps),GO如果不能達到這個目標,那麼SRS就不可能用go重寫。

Platform

此次測試在24CPU的伺服器上,CPU不是瓶頸。

測試使用lo網卡,直接記憶體拷貝,網路也不是瓶頸。

CPU和網路都資源充足時,伺服器本身的執行速度就是關鍵了。

OS選用Centos6 64bits。

用戶端選用c++做用戶端,使用同一個用戶端測試。

Write

下面是C++伺服器,單進程單線程作為伺服器:

g++ tcp.server.cpp -g -O0 -o tcp.server && ./tcp.server 1990 4096 g++ tcp.client.cpp -g -O0 -o tcp.client && ./tcp.client 127.0.0.1 1990 4096 ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   0   6  92   0   0   1|   0    11k|1073M 1073M|   0     0 |2790    81k  0   6  93   0   0   1|   0  7782B|1049M 1049M|   0     0 |2536    76k    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND32573 winlin    20   0 11744  892  756 R 99.9  0.0  17:56.88 ./tcp.server 1990 4096 2880 winlin    20   0 11740  900  764 S 85.3  0.0   0:32.53 ./tcp.client 127.0.0.1 1990 4096

單進程的c++效率還是非常高的1049MBps,對比目前SRS1的168MBps,SRS2跑到的391MBps,其實SRS沒有達到效能極限。

下面是go作為伺服器,no delay設定為1,即go預設的tcp選項,這個選項會造成tcp效能降低:

go build ./tcp.server.go && ./tcp.server 1 1 1990 4096g++ tcp.client.cpp -g -O0 -o tcp.client && ./tcp.client 127.0.0.1 1990 4096 ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   0   5  93   0   0   2|   0  7509B| 587M  587M|   0     0 |2544   141k  0   5  93   0   0   2|   0    10k| 524M  524M|   0     0 |2629   123k   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                           5496 winlin    20   0 98248 1968 1360 S 100.5  0.0   4:40.54 ./tcp.server 1 1 1990 4096       5517 winlin    20   0 11740  896  764 S 72.3  0.0   3:24.22 ./tcp.client 127.0.0.1 1990 4096 

可見在開啟tcp no delay的go只有c++的效能的一半。

下面是go作為伺服器,關閉了tcp no delay選項,單進程作為伺服器:

go build ./tcp.server.go && ./tcp.server 1 0 1990 4096g++ tcp.client.cpp -g -O0 -o tcp.client && ./tcp.client 127.0.0.1 1990 4096 ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   0   5  93   0   0   1|   0    10k| 868M  868M|   0     0 |2674    79k  1   5  93   0   0   1|   0    16k| 957M  957M|   0     0 |2660    85k   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                          3004 winlin    20   0 98248 1968 1360 R 100.2  0.0   2:27.32 ./tcp.server 1 0 1990 4096      3030 winlin    20   0 11740  900  764 R 81.0  0.0   1:59.42 ./tcp.client 127.0.0.1 1990 4096

其實在關閉了tcp no delay之後,go的效能和c++相差不大了。

Multiple CPU

go最厲害的是考慮多cpu並行計算。其實c/c++也可以用fork多進程,對於業務代碼有很大影響;go在不影響業務代碼時直接擴充支援多cpu。

go開啟10個cpu,8個用戶端,開啟no delay(預設)時的效能:

go build ./tcp.server.go && ./tcp.server 10 1 1990 4096g++ tcp.client.cpp -g -O0 -o tcp.client && for((i=0;i<8;i++)); do (./tcp.client 127.0.0.1 1990 4096 &); done----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   4  37  47   0   0  12|   0   105k|3972M 3972M|   0     0 |  14k  995k  4  37  46   0   0  13|   0  8055B|3761M 3761M|   0     0 |  14k  949k  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                           6353 winlin    20   0  517m 6896 1372 R 789.6  0.0  13:24.49 ./tcp.server 10 1 1990 4096      6384 winlin    20   0 11740  900  764 S 68.4  0.0   1:11.57 ./tcp.client 127.0.0.1 1990 4096  6386 winlin    20   0 11740  896  764 R 67.4  0.0   1:09.53 ./tcp.client 127.0.0.1 1990 4096  6390 winlin    20   0 11740  900  764 R 66.7  0.0   1:11.24 ./tcp.client 127.0.0.1 1990 4096  6382 winlin    20   0 11740  896  764 R 64.8  0.0   1:11.30 ./tcp.client 127.0.0.1 1990 4096  6388 winlin    20   0 11740  896  764 R 64.4  0.0   1:11.80 ./tcp.client 127.0.0.1 1990 4096  6380 winlin    20   0 11740  896  764 S 63.4  0.0   1:08.78 ./tcp.client 127.0.0.1 1990 4096  6396 winlin    20   0 11740  896  764 R 62.8  0.0   1:09.47 ./tcp.client 127.0.0.1 1990 4096  6393 winlin    20   0 11740  900  764 R 61.4  0.0   1:11.90 ./tcp.client 127.0.0.1 1990 4096 

也比較厲害了,能跑到30Gbps。

開啟10個cpu,8個用戶端,關閉no delay時看看go的效能:

go build ./tcp.server.go && ./tcp.server 10 0 1990 4096g++ tcp.client.cpp -g -O0 -o tcp.client && for((i=0;i<8;i++)); do (./tcp.client 127.0.0.1 1990 4096 &); done----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   5  42  41   0   0  12|   0  8602B|7132M 7132M|   0     0 |  15k  602k  5  41  41   0   0  12|   0    13k|7426M 7426M|   0     0 |  15k  651k  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                          4148 winlin    20   0  528m 9.8m 1376 R 795.5  0.1  81:48.12 ./tcp.server 10 0 1990 4096     4167 winlin    20   0 11740  896  764 S 89.8  0.0   8:16.52 ./tcp.client 127.0.0.1 1990 4096 4161 winlin    20   0 11740  900  764 R 87.8  0.0   8:14.63 ./tcp.client 127.0.0.1 1990 4096 4174 winlin    20   0 11740  896  764 S 83.2  0.0   8:09.40 ./tcp.client 127.0.0.1 1990 4096 4163 winlin    20   0 11740  896  764 R 82.6  0.0   8:07.80 ./tcp.client 127.0.0.1 1990 4096 4171 winlin    20   0 11740  900  764 R 82.2  0.0   8:08.75 ./tcp.client 127.0.0.1 1990 4096 4169 winlin    20   0 11740  900  764 S 81.9  0.0   8:15.37 ./tcp.client 127.0.0.1 1990 4096 4165 winlin    20   0 11740  900  764 R 78.9  0.0   8:09.98 ./tcp.client 127.0.0.1 1990 4096 4177 winlin    20   0 11740  900  764 R 74.0  0.0   8:07.63 ./tcp.client 127.0.0.1 1990 4096

這個更厲害了,能跑到59Gbps,厲害!

Writev

GO中是沒有writev的,可以用c/c++的writev和go的多進程比一比。

考慮SRS2目前使用writev提升了一倍效能,若srs使用go則可以使用多進程,這兩個對比還是有意義的。

同時,用戶端也使用readv來一次讀取多次,彌補用戶端單進程的瓶頸。

g++ tcp.server.writev.cpp -g -O0 -o tcp.server && ./tcp.server 64 1990 4096 g++ tcp.client.readv.cpp -g -O0 -o tcp.client && ./tcp.client 127.0.0.1 1990 64 4096 ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   0   6  93   0   0   1|   0    15k|1742M 1742M|   0     0 |2578    30k  0   6  93   0   0   1|   0    13k|1779M 1779M|   0     0 |2412    30k    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                             9468 winlin    20   0 12008 1192  800 R 99.8  0.0   1:17.63 ./tcp.server 64 1990 4096           9487 winlin    20   0 12008 1192  800 R 80.3  0.0   1:02.49 ./tcp.client 127.0.0.1 1990 64 4096

使用writev確實能提升一倍的效能,減少了系統調用的時間。

對比使用readv用戶端的go,禁用tcp no delay的情況:

go build ./tcp.server.go && ./tcp.server 1 0 1990 4096g++ tcp.client.readv.cpp -g -O0 -o tcp.client && ./tcp.client 127.0.0.1 1990 64 4096 ----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   0   5  93   0   0   1|   0  5734B| 891M  891M|   0     0 |2601   101k  0   5  93   0   0   2|   0  9830B| 897M  897M|   0     0 |2518   103k  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                             9690 winlin    20   0 98248 3984 1360 R 100.2  0.0   2:46.84 ./tcp.server 1 0 1990 4096         9698 winlin    20   0 12008 1192  800 R 79.3  0.0   2:13.23 ./tcp.client 127.0.0.1 1990 64 4096

這時候go單進程沒有提升,因為瓶頸不在用戶端,所以用戶端使用readv之後也沒有改變。

對比go用10個cpu,用戶端使用readv,禁用tcp no delay:

go build ./tcp.server.go && ./tcp.server 10 0 1990 4096g++ tcp.client.readv.cpp -g -O0 -o tcp.client && for((i=0;i<8;i++)); do (./tcp.client 127.0.0.1 1990 64 4096 &); done----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw   5  41  42   0   0  12|   0  7236B|6872M 6872M|   0     0 |  15k  780k  4  42  41   0   0  12|   0  9557B|6677M 6677M|   0     0 |  15k  723k  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                            10169 winlin    20   0  655m 7072 1388 R 799.9  0.0  51:39.13 ./tcp.server 10 0 1990 4096       10253 winlin    20   0 12008 1192  800 R 84.5  0.0   5:05.05 ./tcp.client 127.0.0.1 1990 64 409610261 winlin    20   0 12008 1192  800 S 80.6  0.0   5:04.77 ./tcp.client 127.0.0.1 1990 64 409610255 winlin    20   0 12008 1192  800 R 79.9  0.0   5:05.32 ./tcp.client 127.0.0.1 1990 64 409610271 winlin    20   0 12008 1192  800 S 79.3  0.0   5:05.15 ./tcp.client 127.0.0.1 1990 64 409610258 winlin    20   0 12008 1192  800 S 78.3  0.0   5:05.45 ./tcp.client 127.0.0.1 1990 64 409610268 winlin    20   0 12008 1192  800 R 77.6  0.0   5:06.54 ./tcp.client 127.0.0.1 1990 64 409610251 winlin    20   0 12008 1188  800 R 76.6  0.0   5:03.68 ./tcp.client 127.0.0.1 1990 64 409610265 winlin    20   0 12008 1192  800 R 74.6  0.0   5:03.35 ./tcp.client 127.0.0.1 1990 64 4096

測試結果和之前差不多。

GO Write Analysis

調試go的TcpConn.Write方法,呼叫堆疊是:

    at /home/winlin/go/src/github.com/winlinvip/srs.go/research/tcp/tcp.server.go:203203        n, err := conn.Write(b)

調用的代碼是:

func handleConnection(conn *net.TCPConn, no_delay int, packet_bytes int) {    for {        n, err := conn.Write(b)        if err != nil {            fmt.Println("write data error, n is", n, "and err is", err)            break        }    }

s調試進去,調用的是:

net.(*conn).Write (c=0xc20805bf18, b=..., ~r1=0, ~r2=...) at /usr/local/go/src/pkg/net/net.go:130130return c.fd.Write(b)

這部分的代碼是:

func (c *conn) Write(b []byte) (int, error) {if !c.ok() {return 0, syscall.EINVAL}return c.fd.Write(b)}

接下來是:

Breakpoint 2, net.(*netFD).Write (fd=0x0, p=..., nn=0, err=...) at /usr/local/go/src/pkg/net/fd_unix.go:327327n, err = syscall.Write(int(fd.sysfd), p[nn:])

這部分代碼是:

func (fd *netFD) Write(p []byte) (nn int, err error) {if err := fd.writeLock(); err != nil {return 0, err}defer fd.writeUnlock()if err := fd.pd.PrepareWrite(); err != nil {return 0, &OpError{"write", fd.net, fd.raddr, err}}for {var n intn, err = syscall.Write(int(fd.sysfd), p[nn:])if n > 0 {nn += n}if nn == len(p) {break}if err == syscall.EAGAIN {if err = fd.pd.WaitWrite(); err == nil {continue}}if err != nil {n = 0break}if n == 0 {err = io.ErrUnexpectedEOFbreak}}if err != nil {err = &OpError{"write", fd.net, fd.raddr, err}}return nn, err}

系統調用層是:

syscall.Write (fd=0, p=..., n=0, err=...) at /usr/local/go/src/pkg/syscall/syscall_unix.go:152152n, err = write(fd, p)

代碼是:

func Write(fd int, p []byte) (n int, err error) {if raceenabled {raceReleaseMerge(unsafe.Pointer(&ioSync))}n, err = write(fd, p)if raceenabled && n > 0 {raceReadRange(unsafe.Pointer(&p[0]), n)}return}

最後是:

#0  syscall.write (fd=12, p=..., n=4354699, err=...) at /usr/local/go/src/pkg/syscall/zsyscall_linux_amd64.go:12281228r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)))

代碼是:

func write(fd int, p []byte) (n int, err error) {var _p0 unsafe.Pointerif len(p) > 0 {_p0 = unsafe.Pointer(&p[0])} else {_p0 = unsafe.Pointer(&_zero)}r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)))n = int(r0)if e1 != 0 {err = e1}return}

調用的是SYS_WRITE,即write。尋找發現是有writev的:

[winlin@dev6 src]$ find . -name "*.go"|xargs grep -in "SYS_WRITEV"./pkg/syscall/zsysnum_linux_amd64.go:27:SYS_WRITEV                 = 20

可惜沒有發現使用這個的代碼,所以go肯定是沒有支援writev的了。

最後所有的Syscall,是用asm彙編寫的:

syscall.Syscall () at /usr/local/go/src/pkg/syscall/asm_linux_amd64.s:2020CALLruntime·entersyscall(SB)

代碼是:

TEXT·Syscall(SB),NOSPLIT,$0-56CALLruntime·entersyscall(SB)MOVQ16(SP), DIMOVQ24(SP), SIMOVQ32(SP), DXMOVQ$0, R10MOVQ$0, R8MOVQ$0, R9MOVQ8(SP), AX// syscall entrySYSCALLCMPQAX, $0xfffffffffffff001JLSokMOVQ$-1, 40(SP)// r1MOVQ$0, 48(SP)// r2NEGQAXMOVQAX, 56(SP)  // errnoCALLruntime·exitsyscall(SB)RETok:MOVQAX, 40(SP)// r1MOVQDX, 48(SP)// r2MOVQ$0, 56(SP)// errnoCALLruntime·exitsyscall(SB)RET

這個過程就結束了。

總結

GO單進程關閉tcp no delay和c/c++的write效能差不多,但是只有c/c++的writev的一半。

GO多進程能線性提升效能,在不改變業務代碼的前提下,效能是c/c++單進程的數倍。


Winlin 2014.11.22

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.