目前开发中爆火的Java、Go和Rust间的比较!

news/2024/11/30 13:46:59/

本文对Java、Go和Rust之间的对比并非完全是基准测试,更多的是比较输出的可执行文件大小、内存使用情况、CPU使用率、运行时要求,当然会有一个小基准测试用于获取RPS数据,使得更容易理解这些数值。

 

为了尝试更合理比较这三者,我在这次比较中分别用每种语言写了个Web服务。该Web服务非常简单,提供了3个REST端点。

 

 

三个Web服务的存储库托管在GitHub[1]上。

 

制品大小

 

介绍下我是如何构建二进制文件的。在Java示例中,我使用maven-shade-plugin[2]插件并使用mvn package命令,Go则使用go build命令,最后是Rust则使用cargo build --release。

 

 

制品编译的大小也取决于所选的库/依赖项,因此,如果它们膨胀了,那么编译的程序也会是同样的结果。在此处特定情况下,对于我所选择的库,上图显示是程序编译的大小。

 

在下面单独的部分中,我将构建所有三个程序并打包成Docker镜像,并列出它们的大小,以及显示每种语言所需的运行时开销。更多细节如下。

 

内存使用情况

 

空闲状态

 

 

什么?在空闲运行时显示内存占用的Go和Rust版本的条形图在哪?好吧,它们是有的,只是Java在JVM启动程序,处于空闲时,什么都不做的情况下,就消耗了高达160MB的内存。在Go的情况下,程序使用了0.86 MB,在Rust的情况下使用0.36 MB。这是一个非常大的区别!在这里,Java比Go和Rust对应的程序多用了两个数量级的内存,只是空跑内存什么也不做。这是对资源的巨大浪费。

 

提供REST请求

 

我们使用wrk[3]来请求API,并观察内存和CPU使用情况,以及三个版本的程序的每个端点在我的机器上的每秒请求数。

 

wrk -t2 -c400 -d30s http://127.0.0.1:8080/hello 
wrk -t2 -c400 -d30s http://127.0.0.1:8080/greeting/Jane
wrk -t2 -c400 -d30s http://127.0.0.1:8080/fibonacci/35

以上wrk命令表示,使用两个线程(用于wrk),并在池中保持400个开启的连接,并反复调用GET端点,持续30秒。此处我只使用两个线程,是因为wrk跟被测试的程序都运行在同一台机器上,因此我不想它们在可用资源,尤其是CPU上相互竞争。

 

每个Web服务都单独测试,且每次运行测试都会重启Web服务。

 

以下是每个版本的程序三次运行中的最佳结果。

 

/hello

该端点返回一个“Hello, World!”的消息。它分配字符串 "Hello, World!",并将其序列化,以JSON格式返回。

 

 

 

 

 

/greeting/{name} 

该端点接受段路径参数{name},然后将字符串"Hello,{name}"格式化,序列化并返回以JSON格式的问候信息。

 

 

 

 

/fibonacci/{number} 

 

该端点接受段路径参数{number}并以JSON格式序列化返回输入的数字和斐波那契数。

 

对于这个特定的端点,我选择用递归的形式来实现它。我知道,毫无疑问,迭代实现可以获得更好的性能结果,而且出于生产目的,应该选择迭代形式,但是在生产代码中,有些情况下必须使用递归(非特指专用于计算斐波那契数)。因此我想让这个实现与CPU堆栈分配密切相关。

 

 

 

 

在/fibonacci端点测试中,Java实现是唯一一个出现150次请求超时的,wrk输出如下所示:

 

 

 

运行时大小

 

为了模仿真实世界的云原生应用,并消除"它在我的机器上正常!"这种情况,我为这三个应用分别创建了一个Docker镜像。

 

Docker源文件包含在存储库中相应程序的文件夹下。

 

我使用openjdk:8-jre-alpine作为Java应用的基础运行时镜像,它是已知的最小的镜像之一。然而这带来了一些需要注意的事项,可能适用,也可能不适用于你的应用。主要是alpine镜像在处理环境变量名方面不符合posix标准,所以你不能在Docker文件中使用带.(点)的ENV(也不是什么大事),另一个是alpine Linux镜像是用musl libc而不是glibc编译的,这意味着如果你的应用程序依赖于需要glibc存在的东西,它就无法工作。就我而言,alpine很好用。

 

