2024年各编程语言运行100万个并发任务需要多少内存?

你还记得2023年那篇比较各种流行编程语言异步编程内存消耗比较的文章吗?

现在是2024年底,我很好奇在一年时间里,随着各种语言的最新版本发布,情况有什么变化。

让我们再次进行基准测试,看看结果!

基准

用于基准测试的程序与去年相同:

让我们启动 N 个并发任务,每个任务等待 10 秒,然后在所有任务完成后程序退出。任务的数量由命令行参数控制。

这次,让我们专注于协程而不是多线程。

所有基准代码可以在async-runtimes-benchmarks-2024访问。

什么是协程?

协程是计算机程序的一种组件,能够暂停和恢复执行。这使得它比传统的线程更灵活,特别适合用于处理需要协作的多任务操作,比如实现任务协作、异常处理、事件循环、迭代器、无限列表和数据管道等功能。

Rust

我用 Rust 创建了 2 个程序。一个使用tokio

use std::env;
use tokio::time::{sleep, Duration};#[tokio::main]
async fn main() {let args: Vec<String> = env::args().collect();let num_tasks = args[1].parse::<i32>().unwrap();let mut tasks = Vec::new();for _ in 0..num_tasks {tasks.push(sleep(Duration::from_secs(10)));}futures::future::join_all(tasks).await;
}

而另一个使用async_std

use std::env;
use async_std::task;
use futures::future::join_all;
use std::time::Duration;#[async_std::main]
async fn main() {let args: Vec<String> = env::args().collect();let num_tasks = args[1].parse::<usize>().unwrap();let mut tasks = Vec::new();for _ in 0..num_tasks {tasks.push(task::sleep(Duration::from_secs(10)));}join_all(tasks).await;
}

两者都是 Rust 中常用的异步运行时。

C#

C#,与 Rust 类似,对 async/await 提供了一流的支持:

int numTasks = int.Parse(args[0]);
List<Task> tasks = new List<Task>();for (int i = 0; i < numTasks; i++)
{tasks.Add(Task.Delay(TimeSpan.FromSeconds(10)));
}await Task.WhenAll(tasks);

自 .NET 7 起,.NET 还提供了 NativeAOT 编译,它将代码直接编译为最终的二进制文件,因此不再需要 VM 来运行托管代码。因此,我们也添加了 NativeAOT 的基准测试。

NodeJS

NodeJS 也是如此:

const util = require('util');
const delay = util.promisify(setTimeout);async function runTasks(numTasks) {const tasks = [];for (let i = 0; i < numTasks; i++) {tasks.push(delay(10000));}await Promise.all(tasks);
}const numTasks = parseInt(process.argv[2]);
runTasks(numTasks);

Python

还有 Python:

import asyncio
import sysasync def main(num_tasks):tasks = []for task_id in range(num_tasks):tasks.append(asyncio.sleep(10))await asyncio.gather(*tasks)if __name__ == "__main__":num_tasks = int(sys.argv[1])asyncio.run(main(num_tasks))

Go

在 Go 语言中,goroutine 是实现并发的关键。我们不需要逐个等待 goroutine ,而是通过 WaitGroup 来统一管理:

package mainimport ("fmt""os""strconv""sync""time"
)func main() {numRoutines, _ := strconv.Atoi(os.Args[1])var wg sync.WaitGroupfor i := 0; i < numRoutines; i++ {wg.Add(1)go func() {defer wg.Done()time.Sleep(10 * time.Second)}()}wg.Wait()
}

Java

