Live data from Hacker News

Comparison of Rust async and Linux thread context switch time and memory use

github.com

121–130 of 201 posts

Re: Comparison of Rust async and Linux thread context switch time and memory use

#121
post #5

That's a huge help. I only need about 20 threads in Rust, some of which are compute-bound. So involving "async" is totally the wrong tool for the job. Goodbye, Tokio.

> So involving "async" is totally the wrong tool for the job. Sadly with so many things having gone async-first (or only) it’s become difficult not to end up with an async runtime anyway, or not be forced to use an async system. I wanted to build a small web-based tool for local, didn’t really find anything which was not async.

mio and mio_httpc are options if you're the kind of person like me who finds 'async' worse than event loops.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#122

Performance profiling on a laptop is largely pointless. There's too much stuff trying to conserve power by limiting performance.

Are your users going to be running your application on laptops? Will they have the same "conserve power by limiting performance" going on? If so, that is _exactly_ the environment you want to do performance work in, generally speaking.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#123

The problem with threads is you need to correctly size your thread pool. That's hugely difficult if you have unknown lengths of blocking IO.

what kind of problem were you trying to solve that you found sizing a thread pool to be difficult? generally when I've worked on high performance server code I've been coding with a target machine in mind, so it's more a matter of mapping the thread pool size to the resources available on that machine. but I'm interested to hear about circumstances where it wouldn't be easy.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#124

Earlier quoted context omitted.

Ease of understanding multithreaded code and wait on results or perform standard control flow constructs in a multithreaded environment? This is a great example in Node on useful combinators that with async await make it easy to express parallel programming concepts with familiar tools. No manual IPC, no fork/join child PID/thread ID handling, etc. https://github.com/sindresorhus/promise-fun The same abstractions (or…

> Ease of understanding multithreaded code Rust is not Javascript. Using threads is actually a lot simpler in Rust than async/await.

I don’t know about Rust but in every other language I’ve used threads were easy to use and understand, except when it came to some bits like signals, which at least on Linux are no longer a big problem. Main thread runs a hot loop to look for data to process, then hands it off on a queue to a worker thread out of a pool. That thread is then solely responsible for processing the event and passing the result either back to the main thread or to the next thread in the pipeline via the same queue mechanism. Last thread to handle the result or the exception frees the resources. It might not be ergonomic for all types of code but it certainly isn’t hard to understand what everything is doing and easy enough to debug since each thread can be tested individually to check its functionality.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#125

Performance profiling on a laptop is largely pointless. There's too much stuff trying to conserve power by limiting performance.

Are your users going to be running your application on laptops? Will they have the same "conserve power by limiting performance" going on? If so, that is _exactly_ the environment you want to do performance work in, generally speaking.

generally speaking, the advantage of async io is strongest for high performance server applications, especially in regards to the cpu usage required relative to the amount of io stuff you can do. with that in mind, "users running your application on laptops" would not be the most common case.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#126
post #75

> People often see that there's some theoretical benefit of async and then they accept far less ergonomic coding styles and the additional bug classes that only happen on async due to accidental blocking etc... despite the fact that when you consider a real-world deployed application, those "benefits" become indistinguishable from noise. However, due to the additional bug classes and worse ergonomics, there is now le…

It seems to me that these are two orthogonal topics. One thing is how you represent tasks, either using OS threads or async tasks. And the other is how you structure concurrency. Maybe I'm missing something, but I think there is nothing preventing the use of those structured concurrency patterns using OS threads as the base for tasks. Then you get some nice benefits of doing this such as proper stack-traces and easie…