至于Go和Rust版本的应用,我使用了静态编译,这意味着它们在运行时镜像中不需要任意libc(glibc、musl等等),也意味着它们不需要一个带OS的基础镜像来运行。所以我使用了scratch镜像,这是一个no-op(无操作?)镜像,它托管编译后的可执行文件,零开销。

 

我使用的Docker镜像命名约定是{lang}/webservice。Java、Go和Rust版本的应用程序的镜像大小分别是113MB、8.68MB和4.24 MB。

 

 

结论

 

 

在得出任何结论之前,我想指出这三种语言之间的关系(或者说缺乏关系)。Java和Go都是垃圾收集型语言,然而,Java是提前编译(AOT)为在JVM上运行的字节码。当Java应用程序启动时,会调用Just-In-Time(JIT)编译器来优化字节码,随时随地将其编译成本地代码,以提高应用程序的性能。

 

Go和Rust都是提前编译成原生代码,在运行时不会发生进一步的优化。

 

Java和Go都是垃圾收集类型语言,存在STW的副作用。意味着每当垃圾回收器运行的时候,它就会停止应用程序,进行垃圾回收,当垃圾回收结束后再从之前的状态中恢复。大部分垃圾回收器需要停止程序,但是也有一些实现不需要这样子。

 

当Java在90年代诞生时,它最大的卖点之一就是“一次编写,随处运行”。在当时这是非常棒的,因为当时市场上还没有很多虚拟化解决方案。如今,大多数CPU都支持虚拟化,在代码可以在任何地方(无论在任何受支持的平台上)运行的前提下,使用一种语言进行开发的诱惑就消失了。Docker和其他解决方案提供了廉价的虚拟化。

 

在整个测试过程中,Java版本的应用比Go或Rust对应的应用消耗了更多的内存,在数量级上,前两次测试中,Java使用的内存大约多出8000%。这意味着对于现实世界的应用来说,Java应用的运营成本更高。

 

在前两项测试中,Go应用程序的CPU使用量比Java少了20%左右,而服务的请求却多了38%。另一方面,Rust版本的CPU使用量比Go少57%,而服务的请求量却多13%。

 

第三个测试在设计上就是CPU密集型的,我希望尽可能地利用CPU。Go和Rust都比Java多利用了1%的CPU。而且我想如果wrk不是在同一台机器上运行的话,这三个版本的CPU都会达到100%的上限。在内存方面,Java比Go和Rust多利用了2000%以上的内存。Java比Go多服务20%左右的请求,而Rust比Java多服务15%左右的请求。

 

在写这篇文章的时候,Java编程语言已经存在了近三十年,这使得在市场上找到Java开发者相对容易一些。另一方面,Go和Rust都是相对较新的语言,所以相对于Java来说,自然而然的数量或开发人员就少了。尽管如此,Go和Rust都得到了很多关注,许多开发人员在新项目中采用了它们,并且有许多在生产环境中运行的项目使用Go和Rust,因为简单地说,它们在资源需求方面比Java更高效。(也可能是因为它们是比较新的酷炫语言)

 

我在写这篇文章的程序时,我学会了Go和Rust。就我而言,Go的学习曲线很短,因为它是一门比较容易上手的语言,而且语法相对于其他语言来说也很小。我只花了几天时间就用Go写好了程序。关于Go,有一点需要注意的是编译速度,我不得不承认,与Java/C/C++/Rust等其他语言相比,Go的编译速度极快。Rust版本的程序我花了一周左右的时间才完成,我不得不说,大部分时间都花在了借用检查器(borrow checker)上。Rust有严格的所有权规则,但一旦掌握了Rust中所有权和借用的概念,编译器的错误信息就会突然变得更有意义。Rust编译器之所以在违反借用检查规则时对你“谆谆教诲”(无情报错),是因为编译器希望在编译时证明分配的内存的生存期和所有权。通过这样做,它保证了程序的安全性(例如:没有悬空指针,除非使用了不安全的代码逃逸), 并且在编译时确定了释放位置,从而消除了对垃圾收集器的需求和运行时成本。当然,这是以学习Rust的所有权系统为代价的。

 

