Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

41–50 of 327 posts

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

#41
> If you know anything about asynchronous sockets it should be that multi-threading a socket doesn't actually yield you more requests / second, and it can actually lower it...

Re-read this a few times, and I'm fairly convinced it is not generally true. The author is also being a bit confusing about what exactly he means by "socket" here. Because while it's true that multi-threading over a server socket (e.g. the one that binds to the port when you launch the server) will not yield performance gains, multi-threading clients (that have their own sockets, including file descriptors) definitely will. That's the whole point of nginx thread pools[1]. Note that nginx does zero "CPU-bound" work, it literally just serves files.

Node/Deno being single-threaded is purely a limitation of Javascript. Tomcat, Jetty, etc. are all multi-threaded. I'm a bit tired, so I can't comment on the rest of the post in detail, but this was a bit of a red flag.

[1] https://gist.github.com/denji/8359866

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

#42

Async is and probably will always be less usable than blocking Rust. It is a very, very useful mode of operating when you really need two of its biggest benefits: lightweight cooperative concurrency and task cancellation, but it comes at a big usability cost. Rust software should use async tactically - in places where it is needed. Unfortunately handling http, which is a large part of many applications is actually a…

Your CRUD web application server almost certainly doesn't need async Rust. Using a blocking HTTP server is not "might be a good idea", it simply is a good idea.

I recommend Rouille for this: https://github.com/tomaka/rouille. In case you are worried about performance, check the benchmark. Blocking Rouille is faster than builtin async server in Node.js.

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

#43
post #40
post #37

Can a mod fix the title please? The poster of this story has editorialized the title so bad that it has no connection with the actual title. Actual title: Local Async Executors and Why They Should be the Default Posted title: Async rust – are we doing it all wrong? Really? Why this kind of terrible editorializing?

I agree that this could be less editorialized, but the actual title doesn't include 'Rust', so that needs to be added at least.

Agreed. Perhaps we can have a syntax to declare what is editorialized. We already add "(YEAR)" and "[pdf]" and "[video]". We could "[Rust]" or other keywords like that about the post missing from the title?

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

#44
post #39
post #35

I find all the async stuff in Rust incredibly ugly, cumbersome, and its one of the biggest reasons I prefer C++, still. C++ lets me just write single- or multithreaded code, because none of the dependencies force their `async` stuff on me. Yeah, its up to me to ensure things are synchronized, but I'd rather do that than try to figure out how to get some dependency that isnt meant to use async to work in some async mo…

I can see where you are coming from. But you can do most things purely in sync rust. There are sync rust multithreading libs like rayon that are a joy to use, and there are even blocking versions of popular http libraries like reqwest: https://docs.rs/reqwest/latest/reqwest/blocking/index.html They are usually doing some ugly stuff internally to make this work, but as a pure library user you don't have to care. If yo…

Having written a lot of async and sync rust, and having both components interact with one another, calling async stuff from a sync context isn't even that ugly these days. But maybe it's stockholm syndrome.

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

#45
> Making things thread safe for runtime-agnostic utilities like WebSocket is yet another price we pay for making everything multi-threaded by default. The standard way of doing what I'm doing in my code above would be to spawn one of the loops on a separate background task, which could land on a separate thread, meaning we must do all that synchronization to manage reading and writing to a socket from different threads for no good reason.

Why so? Libraries like quinn[1] define "no IO" crate to define runtime-agnostic protocol implementation. In this way we won't suffer by forcing ourselves using synchronization primitives.

Also, IMO it's relatively easy to use Send-bounded future in non-Send(i.o.w. single-threaded) runtime environment, but it's almost impossible to do opposite. Ecosystem users can freely use single threaded async runtime, but ecosystem providers should not. If you want every users to only use single threaded runtime, it's a major loss for the Rust ecosystem.

Typechecked Send/Sync bounds are one of the holy grails that Rust provides. Albeit it's overkill to use multithreaded async runtimes for most users, we should not abandon them because it opens an opportunity for high-end users who might seek Rust for their high-performance backends.

[1]: https://github.com/quinn-rs/quinn

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

#46
post #28

Earlier quoted context omitted.

I wonder if HN should have a rule that prohibits editorializing.

Not sure if this is facetious, but HN does have a rule that prohibits editorializing. From the Guidelines: "Otherwise please use the original title, unless it is misleading or linkbait; don't editorialize."

No, I was genuinely asking because I had found many HN posts were editorialized.

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

#47
post #44
post #39

Earlier quoted context omitted.

