您现在的位置是:首页 > 编程 > 

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

2025-07-24 09:31:50
2024年各编程语言运行100万个并发任务需要多少内存? 你还记得202年那篇比较各种流行编程语言异步编程内存消耗比较的文章吗?现在是2024年底,我很好奇在一年时间里,随着各种语言的最新版本发布,情况有什么变化。让我们再次进行基准测试,看看结果!基准用于基准测试的程序与去年相同:让我们启动 个并发任务,每个任务等待 10 秒,然后在所有任务完成后程序退出。任务的数量由命令行参数控制。这次,

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

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

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

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

基准

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

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

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

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

什么是协程?

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

Rust

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

代码语言:javascript代码运行次数:0运行复制
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::<i2>().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

代码语言:javascript代码运行次数:0运行复制
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 提供了一流的支持:

代码语言:javascript代码运行次数:0运行复制
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);

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

odeJS

odeJS 也是如此:

代码语言:javascript代码运行次数:0运行复制
ct util = require('util');
ct delay = util.promisify(setTimeout);

async function runTasks(numTasks) {
  ct tasks = [];

  for (let i = 0; i < numTasks; i++) {
    tasks.push(delay(10000));
  }

  await Promise.all(tasks);
}

ct numTasks = parseInt(process.ar[2]);
runTasks(numTasks);

Python

还有 Python:

代码语言:javascript代码运行次数:0运行复制
import asyncio
import sys

async 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.ar[1])
    asyncio.run(main(num_tasks))

Go

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

代码语言:javascript代码运行次数:0运行复制
package main

import (
    "fmt"
    "os"
    "strconv"
    "sync"
    "time"
)

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

Java

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

代码语言:javascript代码运行次数:0运行复制
import 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((10));
                } catch (InterruptedException e) {
                    // Handle exception
                }
            });
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.join();
        }
    }
}

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

测试环境

  • 硬件:第 1 代英特尔(R)酷睿(TM) i7-1700K
  • 操作系统:Debian GU/Linux 12 (bookworm)
  • Rust: 1.82.0
  • .ET: 9.0.100
  • Go: 1.2.
  • Java: openjdk 2.0.1 build 2.0.1+11-9
  • Java (GraalVM): java 2.0.1 build 2.0.1+11-jvmci-b01
  • odeJS: v2.2.0
  • Python: .1.0

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

结果

最小内存占用

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

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

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

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

1万个任务

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

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

10万个任务

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

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

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

100万个任务

现在让我们走个极端。

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

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

总结

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

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

版权信息

原作者:hez2010

译者:InCerry

原文链接:/

本文参与 腾讯云自媒体同步曝光计划,分享自。原始发表:2025-01-0,如有侵权请联系 cloudcommunity@tencent 删除测试程序内存编程语言并发

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1202184.html

相关标签:无
上传时间: 2025-07-24 01:30:06
留言与评论(共有 11 条评论)
本站网友 神经节苷脂
3分钟前 发表
args().collect(); let num_tasks = args[1].parse
本站网友 天一生水地六成之
13分钟前 发表
本站网友 企业发展战略有哪些
30分钟前 发表
goroutines 应该是非常轻量级的
本站网友 怎样减肥有效
7分钟前 发表
所有程序都使用发布模式启动
本站网友 兰州房子
8分钟前 发表
然而
本站网友 域名列表
15分钟前 发表
goroutine 是实现并发的关键
本站网友 腾讯微博客户端
16分钟前 发表
什么是协程?协程是计算机程序的一种组件
本站网友 王欢
7分钟前 发表
只使用了约 10MB 内存
本站网友 北京社区论坛
11分钟前 发表
因为某些运行时本身就需要一些内存
本站网友 陈绍泽
20分钟前 发表
与最小内存占用相比没有增长太多!C#(ativeAOT) 紧随其后