就竞争而言,在我看来,Go是Java(通常是JVM语言)的直接竞争对手,但不是Rust的竞争对手。另一方面,Rust是Java、Go、C和C++的有力竞争者。

 

因为它们的效率,我认为我自己将会用Go和Rust写更多的程序,但很可能用Rust写得更多。它们都很适合于Web服务、CLI、系统程序等等的开发。然而,Rust比Go有一个根本的优势。它不是一种垃圾收集的语言,而且与C和C++相比,它的设计是为了安全地编写代码。例如,Go并不特别适合用来写操作系统内核,这也是Rust的优势所在,它与C/C++竞争,因为它们是长期存在的、事实上的写操作系统的语言。Rust与C/C++竞争的另一个层面是在嵌入式世界,我们将在以后再讨论这个。

 

感谢您的阅读!


http://www.ppmy.cn/news/749237.html

相关文章

ALV OO:颜色控制

1.信号灯 绿灯:3 黄灯:2 红灯:1 关键代码如下: DATA: BEGIN OF gt_data OCCURS 0.INCLUDE STRUCTURE spfli. DATA: light TYPE c. " 显示信号灯的字段 DATA: END OF gt_data.LOOP AT gt_data." 为內表的信号灯字段赋值…

计算机网络思科参考答案--IP 编址考试

请参见图示。 管理员必须向路由器 A 网络中的所有成员发送一条消息。 那么网络 172.16.16.0/22 的广播地址是什么? 172.16.23.255172.16.255.255172.16.16.255172.16.20.255 172.16.19.255 请参见图示。 思考一下 10.16.10.0/24 网络范围内已经使用了的地址和必须保…

微信小程序:QQ音乐API接口变更后访问被禁止,出现403页面的解决办法

问题描述: 在使用QQ音乐API接口获取音乐的时候,微信后台请求成功,但是出现403禁止访问页面,导致无法播放音乐。 解决办法 一、首先进行QQ音乐官网抓包分析 对RequestURL进行分析,得知每首歌曲音乐源的“VKey"都不同&#…

性能压测工具wrk

wrk支持mac和linux,不支持windows windows: 解决:使用docker 执行1:docker pull williamyeh/wrk 执行2:winpty docker run -it --rm williamyeh/wrk -t12 -c400 -d30s http://news.baidu.com/ 参考:性能…

IAR “Error[e104]: Failed to fit all segments into specified ranges”问题处理

0. 软件环境简介 0.1 集成开发环境: IAR Embedded Workbench for MSP430 IDEV7.10.1 IAR Embedded Workbench sharedcomponents V8.0.4.4801 1. 问题描述 一天,我用相同代码,相同版本IAR,重建一个工程,在编译软…

ELAU MC-4/11/10/400 C600/10/1/1/1/00 电动车辆供电设备(交流充电器)中的引导线通信

ELAU MC-4/11/10/400 C600/10/1/1/1/00 电动车辆供电设备(交流充电器)中的引导线通信 ish 070/60011/0/0/00/0/00/01/00-ELAU AG-伺服电机1.55A 6000RPM 680VDC ish 070/60022/0/1/00/0/10/00/01-ELAU AG-伺服电机2.7安培680伏直流电0.2安培24伏直流电2.2纳米 MC-4/10/50/400 -…

施耐德控制器维修伺服驱动器维修C400A8

施耐德控制器维修伺服驱动器维修C400A8 施耐德ELAU控制器维修C400/A8/1/1/1/00施耐德伺服驱动器维修 施耐德驱动器维修 施耐德控制器维修 施耐德伺服器维修 施耐德PLC控制器维修 ELAU伺服ELAU伺服驱动器维修控制器常见维修故障:短路,模块损坏&#xf…

Python Flask Web 高并发实现

Flask超高并发,Flask多进程gevent ,Flask非gunicorn gevent多进程 1. flask gevent multiprocess wsgi简介 常常大家都是用gunicorn来解决flask后端部署并发的问题, 然而觉得自启多进程是为更优雅的高并发方式。这样就不需要gunicorn了。…