Java、Go 和 Rust 的比较_Spring_java_gg的博客-程序员秘密

技术标签: jvm  python  java  编程语言  人工智能  

fc4229e325a9fab766b38771b6188737.png这是一篇 Java、Go 和 Rust 之间的比较。不仅仅在基准指标的意义上,更多是在输出可执行文件大小、内存使用、CPU 使用、运行时要求之间的比较,当然还有一些简单性能测试。

为了更贴近现实,我已经用这种比较中的每种语言编写了一个 Web 服务。Web 服务非常简单,它为三个 REST http 服务。

bf0105abfe8f4d2a5c06f3031164f6c9.png
Web 服务,使用 Java、Go 和 Rust。

github:https://github.com/dexterdarwich/ws-compare

文件大小

在 Java 打包构建的场景下下,我使用 maven-shade-plugin 将所有内容构建到一个 jar 中,并使用了 mvn package 打成 jar 包。在 Go 的情况下,我使用了go build. 最后,对于 Rust,我使用了cargo build --release.

0e4b19da9531c52c4f8f7c7bd4b846c3.png
每个程序的编译大小(以兆字节为单位)。

工程编译的大小还取决于所选的库/依赖项,在我的具体情况下,以上是编译后的程序大小。

在单独的部分中,我将构建所有三个程序并将其打包为 docker 映像,并将列出它们的大小以及显示每种语言所需的运行时开销。更多详情如下。

内存使用情况

没有任何请求的情况下

d3f3e57ebba6eec0a3586d6387ee9322.png
每个应用程序在内存中空闲时的内存使用情况。

Go 和 Rust 版本在空闲时显示内存占用几乎看不到,只是当 JVM 启动程序并闲置不做任何事情时,Java 消耗了 160 MB 以上的空间。在 Go 的情况下,程序使用 0.86 MB,在 Rust 的情况下使用 0.36 MB。这是一个很大的不同!因为这是在内存中什么也不做情况下,Java 内存占用比 Go 和 Rust 对应物多两个数量级,所以这是对资源的巨大浪费。

 REST 请求

让我们使用 wrk 通过请求访问 API 并观察内存和 CPU 使用情况,以及我的机器上针对程序的三个版本的每个请求地址请求的 qps。

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 个打开的连接,并在 30 秒的持续时间内重复调用 GET 请求。这里我只使用了两个线程,因为 wrk 和被测程序都在同一台机器上运行,所以我不希望它们在可用资源上(尤其是 CPU)相互竞争。

每个 Web 服务都分别进行了测试,并且在每次运行之间重新启动了 Web 服务。下面这个请求是该程序每个版本的三个运行中最好的一个。

/hello

此请求返回 Hello, World! 信息。它分配字符串“Hello, World!” 并将其序列化并以 JSON 格式返回。

51a040509c316f4a82fe212b7169c9ab.png
请求 /hello 时的 CPU 使用率
07cba17d99221ee32b3b2ae56ae63b3f.png
访问 /hello 时的内存使用情况
44edf55b91fe02643b33b326241f134d.png
访问 /hello qps

/greeting/{name}

此请求接受段路径参数 { name },然后格式化字符串“ Hello, {name}!” , 将其序列化并作为 JSON 格式的问候消息返回。

58f6d8ad9682402fcdf69ee407dccdf0.png
请求 /greeting/{name} 时的 CPU 使用率
09568ef960d315d58317bc952d3138fc.png
访问 /greeting/{name} 时的内存使用情况
3229cb8da916cf7d2ef9efadd30520c1.png
访问 /greeting/{name} qps

/fibonacci/{number}

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

这个特定接口我选择以递归形式实现它。毫无疑问通过迭代实现会产生更好的性能结果,并且出于生产目的,应该选择迭代形式,但是在生产代码中存在必须使用递归的情况(不一定是专门用于计算第 n 个斐波那契数)。另外为了测试性能对比,通过该实现能够使大量 CPU 参与堆栈分配。

2f0867e4f2133c2c9c511cc1dcae0279.png
请求 /fibonacci/{number} 时的 CPU 使用率
c116f59440a4dc293834f7022296c3f0.png
访问 /fibonacci/{number} 时的内存使用情况
d5fcb22e2a7925b7e4e7aaafa27f49ad.png
访问 /fibonacci/{number} qps

在 Fibonacci 接口期间,Java 实现是唯一一个在 150 个请求上出现超时的编程语言,如下面wrk的输出所示。

368096307ad08bdaa08b8020aafb84f1.png 10f7d53b7fc7fdfc229f6f05adb213c1.png
/fibonacci 请求的延迟

运行时大小

