为啥 Go 语言的性能还不如java

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为啥 Go 语言的性能还不如java相关的知识,希望对你有一定的参考价值。

参考技术A Go语言自亮相以来并没有展示一个明确的方向,Google员工将Go语言称为一个“试验性语言”,称其试图融合Python等动态语言的开发速度和C或C++等编译语言的性能和安全。一位Go语言的支持者概括而言Go语言如下:简单、快速、安全、并发、快乐编程、开源;但Go语言缺乏方向以及其“集大成者”的尝试很容易会导致其学猫不成学狗也不成,沦为四不像。尽管如此,编者仍然觉得Go语言有相当大的潜力:很多开发者对它感兴趣——不仅它的最初设计者阵容强大,而且在参与修改源代码的人群中也不乏大牛级人物。这很有可能帮助Go语言找到适合自己的方向,开拓系统编程的新方向。 参考技术B GO语言
Go(又称Golang)是Google的Robert Griesemer,Rob Pike及Ken
Thompson开发的一种静态强类型、编译型语言。Go语言语法与C相近,但功能上有:内存安全,GC(垃圾回收),结构形态及CSP-style并发计算。
Java语言
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。
Go语言和Java有什么区别?
1、Go上不允许函数重载,必须具有方法和函数的唯一名称,而Java允许函数重载。
2、在速度方面,Go的速度要比Java快。
3、Java默认允许多态,而Go没有。
4、Go语言使用HTTP协议进行路由配置,而Java使用Akka.routing.ConsistentHashingRouter和Akka.routing.ScatterGatherFirstCompletedRouter进行路由配置。
5、Go代码可以自动扩展到多个核心,而Java并不总是具有足够的可扩展性。
6、Go语言的继承通过匿名组合完成,基类以Struct的方式定义,子类只需要把基类作为成员放在子类的定义中,支持多继承;而Java的继承通过extends关键字完成,不支持多继承。
参考技术C 你哪来的信心这么说?列一下理由了,看我是否能反驳你。 参考技术D 是什么给了你java不如Go语言的错觉

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?

点击上方蓝色“ Go语言中文网 ”关注, 每天一起学 Go

在知乎上看到一个问答:为什么 Go 的 Web 框架速度还不如 Java?链接:https://www.zhihu.com/question/360929863 。

提问者由此问题的根据来自:https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=json,不过提问时说:Go 不是编译型语言吗,速度怎么还不如 Java?显然这个说法有问题。

截个图看看这个网站的跑分:

这里主要整理两个回答。

北南的回答

首先呢,Java 也是编译型的语言。Java 的编译分为两个步骤,第一步是从源代码到 bytecode,也就是.class 文件。我们日常看到的 java jar 包,其实就是 bytecode 的分发形式。第二步是在运行时,java 虚拟机会和 jit 合作,把 bytecode 在需要的时候编译成 native code。所以说呢,你这个链接里的跑分比较,其实大家跑的都是编译后的 native code。

其次,web 框架不单单要看中对语言的执行效率,还要看 IO 的效率。当然这也没什么花头,java 的很多框架和 go 的异步 IO 如出一辙, 说白了还是借助 linux 操作系统中的 epoll 等来减少进程堵塞。于是很多跑分中,语言的执行效率变得次要,在充分优化和合理使用 nonblocking IO 的情况下,一些解释型语言也能取得很好的成绩。

最后,也是最重要的,其实你看这些跑分,框架的性能都好的不行不行的,远远超过一般应用的需要。其实在实际应用中,我们的业务逻辑会更复杂,上下游服务也会更多,其实就算是好多人瞧不起的增删改查操作,并发大了也极容易产生性能瓶颈。换句话说,编程语言和框架本身是不容易成为性能瓶颈的。所以,我建议应该更多的从开发难易程度,以及以后项目长期维护的成本上来选择语言和框架,而不是看跑分。

阿里巴巴淘系技术的回答

该回复分析了这个跑分。

各种框架的应用场景不同导致其优化侧重点不同,下面我们展开详细分析。

http server 概述

首先描述一下一个简单的 web server 的请求处理过程:

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?

Net 层读取数据包后经过 HTTP Decoder 解析协议,再由 Route 找到对应的 Handler 回调,处理业务逻辑后设置相应 Response 的状态码等,然后由 HTTP Encoder 编码相应的 Response,最后由 Net 写出数据。

而 Net 之下的一层由内核控制,虽然也有很多优化策略,但这里主要比较 web 框架本身,那么暂时不考虑 Net 之下的优化。

