Most active commenters
  • amelius(6)
  • neonz80(4)
  • ajross(3)

←back to thread

Async/Await is finally back in Zig

(charlesfonseca.substack.com)
39 points barddoo | 18 comments | | HN request time: 0.001s | source | bottom
Show context
ajross ◴[] No.45782414[source]
Is it time now to say that async was a mistake, a-la C++ exceptions? The recent futurelock discussion[1] more or less solidified for me that this is all just a mess. Not just that one bug, but the coloring issue mentioned in the blog post (basically async "infects" project code requiring that you end up porting or duplicating almost everything -- this is especially true in Python). The general cognitive load of debugging inside out code is likewise really high, even if the top-level expression of the loop generator or whatever is clean.

And it's all for, what? A little memory for thread stacks (most of which ends up being a wash because of all the async contexts being tossed around anyway -- those are still stacks and still big!)? Some top-end performance for people chasing C10k numbers in a world that has scaled into datacenters for a decade anyway?

Not worth it. IMHO it's time to put this to bed.

[1] No one in that thread or post has a good summary, but it's "Rust futures consume wakeup events from fair locks that only emit one event, so can deadlock if they aren't currently being selected and will end up waiting for some other event before doing so."

replies(5): >>45782432 #>>45782502 #>>45782558 #>>45782647 #>>45782786 #
1. amelius ◴[] No.45782432[source]
What is wrong about C++ exceptions?
replies(3): >>45782469 #>>45782488 #>>45782723 #
2. ajross ◴[] No.45782469[source]
"Nothing", in principle. But they're bug factories in practice. It's really easy to throw "past" cleanup code in a language where manual resource management remains the norm.

It's not that they can't be used productively. It's that they probably do more harm than good on balance. And I think async mania is getting there. It was a revelation when node showed it to us 15 years ago. But it's evolved in bad directions, IMHO.

replies(2): >>45782508 #>>45782633 #
3. KerrAvon ◴[] No.45782488[source]
For one thing, they’re expensive and viral. “Zero overhead” implementations don’t take into account the need for unwind tables. For every function/method that might be thrown across. They’re disabled in a lot of production environments for this reason.
replies(2): >>45782913 #>>45783042 #
4. baq ◴[] No.45782508[source]
Yeah node showed that a native async single threaded runtime can be performant. Seems like this knowledge was lost to the world somewhere along windows vista; everyone who has had to ever develop in the cooperative world of early winapi could tell you that easily.
5. efdee ◴[] No.45782633[source]
C# had async/await long before Javascript/node. Not that big a revelation ;-)
replies(1): >>45782863 #
6. jandrewrogers ◴[] No.45782723[source]
There are cases in systems-y code where it is not safe to unwind the stack in the ordinary way and it is difficult to contain the side-effects. These can be non-obvious and subtle edge cases that are often difficult to see and tricky to handle correctly. C++ today is primarily used in code contexts where these kinds of issues can occur. This is why it is a standard practice to disable exceptions at build time i.e. -fno-exceptions.

With the benefit of hindsight, explicit handling and unwinding has proven to be safer and more reliable.

replies(1): >>45782951 #
7. ajross ◴[] No.45782863{3}[source]
.NET wasn't the first either. Lisps were doing continuations in the 70's.

But "invented" and "revealed" are different verbs for a reason. The release of node.js and it's pervasively async architecture changed the way a lot of people thought about how to write code. For the better in a few ways. But the resulting attempt to shoe-horn the paradigm into legacy and emerging environments that demanded it live in a shared ecosystem with traditional "blocking" primitives and imperative paradigms has been a mess.

replies(1): >>45783231 #
8. amelius ◴[] No.45782913[source]
But if you explicitly handle exceptions using IF statements then that's overhead too, right?
replies(1): >>45783017 #
9. amelius ◴[] No.45782951[source]
But you can implement exceptions by using the same IF statement approach you would use for manual error handling. No need for unwinding tables and such if that optimization is a bridge too far for your specific target platform.
10. arbitrandomuser ◴[] No.45783017{3}[source]
yes but i think branch prediction essentialy makes them zero overhead
replies(1): >>45783266 #
11. neonz80 ◴[] No.45783042[source]
There was an interesting talk about C++ exceptions in smaller firmware at CppCon last year: https://youtu.be/bY2FlayomlE

Basically, the overhead of exceptions is probably less than handling the same errors manually in any non-trivial program.

Also, it's not like these table doesn't exist in other languages. Both Rust and Go have to unwind.

12. mrsmrtss ◴[] No.45783231{4}[source]
I think you're underestimating the role of .NET in this. It was .NET that popularized this concept for the masses, and from there it spread to other languages including JavaScript, which also borrowed the exact same async/await keywords from C#.
13. neonz80 ◴[] No.45783266{4}[source]
That's a different type of overhead than having unwind tables. With exceptions you wouldn't need a branch after each function call at all.
replies(1): >>45783746 #
14. amelius ◴[] No.45783746{5}[source]
But a branch that is (almost) never taken has an overhead close to the overhead of a NOP instruction, which may be negligible on modern architectures.
replies(1): >>45784284 #
15. neonz80 ◴[] No.45784284{6}[source]
The CPU can not remember an infinite number of branches. Also, many branches will increase code size. With exceptions the unwind tables and unwind code can be placed elsewhere and not take up valuable L1 cache.
replies(1): >>45784355 #
16. amelius ◴[] No.45784355{7}[source]
> The CPU can not remember an infinite number of branches.

I suspect a modern CPU has a branch instruction saying "This branch will never be taken except in exceptions, so assume this branch is not taken". But I must admit I haven't seriously looked at assembly language for some time.

(EDIT: yes, modern CPUs including x86 and ARM allow the programmer/compiler to hint if a branch is expected to be taken).

> Also, many branches will increase code size.

I'd like to see some data on that. Of course branches take code size, but how much is that percentage-wise? I suspect not much.

replies(1): >>45784564 #
17. neonz80 ◴[] No.45784564{8}[source]
You should take a look at the presentation I mentioned elsewhere in this thread. You also have to keep in mind that it's not only the branches that use space, but also the error handling code. Code which must be duplicated for every single call to a particular function.
replies(1): >>45784868 #
18. amelius ◴[] No.45784868{9}[source]
Ok, thanks. But that code needs to be loaded into memory only if the branch takes place. Which, for exceptions, will be not often. The main assumption is: optimize for the common case, where exceptions are not the common case.