为了模拟真实世界的云原生应用程序,并消除“它可以在我的机器上运行!”的问题,我为这三个应用程序中的每一个都创建了一个 docker 镜像。

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

对于 Go 和 Rust 版本的应用程序,我对它们进行了静态编译,这意味着它们不需要 libc(glibc,musl...等)存在于运行时镜像中,这也意味着它们不需要一个带有操作系统的基础镜像来运行。所以我使用了scratch docker镜像,它是一个以零开销的方式承载编译后的可执行文件。

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

99e11f536127861f15c48b95cc6e1781.png
最终的 Docker 镜像大小

结论

21fb0e5e15d4407757c551a38f60ecbd.png
三种语言的比较

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

Go 和 Rust 都提前编译为本机代码,并且在运行时不会发生进一步的优化。

Java 和 Go 都是垃圾收集语言,有一个 STW 的副作用。这意味着每当垃圾收集器运行时,它将停止应用程序,进行垃圾收集,当完成后,它将从它离开的地方恢复应用程序。大多数垃圾收集器需要 STW,但也有一些实现可以减缓这种情况的发生。

当 Java 在90年代创建时,它最大的卖点之一就是 "一次编译,到处运行"。这在当时是很接地气的需求,因为当时市场上还没有很多虚拟化解决方案。如今,大多数 CPU 都支持虚拟化,这使得使用 Java 语言开发的诱惑力消失了,因为 Docker 和其他解决方案提供了廉价的虚拟化,它可以在任何地方运行(在任何支持的平台上)。

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

对于前两个测试,Go 应用程序使用的 CPU 比 Java 少约 20%,同时处理的请求多 38%。另一方面,Rust 版本使用的 CPU 比 Go 少 57%,同时处理的请求多 13%。

第三项测试在设计上是 CPU 密集型的,我想通过它来榨取 CPU 的每一个指令集。Go 和 Rust 的 CPU 使用率都比 Java 高1%。而我认为如果 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 相比,它的编译速度非常快。Rust 版本的程序花了我一周左右的时间来学习,我不得不说,其中大部分时间是在弄清楚借用检查器要我做什么。Rust 有严格的所有权规则,但一旦掌握了 Rust 中所有权和借用的概念,编译器的错误信息就会突然变得更有意义。Rust 编译器之所以在违反借用检查规则时对你大喊大叫,是因为编译器想在编译时证明分配内存的生命周期和所有权。通过这样做,它保证了程序的安全性(例如:没有野指针,除非使用了不安全的代码转义),并且在编译时确定了取消分配,从而消除了对垃圾收集器的需求和运行时成本。当然,这是以学习Rust的所有权系统为代价的。

就竞争而言,在我看来,Go 是 Java(一般的 JVM 语言)的直接竞争对手,但不是 Rust 的竞争对手。另一方面,Rust 是 Java、Go、C 和 C++ 的一个严重竞争对手。

由于它们的效率,我将会用 Go 和 Rust 写更多的程序,但最有可能的是用 Rust 写更多的程序。这两种语言对于网络服务、cli、系统程序(......等)的开发都很好。然而,Rust不是一种垃圾收集语言,这是它跟 Go 相比的天然优势。与 C 和 C++ 相比,它被设计为可以安全地编写代码。例如,Go 并不特别适合用来编写操作系统内核,而这又是 Rust 的优势所在,它可以与 C/C++ 竞争,因为它们是长期存在的、事实上可以用来编写操作系统的语言。Rust 与 C/C++ 竞争的另一个方面是在嵌入式领域。

推荐

我是如何完成从 Scala 到 Go 过渡的?

Golang标准库和外部库的性能对比


随手关注或者”在看“,诚挚感谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012516914/article/details/121882327

智能推荐

python 单下划线/双下划线使用总结_pycharm两个下划线_pfm685757的博客-程序员秘密

python 单下划线/双下划线使用总结时间:2013-10-08 10:56来源:www.chengxuyuans.comPython 用下划线作为变量前缀和后缀指定特殊变量/方法。主要存在四种情形1.    1. object # public    2. __object__ # special, python system use, user sho

点亮LED灯的三种形式_陈一一敲代码的博客-程序员秘密

LED灯的介绍1.1.什么是LED灯?中文名:发光二极管。英文名简称:LED。应用:LED显示屏、交通信号灯、广告灯、液晶屏背光源等。特点:节能是LED灯最突出的特点、环保、跟其它的灯光源相比之下,LED灯会更加"干净"(干净不是指的是表面,而是这个LED灯它其实是属于一种冷光源)、响应速度快等这是一个二极管示意图,左边是正极右边是负极在LED中这个二级管只有正极接入正极,负极接入负极才是可以通电的。

Linux—基础操作_虚拟机怎么打开目录_Sunshine的博客-程序员秘密

