Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

191–200 of 327 posts

Re: Local async executors and why they should be the default

#191
post #155
post #150

Earlier quoted context omitted.

I used/worked on a similar async model at FB long before I'd ever touched node. Threads are not really solving the same problem as async. Imagine you are building the FB newsfeed. You have 10 stories and want to fetch them as quickly as possible. The stories are all different types and each refers to other data: profile info, privacy, comments (which depend its own profile fetch), likes, etc. So you have this tree of…

I used "async" long before Node too. It's one of the reasons I knew to stay away from Node. "ad-hoc implementation of async" I think you're confusing the problem with the solution. I solve this sort of thing in Go all the time. This is literally what I was doing yesterday. It's fine in that context. You had to reach for async because it was the only option in your context, not because it was the best choice. Async as…

Go is internally doing async-like cooperative multitasking. You just don't see it because they actually integrated it into the language well.

Re: Local async executors and why they should be the default

#192
post #182

Earlier quoted context omitted.

The promise with a good architecture is that the doom part is more infrequent though. And that when the Go/Java doom scenario bites you you'll have a whole lot of technical debt to pay back.

But you're arguing in circles. If attention is paid, if the architecture is good, all else like programmer salary/skill being equal, any of those languages (and plenty of others) will work fine. (And honestly, not a huge difference in the work to achieve that, a good design will look about the same in all four.) It's only an interesting question which to use if attention is not paid, or you don't want to invest enoug…

Agree. But the argument put forward in favor of automatic memory management is that it speeds up development.

But if that is only true if you don't care about the architecture that argument doesn't hold (assuming you want a good architecture).

The claim I make is that you have to think about memory, either directly or indirectly. And if you have to think about it dealing with it yourself isn't a burden, it is liberating.

In in a similar sense to how a static type system is liberating. Which might sound a bit weird, until you need to refactor.

Re: Local async executors and why they should be the default

#193

Just a personal take: after not coding with Rust for several months, I find it more and more difficult to return to an async code I was writing. The whole thing just reads... ugly and inconsistent. It needs too much already-accumulated knowledge. As the article correctly points out, you need a bunch of stuff that are seemingly unrelated (and syntactically speaking you would never guess they belong together). And as o…

Honestly, is 150k events per second even a meaningful number to talk about in performance context? I've got Clojure code ingesting 150k messages per second, processing, then outputting 30k msgs per second sitting at 47% CPU on a cheapo 70 $/mo VPS. Straightforward unoptimized C/C++ achieves millions of ZeroMQ messages processed per second in a single thread.

Why use async or even Rust at all at such small loads?

Re: Local async executors and why they should be the default

#194
post #192

Earlier quoted context omitted.

But you're arguing in circles. If attention is paid, if the architecture is good, all else like programmer salary/skill being equal, any of those languages (and plenty of others) will work fine. (And honestly, not a huge difference in the work to achieve that, a good design will look about the same in all four.) It's only an interesting question which to use if attention is not paid, or you don't want to invest enoug…

Agree. But the argument put forward in favor of automatic memory management is that it speeds up development. But if that is only true if you don't care about the architecture that argument doesn't hold (assuming you want a good architecture). The claim I make is that you have to think about memory, either directly or indirectly. And if you have to think about it dealing with it yourself isn't a burden, it is liberat…

This is still a mostly circular argument though. An architecture is "good" if it's saving resources - compute, time, or labor. If your "good" architecture is slowing down development i.e. more time more labor, you'd better be able to point to significant compute gains or it's not actually good. Rust is, only sometimes, and marginally, better than other languages in this regard.

And your compute gains need to be something you can't just trade off against money for development time, i.e. it either needs to be a fundamental part of your problem, or you need to be planning for hyperscale. This is also only a small fraction of problems.

Re: Local async executors and why they should be the default

#195

Earlier quoted context omitted.

I am responding to the very last point about dropping a future and the resource usage. Let's assume a future that implements reading from a network socket. And the file descriptor (fd) was already passed to epoll/select (I am ignoring io_uring). After dropping the future, the socket is is still being considered by the kernel for reading. Eventually epoll will return, and the IO runtime (like tokio) will notice the st…

I'm not sure how an actor would solve that problem. You're describing the way a system call works, which is not relevant to how your userland asynchrony works. In either case you're always "leaking" (I'd say "holding") the resource while you wait for the kernel to give it back to you.

Indeed I was merely describing how it behaves within the context of future + ioloop with tokio. I didn't take a stance on future vs actor.

In Rust my instinct tells me the difference between actor and future is negligible. Because it compiles down to almost the same thing. But I wouldn't bet my life on it.

Note that my understanding of what an actor is might be wrong. I assume an actor is a sort of thread with a main ioloop receiving and sending messages. Which is basically what a tokio ioloop thread is. Atop of which the futures give you an abstractions to compose IO functions more easily.

Re: Local async executors and why they should be the default

#196
post #192

Earlier quoted context omitted.

