Scala 和 Go 语言的 TCP 基准测试
zajin · · 3789 次点击 · · 开始浏览最近我们需要一些带有些特殊特征的负载平衡器。现成可取并不是我们想要的获取此类特征的途径。
因此我们着手调研怎样才能写出我们自己的软件负载平衡器。由于我们的大部分代码库和专业知识都基于Scala,所以基于java虚拟机来创建此平衡器是个自然之选。
另一方面,很多人,也包括在 Fortytwo的我们自己——经常但不总是——会做一些毫无根据的假设,即JAVA虚拟机比本地编译语言要慢。
由于负载平衡器常是性能极其关键的组件,因此可能一个其他的编程语言/环境会比较好些?
我们不是很乐意走入奇特的世界写C/C++,所以我们开始找寻一种折中的方法,既可以给我们带来传说中的本地代码的性能优势,同时也具有一些高级的特性,如垃圾回收以及内置的并发原语。一个立即浮现出来的这样的语言是Google的相对较新的Go语言。本机编译而且完美内置了并发构造。是不是很完美?
我们决定与最近的WebSockets和TCP发送 相似的时髦方法,在Go 和 Scala基础之上,对TCP网络栈处理能力做基准测试。
我们写了一个简单的"ping-pong"客户端和服务器分别用go语言
01
//SERVER
02
package
main
03
04
import
(
05
"net"
06
"runtime"
07
)
08
09
func
handleClient(conn net.Conn) {
10
defer
conn.Close()
11
12
var
buf [4]byte
13
for {
14
n,
err := conn.Read(buf[0:])
15
if err!=nil
{return}
16
if n>0
{
17
_,
err = conn.Write([]byte("Pong"))
18
if err!=nil
{return}
19
}
20
}
21
}
22
23
func
main() {
24
runtime.GOMAXPROCS(4)
25
26
tcpAddr,
_ := net.ResolveTCPAddr("tcp4", ":1201")
27
listener,
_ := net.ListenTCP("tcp",
tcpAddr)
28
29
for {
30
conn,
_ := listener.Accept()
31
go
handleClient(conn)
32
}
33
}
01
//CLIENT
02
package
main
03
04
import
(
05
"net"
06
"fmt"
07
"time"
08
"runtime"
09
)
10
11
func
ping(times int,
lockChan chan bool)
{
12
tcpAddr,
_ := net.ResolveTCPAddr("tcp4", "localhost:1201")
13
conn,
_ := net.DialTCP("tcp",
nil, tcpAddr)
14
15
for i:=0;
i<int(times);
i++ {
16
_,
_ = conn.Write([]byte("Ping"))
17
var
buff [4]byte
18
_,
_ = conn.Read(buff[0:])
19
}
20
lockChan<-true
21
conn.Close()
22
}
23
24
func
main() {
25
runtime.GOMAXPROCS(4)
26
27
var
totalPings int =
1000000
28
var
concurrentConnections int =
100
29
var
pingsPerConnection int =
totalPings/concurrentConnections
30
var
actualTotalPings int =
pingsPerConnection*concurrentConnections
31
32
lockChan
:= make(chan bool,
concurrentConnections)
33
34
start
:= time.Now()
35
for i:=0;
i<concurrentConnections; i++{
36
go
ping(pingsPerConnection, lockChan)
37
}
38
for i:=0;
i<int(concurrentConnections);
i++{
39
<-lockChan
40
}
41
elapsed
:= 1000000*time.Since(start).Seconds()
42
fmt.Println(elapsed/float64(actualTotalPings))
43
}
01
//SERVER
02
import java.net._
03
import scala.concurrent.ExecutionContext.Implicits.global
04
import scala.concurrent._
05
06
object main{
07
08
def handleClient(s: Socket) : Unit = {
09
val in = s.getInputStream
10
val out = s.getOutputStream
11
while(s.isConnected){
12
val buffer = Array[Byte](4)
13
in.read(buffer)
14
out.write("Pong".getBytes)
15
}
16
}
17
18
def main(args: Array[String]){
19
val server = new ServerSocket(1201)
20
while(true){
21
val s: Socket = server.accept()
22
future
{ handleClient(s) }
23
}
24
}
25
}
01
//CLIENT
02
import scala.concurrent._
03
import scala.concurrent.duration._
04
import scala.concurrent.ExecutionContext.Implicits.global
05
import java.net._
06
07
object main{
08
09
def ping(timesToPing: Int) : Unit = {
10
val socket = new Socket("localhost", 1201)
11
val out = socket.getOutputStream
12
val in = socket.getInputStream
13
for (i
<- 0 until
timesToPing) {
14
out.write("Ping".getBytes)
15
val buffer = Array[Byte](4)
16
in.read(buffer)
17
}
18
socket.close
19
}
20
21
def main(args: Array[String]){
22
var totalPings = 1000000
23
var concurrentConnections = 100
24
var pingsPerConnection : Int = totalPings/concurrentConnections
25
var actualTotalPings : Int = pingsPerConnection*concurrentConnections
26
27
val t0 = (System.currentTimeMillis()).toDouble
28
var futures = (0 until
concurrentConnections).map{_=>
29
future(ping(pingsPerConnection))
30
}
31
32
Await.result(Future.sequence(futures), 1 minutes)
33
val t1 = (System.currentTimeMillis()).toDouble
34
println(1000*(t1-t0)/actualTotalPings)
35
}
36
}
后者和 WebSockets vs. TCP benchmark一文中用到的完全一致。两者操作都很简单并有提升的空间。实际的测试代码中包括的功能性测试能处理一些连接错误,此处省略不作赘述。
客户端想服务器发出一系列持久并发的连接请求并且发送一定数量的ping(即字符串"Ping")。服务器对每个"Ping"请求做出回应并回复"Pong"。
实验是在2.7G赫兹的四核苹果笔记本上演示的。客户端和服务器分别运行,以用来更好地测试程序运行的系统开销。
客户端能生成100个并发请求并发出100万次的ping到服务器端,并通过连接平均分布ping。我们测试了全程的"ping-pong"往返时间。
令我们吃惊的是, Scala比Go好的不止一点,平均往返时间约1.6微秒 (0.0016毫秒)对比于Go的 约11微妙 (0.011毫秒).Go的数字当然仍然是相当的快,但如果所有软件在做的都是接收一个tcp包,再传递给另一个终点,这样就会在最大吞吐量方面带来很大的差异。
相反的需要注意的是,Go服务器具有的内存封装仅仅是10MB,而Scala将近200MB。
Go仍然很新,随着它的成熟可能会有性能的改进,而且它的简单的并发原语可能使付出这些性能的损失是值得的。
尽管如此,这个结果实在有点令人惊讶,至少对我们是如此。我们想在评论中听到一些关于此的想法。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
最近我们需要一些带有些特殊特征的负载平衡器。现成可取并不是我们想要的获取此类特征的途径。
因此我们着手调研怎样才能写出我们自己的软件负载平衡器。由于我们的大部分代码库和专业知识都基于Scala,所以基于java虚拟机来创建此平衡器是个自然之选。
另一方面,很多人,也包括在 Fortytwo的我们自己——经常但不总是——会做一些毫无根据的假设,即JAVA虚拟机比本地编译语言要慢。
由于负载平衡器常是性能极其关键的组件,因此可能一个其他的编程语言/环境会比较好些?
我们不是很乐意走入奇特的世界写C/C++,所以我们开始找寻一种折中的方法,既可以给我们带来传说中的本地代码的性能优势,同时也具有一些高级的特性,如垃圾回收以及内置的并发原语。一个立即浮现出来的这样的语言是Google的相对较新的Go语言。本机编译而且完美内置了并发构造。是不是很完美?
我们决定与最近的WebSockets和TCP发送 相似的时髦方法,在Go 和 Scala基础之上,对TCP网络栈处理能力做基准测试。
我们写了一个简单的"ping-pong"客户端和服务器分别用go语言
01
//SERVER
02
package
main
03
04
import
(
05
"net"
06
"runtime"
07
)
08
09
func
handleClient(conn net.Conn) {
10
defer
conn.Close()
11
12
var
buf [4]byte
13
for {
14
n,
err := conn.Read(buf[0:])
15
if err!=nil
{return}
16
if n>0
{
17
_,
err = conn.Write([]byte("Pong"))
18
if err!=nil
{return}
19
}
20
}
21
}
22
23
func
main() {
24
runtime.GOMAXPROCS(4)
25
26
tcpAddr,
_ := net.ResolveTCPAddr("tcp4", ":1201")
27
listener,
_ := net.ListenTCP("tcp",
tcpAddr)
28
29
for {
30
conn,
_ := listener.Accept()
31
go
handleClient(conn)
32
}
33
}
01
//CLIENT
02
package
main
03
04
import
(
05
"net"
06
"fmt"
07
"time"
08
"runtime"
09
)
10
11
func
ping(times int,
lockChan chan bool)
{
12
tcpAddr,
_ := net.ResolveTCPAddr("tcp4", "localhost:1201")
13
conn,
_ := net.DialTCP("tcp",
nil, tcpAddr)
14
15
for i:=0;
i<int(times);
i++ {
16
_,
_ = conn.Write([]byte("Ping"))
17
var
buff [4]byte
18
_,
_ = conn.Read(buff[0:])
19
}
20
lockChan<-true
21
conn.Close()
22
}
23
24
func
main() {
25
runtime.GOMAXPROCS(4)
26
27
var
totalPings int =
1000000
28
var
concurrentConnections int =
100
29
var
pingsPerConnection int =
totalPings/concurrentConnections
30
var
actualTotalPings int =
pingsPerConnection*concurrentConnections
31
32
lockChan
:= make(chan bool,
concurrentConnections)
33
34
start
:= time.Now()
35
for i:=0;
i<concurrentConnections; i++{
36
go
ping(pingsPerConnection, lockChan)
37
}
38
for i:=0;
i<int(concurrentConnections);
i++{
39
<-lockChan
40
}
41
elapsed
:= 1000000*time.Since(start).Seconds()
42
fmt.Println(elapsed/float64(actualTotalPings))
43
}
01
//SERVER
02
import java.net._
03
import scala.concurrent.ExecutionContext.Implicits.global
04
import scala.concurrent._
05
06
object main{
07
08
def handleClient(s: Socket) : Unit = {
09
val in = s.getInputStream
10
val out = s.getOutputStream
11
while(s.isConnected){
12
val buffer = Array[Byte](4)
13
in.read(buffer)
14
out.write("Pong".getBytes)
15
}
16
}
17
18
def main(args: Array[String]){
19
val server = new ServerSocket(1201)
20
while(true){
21
val s: Socket = server.accept()
22
future
{ handleClient(s) }
23
}
24
}
25
}
01
//CLIENT
02
import scala.concurrent._
03
import scala.concurrent.duration._
04
import scala.concurrent.ExecutionContext.Implicits.global
05
import java.net._
06
07
object main{
08
09
def ping(timesToPing: Int) : Unit = {
10
val socket = new Socket("localhost", 1201)
11
val out = socket.getOutputStream
12
val in = socket.getInputStream
13
for (i
<- 0 until
timesToPing) {
14
out.write("Ping".getBytes)
15
val buffer = Array[Byte](4)
16
in.read(buffer)
17
}
18
socket.close
19
}
20
21
def main(args: Array[String]){
22
var totalPings = 1000000
23
var concurrentConnections = 100
24
var pingsPerConnection : Int = totalPings/concurrentConnections
25
var actualTotalPings : Int = pingsPerConnection*concurrentConnections
26
27
val t0 = (System.currentTimeMillis()).toDouble
28
var futures = (0 until
concurrentConnections).map{_=>
29
future(ping(pingsPerConnection))
30
}
31
32
Await.result(Future.sequence(futures), 1 minutes)
33
val t1 = (System.currentTimeMillis()).toDouble
34
println(1000*(t1-t0)/actualTotalPings)
35
}
36
}
后者和 WebSockets vs. TCP benchmark一文中用到的完全一致。两者操作都很简单并有提升的空间。实际的测试代码中包括的功能性测试能处理一些连接错误,此处省略不作赘述。
客户端想服务器发出一系列持久并发的连接请求并且发送一定数量的ping(即字符串"Ping")。服务器对每个"Ping"请求做出回应并回复"Pong"。
实验是在2.7G赫兹的四核苹果笔记本上演示的。客户端和服务器分别运行,以用来更好地测试程序运行的系统开销。
客户端能生成100个并发请求并发出100万次的ping到服务器端,并通过连接平均分布ping。我们测试了全程的"ping-pong"往返时间。
令我们吃惊的是, Scala比Go好的不止一点,平均往返时间约1.6微秒 (0.0016毫秒)对比于Go的 约11微妙 (0.011毫秒).Go的数字当然仍然是相当的快,但如果所有软件在做的都是接收一个tcp包,再传递给另一个终点,这样就会在最大吞吐量方面带来很大的差异。
相反的需要注意的是,Go服务器具有的内存封装仅仅是10MB,而Scala将近200MB。
Go仍然很新,随着它的成熟可能会有性能的改进,而且它的简单的并发原语可能使付出这些性能的损失是值得的。
尽管如此,这个结果实在有点令人惊讶,至少对我们是如此。我们想在评论中听到一些关于此的想法。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们