Skip to content

$ cat ./posts/kernel/io_uring-vs-epoll.c

-rw-r--r-- 1.5K #kernel #network

io_uring vs epoll

by chris · 2 min read · /kernel /network


I ran both models against one hundred thousand idle-ish connections with a small periodic write, on the same kernel and the same hardware, to see where the syscall savings actually show up.

1. The setup

  • Single socket, 16 cores pinned, hyperthreading off.
  • 100k connections, 64-byte messages, one message per connection per second.
  • Measurements taken after a five minute warm-up, cgroup memory limit fixed.

2. What epoll costs

epoll is cheap to wait on and expensive to act on. Every readiness notification is followed by a syscall to actually move the bytes, so throughput scales with syscall count, not with events.

epoll_wait(4, events, 4096, -1) = 512
read(37, "...", 64)             = 64
read(41, "...", 64)             = 64

3. What io_uring changes

io_uring submits and completes in shared ring buffers, so a batch of reads becomes one submission rather than one syscall each. With IORING_SETUP_SQPOLL the submission syscall disappears entirely in the steady state.

4. Numbers

At this connection count io_uring cut CPU time per message by roughly forty percent, and the tail latency gap widened as batch size grew. Below a few thousand connections the two were indistinguishable — the ring bookkeeping costs as much as the syscalls it removes.

5. When to switch

Switch when you are syscall-bound and you can batch. If your workload is one request per wakeup, epoll remains simpler and just as fast.

← cd .. fris@linux:~/blog$ man kernel