Agree. But the argument put forward in favor of automatic memory management is that it speeds up development. But if that is only true if you don't care about the architecture that argument doesn't hold (assuming you want a good architecture). The claim I make is that you have to think about memory, either directly or indirectly. And if you have to think about it dealing with it yourself isn't a burden, it is liberat…

This is still a mostly circular argument though. An architecture is "good" if it's saving resources - compute, time, or labor. If your "good" architecture is slowing down development i.e. more time more labor, you'd better be able to point to significant compute gains or it's not actually good. Rust is, only sometimes, and marginally, better than other languages in this regard. And your compute gains need to be somet…

> If your "good" architecture is slowing down development i.e. more time more labor [...]

The argument is that it isn't slowing down development in the long run. Because it is a fundamental part of your architecture anyway.

(And my argument has nothing to do with Rust per se)

Re: Local async executors and why they should be the default

#197

Earlier quoted context omitted.

Yes, I suspect you're right. I do not like building giant monoliths with internal task systems. I prefer using async/await on the inside and microservices with rpcs or, ideally, streams for communication. I think perhaps people are trying to push too much into a single process?

> I think perhaps people are trying to push too much into a single process? That's exactly what's happening. A bit of Erlang/Elixir experience would help a lot of people to create architectures (even in other languages) that do not require such kludges. If your application starts to take on the kind of complexity that you normally deal with a monolithic OS kernel you're doing something terribly wrong.

And then people are saying that their microservice is high performance because it's handling 10 000 req/s per 72 core node... which is ok but rather per core (depending on the amount and type of logic being processed), not node. I'm handling requests in high microseconds range (median) in high volume, low latency network service with hard limits on latency numbers (not HFT). Good luck building that kind of service (a lot of constantly changing state) with only microservices with less complexity and higher throughput then in monolith or semi monolith with a lot of internal state guarded by different synchronization mechanisms. I/O overhead alone would eat you.

Re: Local async executors and why they should be the default

#198
post #158

Just a personal take: after not coding with Rust for several months, I find it more and more difficult to return to an async code I was writing. The whole thing just reads... ugly and inconsistent. It needs too much already-accumulated knowledge. As the article correctly points out, you need a bunch of stuff that are seemingly unrelated (and syntactically speaking you would never guess they belong together). And as o…

Rust sweetspot is really for use cases where any kind of automatic memory management is forbidden, either due to real use case requirements (high integrity computing, kernel drivers,...), or due to existing domain culture that frowns upon any other kind of alternatives. For everything else, there are more productive alternatives, even Go, which after generics is kind of ok.

Automatic memory management is not exclusively positive, even if you don't care about performance.

If you lose deterministic destructors and therefore the possibility of RAII, for me it is a loss.

I worked with lots of garbage collected languages (Java, C#, Scala) before rust, and I don't miss garbage collection at all...

Re: Local async executors and why they should be the default

#199

Earlier quoted context omitted.

> I think perhaps people are trying to push too much into a single process? That's exactly what's happening. A bit of Erlang/Elixir experience would help a lot of people to create architectures (even in other languages) that do not require such kludges. If your application starts to take on the kind of complexity that you normally deal with a monolithic OS kernel you're doing something terribly wrong.

And then people are saying that their microservice is high performance because it's handling 10 000 req/s per 72 core node... which is ok but rather per core (depending on the amount and type of logic being processed), not node. I'm handling requests in high microseconds range (median) in high volume, low latency network service with hard limits on latency numbers (not HFT). Good luck building that kind of service (a…

Any design pattern can be applied in a wrong way.

Re: Local async executors and why they should be the default

#200

Earlier quoted context omitted.

If you want golang in Rust just use channels and tasks? Or threads? I don't find async Rust difficult at all, I'm having a hard time really empathizing with this to the extent of needing breaking changes. To me, async from a lang perspective is virtually done - in 2024 I suspect all of the various impl Trait and async Trait stuff will be done and at that point I don't see anything left.

I do that of course, and that's one of the easiest ways to use async Rust. In real projects you need much more however. F.ex. I had to code an example of how to add tasks to an already running pool of tasks and posted my findings here: https://github.com/dimitarvp/rust-async-examples/blob/main/e... (there's #2 as well with some more comments and a different approach). The fact that I needed to make a GitHub repo and…

`flowmatic.Do` is equivalent to `future::try_join` (https://docs.rs/futures/latest/futures/future/fn.try_join.ht...), and `.Each` is equivalent to `stream::iter().map().buffer_unordered()` (https://docs.rs/futures/latest/futures/stream/fn.iter.html, https://docs.rs/futures/latest/futures/stream/trait.StreamEx..., https://docs.rs/futures/latest/futures/stream/trait.StreamEx...).

`.ManageTasks` doesn't seem to have a clear analogue that I can think of right now, that's an interesting one.

`.TaskPool` looks like `StreamExt::buffer_unordered` unless I'm missing something. The code example in the README didn't really say a lot for someone who isn't already well-versed in the library.

Post reply on HN