分享
  1. 首页
  2. 文章

GO的TCP性能测试,优化结果

winlinvip · · 19810 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

之前做过一次测试,没有任何优化的情况下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+ COMMAND
32573 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 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 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 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 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 4096
g++ 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 4096
g++ 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 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 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 4096
g++ 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 4096
10261 winlin 20 0 12008 1192 800 S 80.6 0.0 5:04.77 ./tcp.client 127.0.0.1 1990 64 4096
10255 winlin 20 0 12008 1192 800 R 79.9 0.0 5:05.32 ./tcp.client 127.0.0.1 1990 64 4096
10271 winlin 20 0 12008 1192 800 S 79.3 0.0 5:05.15 ./tcp.client 127.0.0.1 1990 64 4096
10258 winlin 20 0 12008 1192 800 S 78.3 0.0 5:05.45 ./tcp.client 127.0.0.1 1990 64 4096
10268 winlin 20 0 12008 1192 800 R 77.6 0.0 5:06.54 ./tcp.client 127.0.0.1 1990 64 4096
10251 winlin 20 0 12008 1188 800 R 76.6 0.0 5:03.68 ./tcp.client 127.0.0.1 1990 64 4096
10265 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:203
203	 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:130
130		return 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:327
327			n, 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 int
		n, 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 = 0
			break
		}
		if n == 0 {
			err = io.ErrUnexpectedEOF
			break
		}
	}
	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:152
152		n, 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:1228
1228		r0, _, e1 := Syscall(SYS_WRITE, uintptr(fd), uintptr(_p0), uintptr(len(p)))

代码是:
func write(fd int, p []byte) (n int, err error) {
	var _p0 unsafe.Pointer
	if 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:20
20		CALL	runtime·entersyscall(SB)

代码是:
TEXT	·Syscall(SB),NOSPLIT,0ドル-56
	CALL	runtime·entersyscall(SB)
	MOVQ	16(SP), DI
	MOVQ	24(SP), SI
	MOVQ	32(SP), DX
	MOVQ	0,ドル R10
	MOVQ	0,ドル R8
	MOVQ	0,ドル R9
	MOVQ	8(SP), AX	// syscall entry
	SYSCALL
	CMPQ	AX, 0ドルxfffffffffffff001
	JLS	ok
	MOVQ	$-1, 40(SP)	// r1
	MOVQ	0,ドル 48(SP)	// r2
	NEGQ	AX
	MOVQ	AX, 56(SP) // errno
	CALL	runtime·exitsyscall(SB)
	RET
ok:
	MOVQ	AX, 40(SP)	// r1
	MOVQ	DX, 48(SP)	// r2
	MOVQ	0,ドル 56(SP)	// errno
	CALL	runtime·exitsyscall(SB)
	RET

这个过程就结束了。

总结

GO单进程关闭tcp no delay和c/c++的write性能差不多,但是只有c/c++的writev的一半。

GO多进程能线性提升性能,在不改变业务代码的前提下,性能是c/c++单进程的数倍。


Winlin 2014年11月22日


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:winlinvip

查看原文:GO的TCP性能测试,优化结果

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
19810 次点击 ∙ 1 赞
2 回复 | 直到 2000年01月01日 00:00:00
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