Scala 和 Go 语言的 TCP 基准测试

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

最近我们需要一些带有些特殊特征的负载平衡器。现成可取并不是我们想要的获取此类特征的途径。

因此我们着手调研怎样才能写出我们自己的软件负载平衡器。由于我们的大部分代码库和专业知识都基于Scala,所以基于java虚拟机来创建此平衡器是个自然之选。

另一方面,很多人,也包括在 Fortytwo的我们自己——经常但不总是——会做一些毫无根据的假设,即JAVA虚拟机比本地编译语言要慢。

由于负载平衡器常是性能极其关键的组件,因此可能一个其他的编程语言/环境会比较好些?

我们不是很乐意走入奇特的世界写C/C++,所以我们开始找寻一种折中的方法,既可以给我们带来传说中的本地代码的性能优势,同时也具有一些高级的特性,如垃圾回收以及内置的并发原语。一个立即浮现出来的这样的语言是Google的相对较新的Go语言。本机编译而且完美内置了并发构造。是不是很完美?

Go vs Scala

我们决定与最近的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 }
和Scala语言
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仍然很新,随着它的成熟可能会有性能的改进,而且它的简单的并发原语可能使付出这些性能的损失是值得的。
尽管如此,这个结果实在有点令人惊讶,至少对我们是如此。我们想在评论中听到一些关于此的想法。


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

本文来自:CSDN博客

感谢作者:zajin

查看原文:Scala 和 Go 语言的 TCP 基准测试

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

2861 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传