看了下 techempower 提供的压测框架源码[1],各类框架基本上都是基于 epoll 的处理,那么各类框架的性能差距主要体现在上述这些模块的性能了。

关于各类压测的简述

我们再看 techempower 的各项性能排名,有 JSON serialization, Single query, Multiple queries, Cached queries, Fortunes, Data updates 和 Plaintext 这几大类的排名。

其中 JSON serialization 是对固定的 Json 结构编码并返回 (message: hello word), Single query 是单次 DB 查询,Multiple queries 是多次 DB 查询,Cached queries 是从内存数据库中获取多个对象值并以 json 返回,Fortunes 是页面渲染后返回,Data updates 是对 DB 的写入,Plaintext 是最简单的返回固定字符串。

这里的 json 编码,DB 操作,页面渲染和固定字符串返回就是相应的业务逻辑,当业务逻辑越重(耗时越大)时,则相应的业务逻辑逐渐就成为了瓶颈,例如 DB 操作其实主要是在测试相应 DB 库和 DB 本身处理逻辑的性能,而框架本身的基础功能消耗随着业务逻辑的繁重将越来越忽略不计(Round 19[2])中物理机下 Plaintext 下的 QPS 在七百万级,而 Data updates 在万级别,相差百倍以上),所以这边主要分析 Json serialization 和 Plaintext 两种相对能比较体现出框架本身 http 性能的排名。

Round 19[3] Json serialization 中 Java 性能最高的框架是 firenio-http-lite (QPS: 1,587,639),而 Go 最高的是 fasthttp-easyjson-prefork(QPS: 1,336,333),按照这里面的数据是 Java 性能高。

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?
firenio-http-lite

从 fasthttp-easyjson-prefork 的 pprof 看除了 read 和 write 外, json (相当于 Business logic) 占了 4.5%,fasthttp 自身(HTTP Decoder, HTTP Encoder, Router)占了 15%,仅看 Json serialization 似乎会有一种 Java 比 Go 性能高的感觉。

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?
fasthttp

那我们继续把业务逻辑简化,看一下 Plaintext 的排名,Plaintext 模式其实是在使用 HTTP pipeline 模式下压测的,在 Round 19[4] 中 Java 和 Go 已经几乎一样的 QPS 了,在 Round 19 之后的一次测试[5]中 gnet 已经排在所有语言的第二,但是前几个框架 QPS 其实差别很微小。

这时候其实主要瓶颈都在 net 层,而 go 官方的 net 库包含了处理 goroutine 相关的逻辑,像 gonet 之类的直接操作 epoll 的会少一些这方面的消耗,Java 的 nio 也是直接操作的 epoll 。

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?
net

拿了 gnet 的测试源码跑了下压测,看到 pprof 如下,其实这里 gnet 还有更进一步的性能优化空间:time.Time.AppendFormat 占用 30% CPU。

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?
gnet

可以使用如下提前 Format ,允许减少获取当前时间精度的情况下大幅减少这部分的消耗。

var timetick atomic.Value

func NowTimeFormat() []byte {
 return timetick.Load().([]byte)
}

func tickloop() {
 timetick.Store(nowFormat())
 for range time.Tick(time.Second) {
  timetick.Store(nowFormat())
 }
}

