Hacker News new | ask | show | jobs
by jlokier 1831 days ago
> async/await places a burden of knowledge on the developer to explicitly avoid this problem

How many large projects do you know where the developers know every CPU run-length histogram of every library dependencies and their transitive dependencies? Even if they can measure them, they can't realistically alter many of them.

And how many library developers do you know who know the CPU run-length histogram expectations of everything that depends on them, as well as their own dependencies?

In the Go model, the system balances competing modules dynamically in response to unpredictable load patterns, and ensures some amount of fairness. Something which is called, no matter how deep in the call stack and no matter how many steps removed from another module, can do some computation and it doesn't severely affect the rest of the program. In particular, it doesn't cause a giant spike in latency of processing unrelated things.

In the Rust model, timings of totally unrelated modules have a stronger effect on each other. Unrelated modules are not as decoupled. To be conservative, especially in a library, it's better to avoid any lengthy computations in your async functions, breaking them up in to smaller parts just in case something unrelated needs to be able to make progress. In such cases, preemption is more efficient.

The Rust model also leads to an interesting metastability in library design motivation among independent developers. A library that provides an async API and breaks its work up into many small, non-sequentially-dependent tasks will tend to get a higher share of CPU execution than one which uses fewer large tasks for the same job - because the scheduler is not trying to be fair. So there's an incentive for every library developer to break things up into many small async tasks, to make their own library perform better, even though that is less efficient overall.

Overall, I think the Rust async scheduling model is better suited to smaller programs than the Go scheduling model.

1 comments

> In the Go model, the system balances competing modules dynamically in response to unpredictable load patterns, and ensures some amount of fairness. Something which is called, no matter how deep in the call stack and no matter how many steps removed from another module, can do some computation and it doesn't severely affect the rest of the program. In particular, it doesn't cause a giant spike in latency of processing unrelated things.

There's some kind of magical thinking here. The Go runtime attempts to hide as much complexity as it can, and while it works OK most of the time, there are a lot of edge cases that the runtime doesn't handle well [1]. And implementing a runtime that handle these things automagically is a hard task, and nasty bugs ensue[2].

Also, scheduler fairness isn't related to the language itself, since Rust doesn't ship a scheduler the runtime being a third-party library.

[1]: https://github.com/golang/go/issues/36365 [2]: https://github.com/golang/go/issues/40722