Live data from Hacker News

What async promised and what it delivered

causality.blog

261–270 of 317 posts

Re: What async promised and what it delivered

#261
post #228

Earlier quoted context omitted.

Relevant prior work: https://github.com/jimblandy/context-switch

Thank you for this! This is really helpful. The UMCG implementation allows kernel thread context switches to happen in 150-200 microseconds, compared to the 1500-2000 microseconds for normal kernel thread context switches. My goal is to show that if UMCG could be merged into the Linux run time then then it would be competitive with async rust without the headache.

Did you mean nanoseconds instead of microseconds?

Re: What async promised and what it delivered

#262
post #58

Earlier quoted context omitted.

Function coloring does not mean that functions take parameters and have return values. Result is not a color. You can call a function that returns a Result from any other function. Errors as return values do not color a function, they're just return values. Async functions are colored because they force a change in the rest of the call stack, not just the caller. If you have a function nested ten levels deep and it c…

You are stuck in a fixed pattern of thinking where async==color. Here's the meme origin: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... Here's the list of requirements: 1. Every function has a color. 2. The way you call a function depends on its color. 3. You can only call a red function from within another red function. 4. Red functions are more painful to call. 5. Some core library functions are re…

And you appear to have read my post way too hastily to get to the point you wanted to make, because I can't even correlate what you're saying to what I actually said, particular the argument that I'm "stuck" on async being the only color.

I have inside information about how that is not the case since I typed, but deleted before posting, some points about how Haskell is one of the few languages that can create arbitrary numbers of colors in libraries, but it started spiraling when I started trying to characterize when a new color was created. It is, for instance, not as simple as "it's monadic", because types that implement "monad" that allow extracting of the value like List or Maybe don't create colors even though IO does, so I just deleted it, especially when my thoughts turned to trying to explain how Haskell is also one of the few languages that can abstract over color. (Although I gather zig is giving it the college try with sync/async.) Using monad as an example is just an invitation to trouble since way more people think they understand it than actually do.

A function simply taking an annoying amount of parameters is not a color problem, because the entire essence of "color" is the transitivity. If you don't have the transitivity flowing up the call stack, you don't have color. Just being "hard to use" is not sufficient... the proof of which being that we've had "hard to call" functions for many more decades than we've had widespread "color" in our functions and nobody was talking about this transitivity problem until we started having async functions. That is a new problem... I mean, new on the relevant time frames... the whole color thing has been ongoing for years now, but it's still relatively new.

Re: What async promised and what it delivered

#263
post #203
post #87

> Language designers who studied the async/await experience in other ecosystems concluded that the costs of function coloring outweigh the benefits and chose different paths. Not really. The author provides Go as evidence, but Go's CSP-based approach far predates the popularity of async/await. Meanwhile, Zig's approach still has function coloring, it's just that one color is "I/O function" and the other is "non-I/O f…

That is an unfair characterization of Zig. The OP correctly points out: > Function signatures don’t change based on how they’re scheduled, and async/await become library functions rather than language keywords. The functions have the same calling conventions regardless of IO implementation. Functions return data and not promises, callbacks, or futures. Dependency injection is not function coloring.

The key question is whether an IO function can be called from a non-IO question, and vice versa.

Re: What async promised and what it delivered

#264

The discussion around async await always focuses on asynchronous use-cases, but I see the biggest benefits when writing synchronous code. In JS, not having await in front of a statement means that nothing will interfere with your computation. This simplifies access to shared state without race conditions. The other advantage is a rough classification in the type system. Not marking a function as async means that the…

Maybe I am missing something. But the function coloring problem is basically the tension that async can dominate call hierarchies and the sync code in between looses it's beneficial properties to a degree. It's at least awkward to design a system that smoothly tries to blend sync that executes fast and async code that actually requires it. Saying that fs.readSync shouldn't exist is really weird. Not all code written…

The function coloring problem represents multiple complaints. I disagree that the propagation of async makes the sync case irrelevant. In the frontend, receiving a promise has completely different implications on loading states. In the backend, I usually try to separate side-effects from pure functions, so the pure functions are usually sync.

Because JS is single threaded, fs.readSync will freeze the entire app. The only case where I would find that acceptable is in cli-scripts. But that could also be achieved with nodejs’ support for top-level await. There's perhaps a slight overhead from the Promise being created, but JS-Engines have so many optimizations that I don't even know if that matters. If nothing else is scheduled, awaiting a promise is functionally the same as blocking. Even in rare cases where you do want to block other scheduled events from running, you could achieve that with an explicit locking mechanism instead.

You could argue that filesystem access is fast so blocking everything is fine, but what if the file happens to be on a NAS somewhere?

Re: What async promised and what it delivered

#265

> OS threads are expensive: an operating system thread typically reserves a megabyte of stack space Why is reserving a megabyte of stack space "expensive"? > and takes roughly a millisecond to create I'm not sure where this number is from, it seems off by a few orders of magnitude. On Linux, thread creation is closer to 10 microseconds.

