How efficient is it to perform sync (std) network and file I/O in separate futures, compared to using async wrappers?

Using std's synchronous I/O, spawn separate futures for each TCP connection or file, and communicate with each other via async channels.

It's not sufficient to spawn new futures if you're doing synchronous IO. You need to perform blocking tasks outside of the async runtime (in tokio you can use spawn_blocking to run a closure on a pooled thread where blocking is acceptable) otherwise you'll block a runtime thread from being able to make progress.

1 Like

Wait, does this mean going back to OS multithreading? At high concurrency, is it less efficient compared to wrappers that use concurrency primitives?
I've heard that network I/O and file I/O are different, and that the network wrapper can take full advantage of the OS concurrent primitives, while the file one is just a thin wrapper. So is this undesirable for network I/O at high concurrency, while file I/O is fine? I have a large amount of network I/O and a small amount of file I/O.

If you are making a blocking function call you shouldn’t do it on an async runtime thread. You can still see performance benefits since those pooled threads don’t need to do a lot of active work and are mostly inactive until the call completes.

I know at least some of tokio’s async IO APIs are just making blocking calls on a thread pool that then wake the async task when they finish, but I’m not sure about specific details beyond that.

1 Like

The methods for accessing files in Tokio all just offload the work to spawn_blocking. There's no way to do it in a truly async way.

1 Like

Rule of thumb: just use the async I/O operations that your chosen async runtime (tokio, async-std etc) provides.

While it may be possible to get a bit more efficiency via blocking (sync) I/O, a threadpool, and async channels, it's unlikely - and unless you're constantly updating your I/O wrappers, the chances are good that the async runtime will eventually do better than you (see, for example, the tokio-uring experiments, which are bringing true async I/O to files, instead of using a thin sync wrapper and a threadpool).