自 JDK 21 起,Java 提供了虚拟线程,这与协程的概念相似:

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;public class VirtualThreads {public static void main(String[] args) throws InterruptedException {int numTasks = Integer.parseInt(args[0]);List<Thread> threads = new ArrayList<>();for (int i = 0; i < numTasks; i++) {Thread thread = Thread.startVirtualThread(() -> {try {Thread.sleep(Duration.ofSeconds(10));} catch (InterruptedException e) {// Handle exception}});threads.add(thread);}for (Thread thread : threads) {thread.join();}}
}

Java有一个新的 JVM 变体叫做 GraalVM。GraalVM 还提供本机镜像,这与.NET 中的 NativeAOT 概念相似。因此,我们也为 GraalVM 添加了基准测试。

测试环境

  • 硬件:第 13 代英特尔(R)酷睿(TM) i7-13700K
  • 操作系统:Debian GNU/Linux 12 (bookworm)
  • Rust: 1.82.0
  • .NET: 9.0.100
  • Go: 1.23.3
  • Java: openjdk 23.0.1 build 23.0.1+11-39
  • Java (GraalVM): java 23.0.1 build 23.0.1+11-jvmci-b01
  • NodeJS: v23.2.0
  • Python: 3.13.0

如果可用,所有程序都使用发布模式启动,并且由于我们的测试环境中没有 libicu,国际化和全球化支持被禁用。

结果

最小内存占用

让我们从小规模开始,因为某些运行时本身就需要一些内存,我们先只启动一个任务。


我们可以看到 Rust、C#(NativeAOT) 和 Go 达到了类似的结果,因为它们都被静态编译成原生二进制文件,需要很少的内存。Java(GraalVM native-image) 也表现不错,但比其他静态编译的程序多用了一点内存。其他在托管平台上运行或通过解释器运行的程序消耗更多内存。

在这种情况下,Go似乎有最小的内存占用。

Java 使用 GraalVM 的结果有点出人意料,因为它比 OpenJDK 的 Java 消耗更多内存,但我猜这可以通过一些设置来调优。

1万个任务

这里有一些惊喜!两个 Rust 基准测试都取得了非常好的结果:即使后台运行着1万个任务,它们使用的内存也很少,与最小内存占用相比没有增长太多!C#(NativeAOT) 紧随其后,只使用了约 10MB 内存。我们需要更多任务来给它们施加压力!

Go 的内存消耗显著增加。goroutines 应该是非常轻量级的,但实际上它们消耗的 RAM 比 Rust 多得多。在这种情况下,Java(GraalVM native image) 中的虚拟线程似乎比 Go 中的 Goroutines 更轻量级。令我惊讶的是,Go 和Java(GraalVM native image) 这两个静态编译成原生二进制文件的程序,比运行在VM上的C#消耗更多RAM!

10万个任务

在我们将任务数量增加到 10 万后,所有语言的内存消耗开始显著增长。

Rust 和 C# 在这种情况下都表现得很好。一个大惊喜是 C#(NativeAOT) 甚至比 Rust 消耗更少的 RAM ,击败了所有其他语言。非常令人印象深刻!

在这一点上,Go 程序不仅被 Rust 击败,还被 Java(除了在 GraalVM 上运行的那个)、C# 和 NodeJS 击败。

100万个任务

现在让我们走个极端。

最终,C#毫无疑问地击败了所有其他语言;它非常有竞争力,真的成为了一个怪物。正如预期的那样,Rust在内存效率方面继续表现出色。

Go 与其他语言的差距进一步扩大。现在 Go 比冠军多消耗13倍以上的内存。它也比 Java 多消耗2倍以上,这与 JVM 是内存大户而 Go 轻量级的普遍认知相矛盾。

总结

正如我们观察到的,大量并发任务即使不执行复杂操作也会消耗大量内存。不同的语言运行时有不同的权衡,有些对少量任务来说轻量高效,但在处理数十万个任务时扩展性较差。

自去年以来,很多事情都发生了变化。通过对最新编译器和运行时的基准测试结果,我们看到 .NET 有了巨大的改进,使用 NativeAOT 的 .NET 真的能与 Rust 竞争。用 GraalVM 构建的 Java 原生镜像在内存效率方面也表现出色。然而,Go 的 goroutines 在资源消耗方面继续表现不佳。

版权信息

原作者:hez2010

译者:InCerry

原文链接:https://hez2010.github.io/async-runtimes-benchmarks-2024/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/843246.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用星球助手导出帖子的手把手教程

当我们把星球的帖子下载到本地电脑之后, 如果想要导出成PDF或者Word之类的格式进行学习, 该怎么弄呢? 其实也是相当简单的. 到"搜索"模块里, 选择"帖子", 输入关键词后者留空都可以, 点击"搜索", 在出现的帖子的右上角有三个图标, 从左到右分别…

OpenVZ 8.0 - 基于容器的 Linux 开源虚拟化解决方案

OpenVZ 8.0 - 基于容器的 Linux 开源虚拟化解决方案OpenVZ 8.0 - 基于容器的 Linux 开源虚拟化解决方案 Open source container-based virtualization for Linux 请访问原文链接:https://sysin.org/blog/openvz-8/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.or…

Virtuozzo Hybrid Server 8.0 - 容器、计算和存储虚拟化平台

Virtuozzo Hybrid Server 8.0 - 容器、计算和存储虚拟化平台Virtuozzo Hybrid Server 8.0 - 容器、计算和存储虚拟化平台 The VMware alternative for service providers and enterprises 请访问原文链接:https://sysin.org/blog/virtuozzo-hybrid-server-8/ 查看最新版。原创…

一款开源、免费、美观的 Avalonia UI 原生控件库 - Semi Avalonia

前言 最近发现DotNetGuide技术社区交流群有不少小伙伴在学习Avalonia,今天大姚给大家分享一款开源、免费、美观的 Avalonia UI 原生控件库:Semi Avalonia。Avalonia项目介绍 Avalonia是一个强大的框架,使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制U…

用星球助手搜索本地帖子的手把手教程

通过知识星球手机App或者网页的人都知道, 搜点东西是真费劲, 要么没法搜到想要的东西, 要么就是搜出来一大堆有用的没用的, 根本找不到自己真正想要的东西. 好在星球助手极大程度解决了这个问题. 我们将帖子下载到本地电脑上之后, 我们可以用更加精准的关键词进行搜索, 针对问答…

读数据质量管理:数据可靠性与数据质量问题解决之道18数据发现

数据发现1. 让元数据为业务服务 1.1. 在过去十多年中,数据团队越来越擅长收集大量的数据 1.2. 公司如今正在收集越来越多关于其数据的数据,也就是元数据1.2.1. dbt等ETL解决方案让跟踪和使用元数据变得容易,而云服务提供商则使栈中数据解决方案之间的元数据的互操作性变得更…

初见客户交流指南:精准高效,建立信任

在已知客户对产品有大致需求(询盘)的背景下,初次拜访交流至关重要。以下是一份实用的交流指南: 充分准备 自我介绍清晰明了 简洁明了地告知客户您的基本信息,包括姓名、籍贯、公司名称、职位以及您在决策过程中的角色。 树立专业形象,传达出“业务方面,找我就对了”的自…

《数字经济产业链》及其相关公司 —— 深度梳理

《数字经济产业链》及其相关公司 —— 深度梳理 来源:行业研究报告发布时间:2023 年 05 月 22 日 11:07:35(网经社讯)产业链分析 1、产业链概述 2、数字经济行业上游: (1)5G 根据中国信通院、中国移动、中国电信、中国联通、工信部,综合市场公开资料,2025 年中国 5G …

写一个方法检测页面中的所有标签是否正确闭合

function checkTagClosure(htmlString) {// 使用栈来跟踪打开的标签const tagStack = [];// 正则表达式匹配标签 (包括自闭合标签)const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\s*\/?>/g;let match;while ((match = tagRegex.exec(htmlString)) !== null) {const tag…

【api开发】API通信协议总结

API协议 1. REST(Representational State Transfer) 一种用于设计网络应用程序的架构风格。它强调无状态通信,使用标准的HTTP方法(GET、POST、PUT、DELETE),并通过URL识别资源。 2. GraphQL 一种API查询语言,允许客户端精确请求它们所需的数据,不多也不少。这种效率是G…

你有使用过picture标签吗?说说它有哪些运用场景

是的,我了解 <picture> 元素。它是一个 HTML5 元素,用于为不同的屏幕尺寸、设备像素比或文件格式提供不同的图像版本。浏览器会根据当前环境选择最合适的图像显示,从而优化页面加载速度和用户体验。 <picture> 元素本身并不显示图像,而是充当一个容器,其中包含…

小米10ultra ISO12233 主摄OV48C 不同亮度下比较

夜间室内几乎完全黑的环境 结论 1200w像素,没啥差别,吃满像素。4800w像素,也没啥区别 IMG_20241129_022234.HEIC 仅有闪光灯 质量 低 1.49MB 1/50s IMG_20241129_022238.HEIC 头灯补光 质量 低 1.65MB 1/100s 34 38 4800w像素 IMG_20241129_022250.HEIC 4800w像素 质量低…