I can see where you are coming from. But you can do most things purely in sync rust. There are sync rust multithreading libs like rayon that are a joy to use, and there are even blocking versions of popular http libraries like reqwest: https://docs.rs/reqwest/latest/reqwest/blocking/index.html They are usually doing some ugly stuff internally to make this work, but as a pure library user you don't have to care. If yo…

Having written a lot of async and sync rust, and having both components interact with one another, calling async stuff from a sync context isn't even that ugly these days. But maybe it's stockholm syndrome.

It's OK but not great.

Ideally library authors should provide a sync facade so people don't have to deal with this, like reqwest does.

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

#48
This article is against multithreaded executors by default and that synchronous code is easier to read and more practical than async code. I completely understand this.

My hobby and main interest is multithreading, async, coroutines, parallelism so I love articles like this so thank you.

I am trying to design a solution that lets us have our cake and eat it to. I want multithreaded coroutines or multithreaded async executors by default. I am trying to design a server and runtime that is largely parallel, concurrent, efficient, easy to understand, easy to reason about, async and easy to read and maintain. I want:

* Tokio is not bad but I think a codebase that uses async rust requires a high level of skill, cognitive load and understanding. It's not straightforward!

* synchronous straight-line flow code is the easiest to read and follow

* promises and callbacks aren't easy to read or follow control flow

* making a single threaded program parallel after writing it is almost a complete rewrite

* work stealing thread pools solve the starvation problem, so they're good!

* IO shouldn't block CPU and CPU shouldn't block IO

* I am trying to design a syntax that is data orientated that means programs can be parallelised after writing them, so parallelisation comes for free

* we can use the LMAX Disruptor pattern for efficient cross-thread communication. I use a lockfree multiconsumer multiproducer ringbuffer in my programs.

I have an epoll-server which multiplexes clients/sockets over threads, this is more efficient than a thread-per-socket/client. I need to change it into a websocket server.

Imagine you're a search engine company and you want to index links between URLs. How would you model this with async rust and thread pools?

  task download-url
   for url in urls:
    download(url)

  task extract-links
   parsed = parse(document)
   return parsed

  task fetch-links
   for link in document.query("a")
    return link

  task save-data
   db.save(url, link)
How would you do control flow and scheduling and parallelism and async efficiently with this code?

`db.save()`, `download()` are IO intensive whereas `document.query("a")` and `parse` is CPU intensive.

I think its work diagram looks like this: https://github.com/samsquire/dream-programming-language/blob...

I've tried to design a multithreaded architecture that is scalable which combines lightweight threads + thread pools for work + control threads for IO epoll or liburing loops:

Here's the high level diagram:

https://github.com/samsquire/ideas5/blob/main/NonblockingRun...

The secret is modelling control flow as a data flow problem and having a simple but efficient scheduler.

I wrote about schedulers here and binpacking work into time:

https://github.com/samsquire/ideas4#196-binpacking-work-into...

I also have a 1:M:N lightweight thread scheduler/multiplexer:

https://github.com/samsquire/preemptible-thread

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

#49
post #18

This is bad editorializing. You're putting words in the author's mouth that cannot even be found on that page. Don't do that. Edit: Thanks to the mods or whoever fixed it.

If anyone is confused what the "bad editorializing" is ...

Actual title of article: Local Async Executors and Why They Should be the Default

The title posted to HN: Async rust – are we doing it all wrong?

The title has been fixed now. But yeah it is annoying to see badly editorialized titles. It is against the guidelines -- https://news.ycombinator.com/newsguidelines.html

> Otherwise please use the original title, unless it is misleading or linkbait; don't editorialize.

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

#50
post #21

> Yes the RwLock and mpsc comes from Tokio and lets you .await instead of blocking a thread, but these are not async primitives, these are multi-threading synchronization primitives. The only reason all this async stuff even exists is because we want concurrency. We want to say "while this one task waits for I/O, this other task will do stuff". So it's not too surprising to me that an intro to async would include syn…

Multi threading and concurrency are not the same. You can have a very high performance server that handles thousands of requests concurrently on a single thread. That's how node/deno do things. But the way to do things in async rust is that if you want concurrency you also have to use multithreading. At least that is what you see in all the examples and docs. As soon as you require your futures to be Send you have to…

> So e.g. you have to use Arc>

In terms of C++ code that would equate to std::shared_ptr> which ... sounds quite wasteful in terms of scalability/performance. Why is it not possible to simply return a Rust promise? That's the way I do it in my C++ async (executor) library backed by work-stealing queues under the hood.

Post reply on HN