Comment by zamalek

9 months ago

Absolutely. Async/await typically improves headroom (scalability) at the cost of latency and throughput. It may also make code easier to reason about.

I disagree with this, you're probably not paying much (if at all) in latency or throughput for better scaling.

What you're paying for with async/await is a state machine that describes the concurrent task, but that state machine can be incredibly wasteful in size due to the design of futures and the desugaring pass that converts async/await into the state machine.

That's why I said it's not "zero cost" in the loosest definition of the phrase - you can write a better implementation by hand.

  • That is true. Rust's async/await desugaring is still missing optimizations. I think that will be ironed out eventually. What mainly concerns me about async/await is that, even with Rust's best efforts, the baseline complexity will probably always be somewhat higher than for sync code. I will be pleased if the gap is minimized and people only need to reach for async when they want to. Right now, the latter isn't the case because of the "virality [of] function coloring".

Definitely makes code harder to reason about.

  • If you were to write the same code without using async you'd be trudging through a mess of callbacks and combinators. This is what writing futures code before 2018 was like. It was doable if you needed the perf but it sucked. Async is a huge improvement to readability and reasoning that we didn't have before.

    • No, actually that was just javascript. Programming environments with threading models don't have to live that way. Separate threads can communicate through channels and do quite well for themselves. See how it works is, you do something like let data = file.read(); and the it just sits there on that line until the read is done and then your data has the actual bytes in it and you just use them and go on with your life.

      5 replies →

> at the cost of latency and throughput.

Compared to what?

Doing epoll manually?

  • A reactor has to move the pending task to some type of work queue. The task has to pulled off the work queue. The work queue is oblivious as to the priority of your tasks. Tasks aren't as expensive as context switching, but they aren't free either: e.g. likely to ruin CPU caches. Less code is fewer instructions is less time.

    If you care enough, you generally should be able to outdo the reactor and state machines. Whether you should care enough is debatable.

    • The cache thing is a thing I think a lot of people with a more... naive... understanding of machine architecture don't clue into.

      Even just synchronizing on an atomic can thrash branch prediction and L1 caches both, let alone working your way through a task queue and interrupting program flow to do so.

    • So yeah, you're thinking about the comparison between async/await and manual state machines management with epoll. But that's not what most people have in mind when you're saying async/await have performance impact, most of them would immediately think you're talking about the difference with threads.

  • If I'm not doing slow blocking I/O, I'm not doing epoll anyways.

    But the moment somebody drops async into my codebase, yay, now I get to pay the cost.

    • Either you are doing slow IO (in some of your dependency) or you don't have anyone dropping async in your code though…

  • Threading, probably.

    • Async/await isn't related to threading (although many users and implementations confuse them); it's a way of transforming a function into a suspendable state machine.

      4 replies →

    • I don't think so, because there isn't a performance drawback compared to threads when using async. In fact there's literally nothing preventing you from using a thread per task as your future runtime and just blocking on `.await` (and implementing something like that is a common introduction to how async executors run under the hood so it's not particularly convoluted).

      Sure there's no reason to do that, because non-blocking syscalls are just better, but you can…

      1 reply →