Hacker News new | ask | show | jobs
by fefe23 644 days ago
This argument is basically hand waving, and it's factually wrong.

Yes, switching from user to kernel mode and back is expensive. But let's count the syscalls.

Model 1: Threads. 1 blocking read(), 1 blocking write(). Plus cost for the OS scheduler.

Model 2: 1 "I want to read", 1 "can I read now?", 1 actual read, 1 "I want to write now", 1 "can I write now?", 1 actual write. Multiply as necessary if the read or write are only partial. Add IPC cost as necessary if you have more than one thread handling async events.

Measuring by the syscall overhead, blocking I/O is much more efficient.

There are other costs to it, though, so it usually consumes more resources per handled socket and turns out to be more expensive than async I/O. But it's not the syscall cost causing it.

2 comments

This ignores the existence of batching capabilities like epoll and io uring. Async IO may need to do more syscalls than blocking IO for a single operation, but the story looks different if you consider a large number of operations that are awaited as a group.
io_uring actually changes this story because you can communicate events without crossing the syscall barrier, but only if you get enough incoming events to saturate your processing capabilities.

epoll batching only works in respect to the event notifications from kernel to user space. The only other thing that could be considered batched is that you can switch from "I want to read" to "I want to write" without telling the kernel first that you don't want to read anymore. But that's hardly worth mentioning.

It seems epoll could be slower than poll for async/await environments. It requires epoll_ctl (syscall) on each fd state transition. `poll` could wait in a single syscall for a small set of current blocked fds.
Model 2 is wrong, most of async/await implements this:

Nonblock read(), if EAGAIN: append fd into event waiting queue, wait event and read() again

Nonblock write(), if EAGAIN: append fd into event waiting queue, wait event and write() again

For loaded systems EAGAIN case is quite rare and syscall count is same.

That is an assumption and generalization you can't make. In fact it is usually wrong. I invite you to do actual measurements before claiming things as fact in the future.

I, for example, implement an async web server and your case basically never happens. You get a connection, you call read, there is not going to be data yet. In fact calling read immediately is basically wasted effort, so I'm not doing it. I save one syscall over your proposal right away. Optimizations like TCP fast open might change this over time, but definitely won't when you do SSL. You need to wait for a request and then for replies. SSL involves several additional back and fros over plain TCP.

Writing is worse unless you tell the OS to provide ungodly buffer space per connection, which you shouldn't, because then your server won't scale.

I don't argue that async/await could match manually written io loop for specific task. You started with blocking read/write and threads.