Live data from Hacker News

Zig's New Async I/O

kristoff.it

261–270 of 293 posts

Re: Zig's New Async I/O

#261

Earlier quoted context omitted.

> Arguably, our solution to the problem is just to color every function async-colored. This is essentially how Golang achived color-blindness.

It's basically how Java does it (circa 17) as well. It's something you really can't do without a pretty significant language runtime. You also really need people working within your runtime to prefer being in your runtime. Environments that do a lot of FFI don't work well with a colorblind runtime. That's because if the little C library you call does IO then you've got an incongruous interaction that you need to worr…

Neither Java nor Go make all functions async. What they provide is stackful coroutines (or equivalently one shot continuations) that allow composing async and sync functions transparently.

Re: Zig's New Async I/O

#262
post #8
post #3

I feel that I have to point this out once again, because the article goes so far as to state that: > With this last improvement Zig has completely defeated function coloring. I disagree with this. Let's look at the 5 rules referenced in the famous "What color is your function?" article referenced here. > 1. Every function has a color Well, you don't have async/sync/red/blue anymore, but you now have IO and non-IO fun…

The key difference to typical async function coloring is that `Io` isn't something you need specifically for asynchronicity; it's something which (unless you make a point to reach into very low-level primitives) you will need in order to perform any IO, including reading a file, sleeping, getting the time, etc. It's also just a value which you can keep wherever you want, rather than a special attribute/property of a…

Colouring every function async-coloured by default is something that's been attempted in the past; it was called "threads".

The innovation of async over threads is simply to allocate call stack frames on the heap, in linked lists or linked DAGs instead of fixed-size chunks. This sounds inefficient, and it is: indexing a fixed block of memory is much cheaper. It comes with many advantages as well: each "thread" only occupies the amount of memory it actually uses, so you can have a lot more of them; you can have non-linear graphs, like one function that calls two functions at the same time; and by reinventing threading from scratch you avoid a lot of thread-local overhead in libraries because they don't know about your new kind of threads yet. Because it's inefficient (and because for some reason we run the new threading system on top of the old threading system), it also became useful to run CPU-bound functions in the old kind of stack.

If you keep the linked heap activation records but don't colour functions, you might end up with Go, which already does this. Go can handle a large number of goroutines because they use linked activation records (but in chunks, so that not every function call allocates) and every Go function uses them so there is no colour.

You do lose advantages that are specific to coloured async - knowing that a context switch will not occur inside certain function calls.

As usual, we're beating rock with paper in the moment, declaring that paper is clearly the superior strategy, and missing the bigger picture.

Re: Zig's New Async I/O

#263
post #259

Earlier quoted context omitted.

Well, here we go I guess. Why can't you just use FastAPI? Or Tornado? Isn't there also an async Flask? Isn't Django also async now? What minor god have you angered to be chained to a non-async framework?

Of all the responses, this was perhaps my least expected one when I was talking about being chained to an async framework. Async isn't a replacement for threads, async doesn't let you spread your work out over multiple cores and doesn't give you time slicing. In Python the asyncio module actually gives you a threadpool to run computationally intensive work in as kind of one-offs. But when you need something like back…

Skipping to the end here: if you're inside a Python thread and you're making your own event loop, something has gone badly. Maybe you made a bad choice (using threads inside an async task instead of an executor), maybe you have some legacy nightmare dependency thing to deal with, but multiple event loops, let alone nested event loops, are suboptimal from a resource usage standpoint.

Re: Zig's New Async I/O

#264
post #19

Earlier quoted context omitted.

To me, there's no difference between the IO param and async/await. Adding either one causes it to not be callable from certain places. As for the second thing: You can do that, but... You can also do this in Rust. Yet nobody would say Rust has solved function coloring. Also, check this part of the article: > In the less common case when a program instantiates more than one Io implementation, virtual calls done throug…

>To me, there's no difference between the IO param and async/await. You can't pass around "async/await" as a value attached to another object. You can do that with the IO param. That is very different.

Other commenters have already provided examples for other languages, and it's the same for Rust: async functions are just regular functions that return an impl Future type. As a sync function, you can call a bunch of async functions and return the futures to your caller to handle, or you can block your current thread with the block_on function typically available through a handle (similar to the Io object here) provided by your favorite async runtime [0].

In other words, you don't need such an Io object upfront: You need it when you want to actually drive its execution and get the result. From this perspective, the Zig approach is actually less flexible than Rust.

[0]: https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.htm...

Re: Zig's New Async I/O

#265
post #228
post #219

Earlier quoted context omitted.

The problem is that C expects to have enough stack to put stuff there, but green threads allocate small stacks to reduce (virtual) memory use.

That’s not a problem when a dedicated thread pool is used as mentioned by GP. However they don’t solve the thread affinity issue.

That's the reason for the thread pool, and the resulting thread affinity issue.

Re: Zig's New Async I/O

#266
post #224

Note that this same concept is "sans io" and was previously discussed for it's use in Rust: https://www.firezone.dev/blog/sans-io https://sans-io.readthedocs.io/ https://news.ycombinator.com/item?id=40872020

If the functions are still calling I/O methods directly rather than the I/O being externally driven, I don't think that qualifies as sans-io, based on my previous exposure / based on your second link: > For byte-stream based protocols, the protocol implementation can use a single input buffer and a single output buffer. For input (that is, receiving data from the network), the calling code is responsible for deliveri…

Ah good point -- sans I/O as described in that second link is a bit more narrow than what Zig is doing here. The sans I/O discussed there is more for protocols specifically and less for general I/O.

I guess a better name for this approach might be "explicitly managed I/O".

Re: Zig's New Async I/O

#267
What about timers? That's the other resource an event loop must offer, besides I/O.

If you are using I/O with green threads but sleep the OS thread, you block other green threads. Likewise, if you have stackless coroutines (when they exist in Zig) but sleep the OS thread, you block other coroutines.

So is there an io.sleep?

Also - does any of this use io_uring on Linux?

Re: Zig's New Async I/O

#268

Earlier quoted context omitted.

Passing in your dependencies as function arguments is a form of dependency injection. It is the simplest and thus arguably best form of dependency injection.

This is like saying arithmetic is a form of calculus, the simplest form. Ie it reduces the concept (DI) to a meaningless tautology.

Not at all. Dependency injection is the injection of dependencies as logical parameters. The simplest and arguably best way to inject logical parameters to a segment of code is to use function parameters. You can have a complicated DI framework that involves Java class annotations and megabytes of XML, but that’s not the central idea.

Re: Zig's New Async I/O

#269
post #265
post #228

Earlier quoted context omitted.

That’s not a problem when a dedicated thread pool is used as mentioned by GP. However they don’t solve the thread affinity issue.

That's the reason for the thread pool, and the resulting thread affinity issue.

This seems to assume that the same green thread will always run on the same native thread, which I don’t think is universally the case.

Re: Zig's New Async I/O

#270
post #185

Earlier quoted context omitted.

Also, I do find it funny that we went from "Zig has completely defeated function coloring" to "Zig has colored objects".

but that's not even the case, because it's certainly possible to write a function that receives an object that holds onto an io (and uses it in its vtable calls) that equally well receives an object that doesn't have anything to do with io [0]. The consumers of those objects don't have to care, so there's no coloring. [0] and this isn't even really a theoretical matter, having colorblind object passing is extremely u…

I think in practice the caller still needs to know.

If I call `a.foo()` but `a` has and is using a stackless coroutine IO but the caller is being executed from a green thread IO then as was said before, I'm hitting UB.

But, I do like that you could skip/mock IO for instance. That's pretty neat.

Post reply on HN