Live data from Hacker News

How much memory do you need in 2024 to run 1M concurrent tasks?

hez2010.github.io

41–50 of 205 posts

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#41

I feel this benchmark compares apples to oranges in some cases. For example, for node, the author puts a million promises into the runtime event loop and uses `Promise.all` to wait for them all. This is very different from, say, the Go version where the author creates a million goroutines and puts `waitgroup.Done` as a defer call. While this might be the idiomatic way of concurrency in the respective languages, it do…

Actually, I think this benchmark did the right thing, that I wish more benchmarks would do. I'm much less interested in what the differences between compilers are than in what the actual output will be if I ask a professional Go or Node.js dev to solve the same task. (TBF, it would've been better if the task benchmarked was something useful, eg. handling an HTTP request.)

Go heavily encourages a certain kind of programming; JavaScript heavily encourages a different kind; and the article does a great job at showing what the consequences are.

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#43

I feel this benchmark compares apples to oranges in some cases. For example, for node, the author puts a million promises into the runtime event loop and uses `Promise.all` to wait for them all. This is very different from, say, the Go version where the author creates a million goroutines and puts `waitgroup.Done` as a defer call. While this might be the idiomatic way of concurrency in the respective languages, it do…

As far as I know there is no way to do Promise like async in go, you HAVE to create a goroutine for each concurrent async task. If this is really the case then I believe the submition is valid. But I do think that spawning a goroutine just to do a non-blocking task and get its return is kinda wasteful.

You could in theory create your own event loop and then get the exact same behaviour as Promises in Go, but you probably shouldn't. Goroutines are the way to do this in Go, and it wouldn't be useful to benchmark code that would never be written in real life.

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#44

Earlier quoted context omitted.

I don't think 1M posix threads is a thing. 1K is no big deal though.

~100k is a thing on Linux.

On Linux, 400k threads was a thing on even on 2002 hardware that was 32-bit and thus limited to 4GB: https://news.ycombinator.com/item?id=37621887

In the titular post there's a link to a previous comparison between approaches, and plain OS threads used from Rust fare quite well, even if the author doesn't up the OS limits to keep that in the running for the higher thread cases: https://pkolaczk.github.io/memory-consumption-of-async/

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#45
post #7

This depends a lot on how you define "concurrent tasks", but the article provides a definition: Let's launch N concurrent tasks, where each task waits for 10 seconds and then the program exists after all tasks finish. The number of tasks is controlled by the command line argument. Leaving aside semantics like "since the tasks aren't specified as doing anything with side effects, the compiler can remove them as dead c…

> I have no idea what Go is doing to get 2500 bytes per task. TFA creates a goroutine (green thread) for each task (using a waitgroup to synchronise them). IIRC goroutines default to 2k stacks, so that’s about right. One could argue it’s not fair and it should be timers which would be much lighter. There’s no “efficient wait” for them but that’s essentially the same as the appendix rust program.

Fair or not, it’s a strange way to count - Go isn’t using that RAM. It’s preallocating it because any real world program will.

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#46

I feel this benchmark compares apples to oranges in some cases. For example, for node, the author puts a million promises into the runtime event loop and uses `Promise.all` to wait for them all. This is very different from, say, the Go version where the author creates a million goroutines and puts `waitgroup.Done` as a defer call. While this might be the idiomatic way of concurrency in the respective languages, it do…

The requirement is to run 1 million concurrent tasks. Of course each language will have a different way of achieving this task each of which will have their unique pros/cons. That's why we have these different languages to begin with.

The accounting here is weird though; Go isn’t using that RAM, it’s expecting the application to. The reason that doesn’t happen is because it’s a micro benchmark that produces no useful work..

The way the results are presented a reader may think the Go memory usage sounds equivalent to the others - boilerplate, ticket-to-play - and then the Go usage sounds super high.

But they are not the same; that memory is in anticipation of a real world program using it

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#48
post #41

I feel this benchmark compares apples to oranges in some cases. For example, for node, the author puts a million promises into the runtime event loop and uses `Promise.all` to wait for them all. This is very different from, say, the Go version where the author creates a million goroutines and puts `waitgroup.Done` as a defer call. While this might be the idiomatic way of concurrency in the respective languages, it do…

Actually, I think this benchmark did the right thing, that I wish more benchmarks would do. I'm much less interested in what the differences between compilers are than in what the actual output will be if I ask a professional Go or Node.js dev to solve the same task. (TBF, it would've been better if the task benchmarked was something useful, eg. handling an HTTP request.) Go heavily encourages a certain kind of progr…

> Go heavily encourages a certain kind of programming;

True, but it really doesn't encourage you to run 1m goroutines with the standard memory setting. Though it's probably fair to run Go wastefully when you're comparing it to Promise.All.

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#49
post #41

Earlier quoted context omitted.

Actually, I think this benchmark did the right thing, that I wish more benchmarks would do. I'm much less interested in what the differences between compilers are than in what the actual output will be if I ask a professional Go or Node.js dev to solve the same task. (TBF, it would've been better if the task benchmarked was something useful, eg. handling an HTTP request.) Go heavily encourages a certain kind of progr…

> Go heavily encourages a certain kind of programming; True, but it really doesn't encourage you to run 1m goroutines with the standard memory setting. Though it's probably fair to run Go wastefully when you're comparing it to Promise.All.

Of course! That's why the article is telling you that some languages (C#, Rust) are better at it than others (Go, Java). Doesn't mean that Go and Java are bad languages! Just that they aren't good to do this thing.

Re: How much memory do you need in 2024 to run 1M concurrent tasks?

#50
post #43

Earlier quoted context omitted.

As far as I know there is no way to do Promise like async in go, you HAVE to create a goroutine for each concurrent async task. If this is really the case then I believe the submition is valid. But I do think that spawning a goroutine just to do a non-blocking task and get its return is kinda wasteful.

You could in theory create your own event loop and then get the exact same behaviour as Promises in Go, but you probably shouldn't. Goroutines are the way to do this in Go, and it wouldn't be useful to benchmark code that would never be written in real life.

I guess what you can do in golang that would be very similar to the rust impl would be this (and could be helpful even in real life, if all you need is a whole lot of timers):

  func test2(count int) {
  
   timers := make([]*time.Timer,count)
   for idx, _ := range timers {
    timers[idx] = time.NewTimer(10 * time.Second)
   }
   for idx, _ := range timers {
    
This yields to 263552 Maximum resident set size (kbytes) according to /usr/bin/time -v

I'm not sure if I missed it, but I don't see the benchmark specify how the memory was measured, so I assumed the time -v.

Post reply on HN