There's much ridiculous hatred for OS threads based on people's biases of operating systems and hardware from 20 years ago. So much so that they'll sign themselves up for async frameworks that thread steal at will and bounce things all over cores causing cache line bouncing and associated memory stalls, not understanding what this is doing to their performance profile. And endure complexity, etc. through awkward asyn…

[deleted]

Re: What async promised and what it delivered

#266
post #174

Earlier quoted context omitted.

hardware from 10 years ago - do you have benchmarks for more recent hardware? https://vorner.github.io/async-bench.html

I don't think those benches are much of a flex, even by the author's own description you'd be fine with any of them. They all have acceptable performance and don't show any order of magnitude differences or non-linear scaling problems. Further, the benches that are showing best there are non-thread-stealing scenarios, not tokio. I also suspect simply tuning the thread-based workloads more aggressively would have the…

You assumed

> Likely more efficient than half the async runtimes out there.

The benchmark shows the opposite: 2 (multithreaded async runtime) vs 7 (threads) * 10^8 ns per request for 2k requests/s.

> non-linear scaling problems.

oh, look closely, the relative gap increases with #requests/s

Re: What async promised and what it delivered

#267

Earlier quoted context omitted.

I write reams of async rust for a living, and completely disagree with this characterization. The concurrency primitives in the futures crate are able to elegantly model the large majority of places where concurrency is needed, and they are nicely compostable. More than once, we have wanted to improve the performance of some path and been able to lift the sequential model into a stream, evaluated concurrently with so…

I completely disagree. Having to make sure every little function is Send + Sync + lifetime even if it doesn't need it is fucking hell. writing concurrent code with plain kernel threads is so much easier to write and read. If you just want to build a normal backend service, you can't escape async libraries. Wrapping the async functions with `block_on` is not ideal I'd rather just have access to standard sync primitive…

You don’t have to make everything Send/Sync if you don’t need to. Use tokio’s local runtime and spawn_local(), or use one of the other async runtimes.

You also don’t need to spawn() futures to await them. Spawn enables parallelism on the multithreaded runtime, holding join handles, etc. If all you need is to execute concurrent code, though, the various combinators and functions in the futures crate lets you do so without having hard requirements on Send/Sync. The large majority of the concurrent code I write uses nothing specific from the tokio crate, including spawn.

As is often the case in rust, the compiler is also telling you the correct thing. If you’re using the multithreaded runtime and spawning, your code may execute in another thread, so it has to be Send/Sync, and since the ownership of the future is transferred to the executor, it must also be ‘static.

Re: What async promised and what it delivered

#268

Earlier quoted context omitted.

Inversion of thought pattern: Why is a thread such a waste that we can't have one per concurrent request? Make threads less wasteful instead. Go took things in this direction.

How do you suggest we just "make threads less wasteful"? I mean, I suppose we could move the scheduling and tracking out of kernel mode and into user mode... But then guess what we've just reinvented?

Goroutines? Java virtual threads?

Re: What async promised and what it delivered

#269
post #262

Earlier quoted context omitted.

You are stuck in a fixed pattern of thinking where async==color. Here's the meme origin: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... Here's the list of requirements: 1. Every function has a color. 2. The way you call a function depends on its color. 3. You can only call a red function from within another red function. 4. Red functions are more painful to call. 5. Some core library functions are re…

And you appear to have read my post way too hastily to get to the point you wanted to make, because I can't even correlate what you're saying to what I actually said, particular the argument that I'm "stuck" on async being the only color. I have inside information about how that is not the case since I typed, but deleted before posting, some points about how Haskell is one of the few languages that can create arbitra…

A function taking a new parameter that you have to pass all the way down the call stack is a color. If you have a large Haskell application, and then you decide that something 50 functions deep needs to access the user database, you've added a color. It's color if it affects the whole call stack in reality. You could pass an empty user database, but you obviously won't.

Re: What async promised and what it delivered

#270
post #193
post #183

Earlier quoted context omitted.

Could you point out the issue here? Why does async make it harder to reason about when resources are released?

Basically it’s the non-linear execution flow creating situations which are harder to reason about. Here’s an example I’m trying to help a Node team fix right now: something is blocking the main loop long enough that some of the API calls made in various places are timing out or getting auth errors due to the signature expiring between when the request was prepared and when it is actually dispatched because that’s spo…

I still don't get it.

The execution flows of individual async tasks are still linear, much like individual threads are linear.

Scheduling (tasks by the async runtime vs threads by the OS), however results in random execution order either way.

If there is a slow resource, both, async tasks as well as threads will pile potentially increasing response times.

Wether async or threads, you can easily put a concurrency limit on resources using e.g. semaphores [1]:

- limit yourself to x connections (either wait or return an error)

- limit the resource to x concurrent usages (either wait until other users leave, or return an error)

Regarding blocking the main loop: with async and non-blocking operations, how would something block the main loop? And why would the main loop being blocked cause API calls being timing out? Is it single threaded?

[1]: https://docs.rs/tokio/latest/tokio/sync/struct.Semaphore.htm...

Post reply on HN