文章目录1.虚拟机的操作2.命令行提示符详解3.文件管理命令4.目录相关命令(1)目录的建立(2)删除文件/目录1.虚拟机的操作在真机中打开一个Terminal,真机中有两个虚拟机,一个desktop,一个server输入rht-vmctl start desktop打开一个虚拟机输入rht-vmctl view desktop显示虚拟机输入rht-vmctl reset deskt...

使用Navicat for mysql添加外键约束_Charles.zhang的博客-程序员秘密

先介绍一下基本情况:现在有两个表一张是t_blog表(博客表),一张是t_blogType表(博客类别)现在我想把t_blog表中的typeID属性设置为外键,关联t_blogType表中的id属性。1,右键t_blog表,设计表,选择外键属性2,然后会出现一个设置外键的界面,一共有七列。简单介绍一下这几列的意思:...

语法分析——Python实现递归下降分析程序_snl递归下降python实现_资深吹牛专家的博客-程序员秘密

例子文法实现步骤定义每个非终结符的递归下降分析程序从开始符号递归下降分析如递归下降能到输入串的最后一个符号,则输入串合法;反之输入串不合法代码实现定义全局变量i用来表示当前输入串指针所在位置i=0E的递归下降分析程序:根据文法有E=T∩Gdef E(): # E->TG if T() and G(): return True else: return FalseT的递归下降分析程序:根据文法有T=F∩Sdef T

大端小端字节序与网络字节序_网络字节序是大端序还是小端序_边缘计算机的博客-程序员秘密

概念大端(Big-Endian),小端(Little-Endian)以及网络字节序的概念在编程中经常会遇到。大小端是面向多字节类型定义的,比如2字节、4字节、8字节。整型、长整型、浮点型等,单字节的字符串不用考虑 在存储、传输、接收时需要处理 大端(Big-Endian)内存存储上、低地址存高字节 小端(Little-Endian) 内存存储上,低地址存低字节 网络字节序是指大端传输一个long型数据占4个字节它们分别是:0x12, 34, 56, 78,大小端字节在内存中 存储...

随便推点

Log4j配置 收藏 log4j配置范例_luweifeng1983的博客-程序员秘密

http://blog.csdn.net/wangpancom/archive/2006/10/03/1319611.aspxhttp://blog.csdn.net/iPanda/archive/2005/11/30/540268.aspxlog4j配置范例LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮...

Android 轻松实现语音识别_updownlife的博客-程序员秘密

苹果的iphone 有语音识别用的是Google 的技术,做为Google 力推的Android 自然会将其核心技术往Android 系统里面植入,并结合google 的云端技术将其发扬光大。 所以Google Voice Recognition在Android 的实现就变得极其轻松。  语音识别,借助于云端技术可以识别用户的语音输入,包括语音控制等技术,下面我们将利用Google

Markdown入门_weixin_30656145的博客-程序员秘密

什么是Markdown http://note.youdao.com/iyoudao/?p=1895 Markdown是一种轻量级的「标记语言」。是为那些经常需要码字或者进行文字排版的、对码字手速和排版顺畅度有要求的人群设计的,他们希望用键盘把文字内容打出来的同时搞定排版,最好从头到尾都不要使用鼠标。这些人最常见的是经常需要写文档的码农,另外包括博客写手、网站小编、出版业人士等等。 如果你是程...

ubuntu 16.04 运行sudo apt install default-jre后报错 Could not create the Java Virtual Machine_firecityplans的博客-程序员秘密

ubuntu 16.04 64 位上使用sudo apt install default-jre后,在终端输入java --version时报以下错误:Error: Could not create the Java Virtual Machine原以为是安装错误,后又重新安装sudo apt install default-jdk后,在终端输入java --version时仍然报以上错误...

如何从 900 万张图片中对 600 类照片进行分类? | 技术头条_喜欢打酱油的老鸟的博客-程序员秘密

https://www.toutiao.com/a6674844350158995976/作者 | Aleksey Bilogur译者 | 风车云马责编 | Jane出品 | AI科技大本营(公众号id:rgznai100)【CSDN 编者按】完成一个简单的端到端的机器学习模型需要几步?在本文中,我们将从一个完整的工作流:下载数据→图像分割与处理→建模→发布模型,教...

iOS多线程-SDWebImage简单介绍 1 设置imageView的图片 (内存缓存&磁盘缓存) 1 [cell.imageView sd_setImageWithURL:[NSURL URL_牵只蜗牛去散步!的博客-程序员秘密

iOS多线程-SDWebImage简单介绍1 设置imageView的图片 (内存缓存&磁盘缓存)1 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placehoder"]];2 设置图片另外几种

推荐文章

热门文章

相关标签