The advantage of async tasks for structured concurrency lies in task cancellation, which is intrinsically linked to the notion of "task ownership". If you are using an OS thread to offload some task, and then realize that you don't need that task's result anymore, your safest bet is to let the thread run until the end and then discard the results it produces. Other options include adding custom cancellation logic to the thread and remembering to call it at the appropriate time. Nobody checks that you are doing this correctly, which means you may leak resources such as the thread's memory or a TCP connection. On the other hand when using async/await in Rust, the fact of owning a future (i.e. owning the promise that will return you the value when it's done) implies ownership of the task's resources, such as memory, file descriptors, or TCP connections. Dropping the future before it completes means that the task will stop and all resources will be freed/closed immediately, and this is checked statically by the compiler.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#127
post #74
post #32

Earlier quoted context omitted.

Keep in mind that a new async task doesn't create a new thread. So yes, "not creating a new thread" is 3x faster than "creating a thread". If the app layer can context switch using language level constructs, and do co-operative switching, then yes, one gets the 3x benefit. imho, whether the async executor and scheduler is performant enough to manage the tasks is what one should worry about.

I'm confused. If many async tasks are ran on a single thread, what the thread does when is blocked waiting for things to happen? Does it sleep? If so, a context switch takes place anyway. If not, what is the impact on GUI applications? If I have a main thread to manage my GUI, should I spin a new thread to run my async tasks? A modern microcontroller/microprocessor is inherently event driven (for example, on ARM, at…

in rust, there is no built-in runtime, so it depends on which one you are using. the runtime (e.g. tokio) is responsible for polling the future.

for network io, behind the scenes this is most likely utilizing epoll system calls. epoll mitigates context switch problem in a few ways, mostly because there is only one stack context to notify about new io events, instead of many.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#128
post #77
post #75

> People often see that there's some theoretical benefit of async and then they accept far less ergonomic coding styles and the additional bug classes that only happen on async due to accidental blocking etc... despite the fact that when you consider a real-world deployed application, those "benefits" become indistinguishable from noise. However, due to the additional bug classes and worse ergonomics, there is now le…

That's interesting to hear - but how much of an investment is it to climb the that mountain (or hill) that makes you comfortable with working with the async model?

I'm not the best qualified to answer this question as I do spend a lot of time reading about programming languages in general, and even though I was able to grasp Rust's async/await very fast, I probably owe it to previous knowledge of a relatively large variety of programming paradigms. I'll thus rely on other commenters that seem to agree that it's really not as hard as you would expect. In particular Rust helps a lot in making sure you don't make too many mistakes so I'd wager that learning to do correct async/await in Rust is probably easier than in, say, Javascript.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#129
post #100

Earlier quoted context omitted.

In Javascript, this is a typical rookie mistake. Every newcomer would do it once, get lectured about `Promise.all` in code review, and move on. Honestly, I'd be really surprised if this was a common practice in C#.

Rust doesn’t allow you to do this.

All you have to do is wrap multiple futures into a single one and then await on the combined one. There is no programming language on earth that can prevent this.

Re: Comparison of Rust async and Linux thread context switch time and memory use

#130

Earlier quoted context omitted.

Speaking of futures_unordered and similar patterns, I think a part of the "async promise" that has failed is the lack of concurrency for a single user request by default in most languages. That is, the 'easy' path is to write code such as the following (in vaguely C# pseudocode): var p = await GetUserPermission( username ); var c = await GetServerConfig(); var m = await GetMessageOfTheDay(); Assume each await call is…

I've been using C# for around 20 years, basically since it was first released. I never personally had any issue with working with threads and locks, finding it simple enough to reason about them, though I understand lots of people felt differently. When async/await first came to C# around 10 years ago, I grumbled because I didn't see the point; I found it much harder to reason about the flow of code, and initially at…

I find your comment about stack traces a bit weird: of course, when all your work is sequential and you can use only threads, you will have a nice stack trace for free, when async stack traces need a lot of support from the tooling.

But most of the time you not only use thread, but also several synchronization primitives (locks, channel, etc.) and when doing so, regarding stack trace you are in an even worst situation than what async stack traces gives you (“some thread changed this shared-memory value and now it's not what you expected, but you have no easy way to know which one did and when, good luck”).

Post reply on HN