func nowFormat() []byte {
 return []byte(time.Now().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
}

func init() {
 timetick.Store(nowFormat())
 go tickloop()
}

这样优化后接下来的瓶颈在于 runtime 的内存分配,是由于这个压测代码中还存在下面的部分没有复用内存:

知乎问答:为什么 Go 的 Web 框架速度还不如 Java?
runtime
pipeline code

其实 gnet 本身的消耗已经做到非常小了,而 c++ 的 ulib 也是类似这样使用的非常简单的 HTTP 编解码操作来压测。

分析

对于这里面测试的框架,影响因素主要如下:

1、直接基于 epoll 的简单 http: 没有完整的 http decoder 和 route (如 gnet, ulib 直接简单的字节拼接,固定的路由 handler 回调)

2、zero copy 和内存复用: 内部处理字节的 0 拷贝(go 官方 http 库为了减少开发者的出错概率,没有使用 zero copy,否则开发者可能在无意中引用了已经放回 buff 池内的的数据造成没有意识到的并发问题等等),而内存复用,大部分框架或多或少都已经做了。

3、prefork:注意到 go 框架中有使用了 prefork 进程的方式(比如 fasthttp-prefork),这是 fork 出多个子进程,共享同一个 listen fd,且每个进程使用单核但并发(1 个 P)处理的逻辑可以避免 go runtime 内部的锁竞争和 goroutine 调度的消耗(但是 go runtime 中为了并发和 goroutine 调度而存在的相关“无用”代码的消耗还是会有一些)

4、语言本身的性能差异 对于第一点,其实简化了各种编解码和路由之后,虽然提高了性能,但是往往会降低框架的易用性,对于一般的业务而言,不会出现如此高的 QPS,同时选择框架的时候往往还需要考虑易用性和可扩展性等,同时还需要考虑到公司内部原有中间件或者 SDK 所使用的框架集成复杂度。

对于第二点,如果是作为一个网络代理而言,没有业务方的开发,往往可以使用真正的完全 zero copy,但是作为业务开发框架提供出去的话是需要考虑一定的业务出错概率,往往牺牲一部分性能是划算的。

第三点 prefork , java netty 等是直接对于线程操作,可以更加定制化的优化性能,而 go 的 goroutine 需要的是一个通用协程,目的是降低编写并发程序的难度,在这个层次上难免性能比不上一个优化的非常出色的 Java 基于线程操作的框架;但是直接操作线程的话需要合理控制好线程数,这是个比较头疼的调优问题(特别是对于新手来说),而 goroutine 则可以不关心池子的大小,使得代码更加优雅和简洁,这对于工程质量保障其实是一个提升。另外这里存在 prefork 是由于 go 没法直接操作线程,而 fasthttp 提供了 prefork 的能力,使用多进程方式来对标 Java 的多线程来进一步提高性能。

第四点,语言本身来说 Java 还是更加的成熟,包括 JVM 的 Jit 能力也使得在热代码中和 Go 编译型语言的差异不大,何况 Go 本身的编译器还不是特别成熟,比如逃逸分析等方面的问题, Go 本身的内存模型和 GC 的成熟度也比不上 Java。还有很重要的一点,Go 的框架成熟度和 Java 也不在一个级别,但相信这些都会随着时间逐步成熟。

总之,对于这个框架压测数据意义在于了解性能天花板,判断继续优化的空间和 ROI (投入产出比)。具体选择框架还是要根据使用场景,性能,易用性,可扩展性,稳定性以及公司内部的生态等作出选择,语言和性能分别只是其中一个因素。

各种框架的应用场景不同导致其优化侧重点不同,如 spring web 为了易用性,可扩展性和稳定性而牺牲了性能,但它同样拥有庞大的社区和用户。再比如 Service Mesh Sidecar 场景下 Go 的天然并发编程上的优势,以及小内存占用,快速启动,编译型语言等特点使得比 Java 更加适合。

(附:其实我使用上述代码和 dockerfile 构建,并且使用同样的压测脚本[6],在阿里云 4 核独享机器测试下 go fasthttp-easyjson-prefork 框架 Json serialization 的性能要高于 Java wizzardo-http 和 firenio-http-lite 30% 以上且延迟更低的,这可能和内核有关)。

以上为淘系架构团队风弈的个人见解,欢迎前来讨论。

同时也欢迎带上简历发送到邮箱:fengyi.shy@alibaba-inc.com,加入淘系架构团队,一起并肩作战或前来指导工作,为上层淘系各业务提升基础性能和研发效率。

总结

我认为,大部分时候,框架的性能不是关键,更何况 Go、Java 等语言相比 PHP 等动态语言性能相对更好。何况 PHP 做 Web 开发,语言本身在大部分时候都不是问题呢。

你怎么看?欢迎留言。

参考资料

[1]

压测框架源码: https://github.com/TechEmpower/FrameworkBenchmarks

[2]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[3]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[4]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[5]

一次测试: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext

[6]

同样的压测脚本: https://tfb-status.techempower.com/unzip/results.2020-05-13-21-08-06-543.zip/results/20200509183726/fasthttp-easyjson-prefork/json/raw.txt



推荐阅读



福利

我为大家整理了一份 从入门到进阶的Go学习资料礼包 ,包含学习建议:入门看什么,进阶看什么。 关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

以上是关于为啥 Go 语言的性能还不如java的主要内容,如果未能解决你的问题,请参考以下文章

Go语言与Java之间性能相差多少

Go语言概述

go语言和java学哪个比较好?

golang并发真的比java高吗

为啥说人工智能和机器学习是Python 独有的专利,像其他C,JAVA 都无法实现?啥原因?

Go语言微服务开发框架实践(上篇)