Fundamentals 14 min read

How Much Memory Do 1 Million Concurrent Tasks Consume Across Languages?

This article benchmarks the peak memory usage of one, ten‑thousand, one‑hundred‑thousand and one‑million concurrent tasks in Rust, Go, Java, C#, Node.js, Python and Elixir, revealing surprising differences between native threads, async runtimes and virtual threads.

IoT Full-Stack Technology
IoT Full-Stack Technology
IoT Full-Stack Technology
How Much Memory Do 1 Million Concurrent Tasks Consume Across Languages?

Introduction

The author observed a >20× memory‑usage gap when comparing programs that handle many network connections, prompting the creation of a unified benchmark that launches N concurrent tasks, each sleeping for ten seconds, then exits.

Benchmark Design

For each language a small program is written that spawns N tasks (controlled via a command‑line argument). All implementations use the language’s idiomatic concurrency primitive: native threads, async/await runtimes, goroutines, virtual threads, etc. The source code for every benchmark is available on GitHub.

Rust

Three variants are provided: a traditional thread version, a Tokio async version, and an async‑std version (omitted for brevity).

let mut handles = Vec::new();
for _ in 0..num_threads {
    let handle = thread::spawn(|| {
        thread::sleep(Duration::from_secs(10));
    });
    handles.push(handle);
}
for handle in handles {
    handle.join().unwrap();
}
let mut tasks = Vec::new();
for _ in 0..num_tasks {
    tasks.push(task::spawn(async {
        time::sleep(Duration::from_secs(10)).await;
    }));
}
for task in tasks {
    task.await.unwrap();
}

Go

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

Both classic threads and JDK 21 preview virtual threads are benchmarked.

List<Thread> threads = new ArrayList<>();
for (int i = 0; i < numTasks; i++) {
    Thread thread = new Thread(() -> {
        try { Thread.sleep(Duration.ofSeconds(10)); } catch (InterruptedException e) {}
    });
    thread.start();
    threads.add(thread);
}
for (Thread thread : threads) {
    thread.join();
}
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) {}
    });
    threads.add(thread);
}
for (Thread thread : threads) {
    thread.join();
}

C#

List<Task> tasks = new List<Task>();
for (int i = 0; i < numTasks; i++) {
    Task task = Task.Run(async () => {
        await Task.Delay(TimeSpan.FromSeconds(10));
    });
    tasks.Add(task);
}
await Task.WhenAll(tasks);

Node.js

const delay = util.promisify(setTimeout);
const tasks = [];
for (let i = 0; i < numTasks; i++) {
    tasks.push(delay(10000));
}
await Promise.all(tasks);

Python

async def perform_task():
    await asyncio.sleep(10)

tasks = []
for _ in range(num_tasks):
    task = asyncio.create_task(perform_task())
    tasks.append(task)
await asyncio.gather(*tasks)

Elixir

tasks = for _ <- 1..num_tasks do
    Task.async(fn -> :timer.sleep(10000) end)
end
Task.await_many(tasks, :infinity)

Test Environment

CPU: Intel Xeon E3‑1505M v6 @ 3.00 GHz

OS: Ubuntu 22.04 LTS

Rust 1.69, Go 1.18.1, OpenJDK 21‑ea, .NET 6.0.116, Node v12.22.9, Python 3.10.6, Elixir 1.12.2 (Erlang/OTP 24)

All programs built in release mode with default settings.

Results

Minimum Memory (1 task)

Two groups emerge: native‑compiled Go and Rust binaries use the least memory, while managed runtimes (Java, .NET, Node, Python, Elixir) consume roughly an order of magnitude more. .NET shows the highest baseline usage.

Peak memory for 1 task
Peak memory for 1 task

10 000 Tasks

Java threads consume ~250 MB, confirming they are memory‑heavy. Rust native threads remain lightweight, staying below many runtimes’ idle memory. Go’s goroutine memory is ~50 % higher than Rust threads, contrary to expectations of a larger advantage.

Peak memory for 10,000 tasks
Peak memory for 10,000 tasks

100 000 Tasks

Thread‑based benchmarks cannot run at this scale on the author’s machine. Go falls behind Rust, Java, C#, and Node.js, using more than 12× the memory of the best Rust runtime. .NET’s memory remains oddly flat, suggesting pre‑allocation.

Peak memory for 100,000 tasks
Peak memory for 100,000 tasks

1 000 000 Tasks

Elixir initially fails with a system limit error; increasing the Erlang process limit (‑erl '+P 1000000') resolves it. Rust’s Tokio runtime still leads, while C# remains highly competitive, even slightly beating one Rust runtime. Go’s memory usage explodes, exceeding the best Rust implementation by >12× and Java by >2×.

Peak memory for 1,000,000 tasks
Peak memory for 1,000,000 tasks

Observations

Native‑compiled languages (Rust, Go) have the smallest memory footprint for small task counts, but Go’s per‑goroutine stack allocation (2 KiB) causes memory to grow faster than Rust threads.

.NET shows surprisingly low incremental memory usage, possibly due to pre‑allocation or high idle baseline.

Managed runtimes (Java, C#, Node.js, Python) start heavier but scale more predictably; C# performs especially well.

Elixir/Erlang can handle a million processes if the process limit is raised, using ~2.7 GiB.

Conclusion

Running a massive number of concurrent tasks can dominate memory consumption even when tasks are idle. Languages with low‑overhead native threads excel at small scales, while some runtimes with higher initial overhead handle large‑scale concurrency more gracefully. Future benchmarks will explore task‑startup latency and inter‑task communication.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaConcurrencyRustGoC#benchmarkasyncmemory consumption
IoT Full-Stack Technology
Written by

IoT Full-Stack Technology

Dedicated to sharing IoT cloud services, embedded systems, and mobile client technology, with no spam ads.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.