Java、Go 和 Rust 的比较
原载于:公众号「云原生技术爱好者社区」
这是一篇 Java、Go 和 Rust 之间的比较。不仅仅在基准指标的意义上,更多是在输出可执行文件大小、内存使用、CPU 使用、运行时要求之间的比较,当然还有一些简单性能测试。
为了更贴近现实,我已经用这种比较中的每种语言编写了一个 Web 服务。Web 服务非常简单,它为三个 REST http 服务。
Web 服务,使用 Java、Go 和 Rust。
文件大小
在 Java 打包构建的场景下下,我使用 maven-shade-plugin 将所有内容构建到一个 jar 中,并使用了 mvn package 打成 jar 包。在 Go 的情况下,我使用了 go build. 最后,对于 Rust,我使用了 cargo build —release.
每个程序的编译大小(以兆字节为单位)。工程编译的大小还取决于所选的库/依赖项,在我的具体情况下,以上是编译后的程序大小。
在单独的部分中,我将构建所有三个程序并将其打包为 docker 映像,并将列出它们的大小以及显示每种语言所需的运行时开销。更多详情如下。
内存使用情况
没有任何请求的情况下
每个应用程序在内存中空闲时的内存使用情况。
Go 和 Rust 版本在空闲时显示内存占用几乎看不到,只是当 JVM 启动程序并闲置不做任何事情时,Java 消耗了 160 MB 以上的空间。在 Go 的情况下,程序使用 0.86 MB,在 Rust 的情况下使用 0.36 MB。这是一个很大的不同!因为这是在内存中什么也不做情况下,Java 内存占用比 Go 和 Rust 对应物多两个数量级,所以这是对资源的巨大浪费。
REST 请求
让我们使用 wrk 通过请求访问 API 并观察内存和 CPU 使用情况,以及我的机器上针对程序的三个版本的每个请求地址请求的 qps。
关于上面的 wrk 命令说如下,使用两个线程(用于 wrk)并在池中保持 400 个打开的连接,并在 30 秒的持续时间内重复调用 GET 请求。这里我只使用了两个线程,因为 wrk 和被测程序都在同一台机器上运行,所以我不希望它们在可用资源上(尤其是 CPU)相互竞争。
每个 Web 服务都分别进行了测试,并且在每次运行之间重新启动了 Web 服务。下面这个请求是该程序每个版本的三个运行中最好的一个。
此请求返回 Hello, World! 信息。它分配字符串“Hello, World!” 并将其序列化并以 JSON 格式返回。
请求 /hello 时的 CPU 使用率
访问 /hello 时的内存使用情况
访问 /hello qps
/greeting/{name}
此请求接受段路径参数 { name },然后格式化字符串“ Hello, {name}!” , 将其序列化并作为 JSON 格式的问候消息返回。
请求 /greeting/{name} 时的 CPU 使用率
访问 /greeting/{name} 时的内存使用情况
访问 /greeting/{name} qps
/fibonacci/{number}
此端点接受段路径参数 { number } 并返回斐波那契数和序列化为 JSON 格式的输入数字。
这个特定接口我选择以递归形式实现它。毫无疑问通过迭代实现会产生更好的性能结果,并且出于生产目的,应该选择迭代形式,但是在生产代码中存在必须使用递归的情况(不一定是专门用于计算第 n 个斐波那契数)。另外为了测试性能对比,通过该实现能够使大量 CPU 参与堆栈分配。
请求 /fibonacci/{number} 时的 CPU 使用率
访问 /fibonacci/{number} 时的内存使用情况
访问 /fibonacci/{number} qps 在 Fibonacci 接口期间,Java 实现是唯一一个在 150 个请求上出现超时的编程语言,如下面 wrk 的输出所示。
/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。
最终的 Docker 镜像大小
结论
三种语言的比较
在得出任何结论之前,我想指出这三种语言之间的关系(或没有关系)。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++ 竞争的另一个方面是在嵌入式领域。
版权声明: 本文为 InfoQ 作者【百度开发者中心】的原创文章。
原文链接:【http://xie.infoq.cn/article/6a9854060ffa9609c86d84775】。文章转载请联系作者。
评论