upvote
I’ve replaced Asio recently with stright epoll event loop and got about 16% RPS better. That is for resonably sized SQL server, so be careful with nice precanned libraries.
reply
I switched out asio's epoll backend for its io_uring in a database server and CPU utilization shot up. Probably depends on usage and the specifics of how it's integrated into the event code.
reply
No async io framework exists which utilizes everything io_uring can, they are all build around the poll model. As such io_uring will always be worse than the poll like abstractions.

The two things that make io_uring fast are chaining of operations and zero syscall mode, the former would require that all async io frameworks/libs would need to be rewritten to make use of that and then all user facing apps would also need to be rewritten since all you’d get now are completions to operations instead of waiting if you can run a operation.

reply
Classic.

Know that the increase in CPU utilization may mean you've improved the performance of your "database server," because now your CPU cores are waiting less on IO. It also may not mean this, but just looking at htop won't tell you either way.

reply
That’s paradoxically what you can expect on a busy server - your CPU can spend time doing work that would have been previously IO wait time. Of course, it could be a bug in the implementation where you’re spinning doing no work erroneously, but depends on the details.
reply
Yeah, the explanation that I've usually heard for this sort of thing is that it's intended to get back CPU time that's lost when too many system threads are blocking to keep something on every core even during I/O (or pay for it in terms of the context switching overhead if you compensate for this with an extremely large number of system threads). The theory is that you'll avoid idle CPU compared to the common "one thread per core" way of doing things due to some of them being idle during I/O, at the cost of using some extra CPU to handle more things in user space. Obviously how much this helps can vary between use cases, but the measure of how much it's helping (or if it's maybe not helping at all!) is throughput, not CPU utilization.
reply
That is an imprecise explanation being conveyed to you - thread per core is still valuable in an io_uring world. The reason for that is how computers are built - its inherent in the kinds of operations that are cheap and what happens otherwise, the I/O model doesn’t matter.

Specifically, not thread per core code has the following issues:

* you have to use atomics/locks to synchronize data access. This involves expensive HW operations to implement the semantics of what an atomic operation is

* you have to deal with lock contention and cache contention

* when an OS migrates the core that is executing your code, you’ve suddenly got cold caches all over the place (icache, dcache, and TLB).

There’s also a bunch of related things that pop up - even if you do thread per core, the processor interrupts for events probably land on a different CPU resulting in extra overhead within the OS to deliver the event to you.

Io_uring doesn’t “handle more things in user space”. It specifically avoids a bunch of overheads; you’re context switching less (other cores can execute the OS code to process your request) and you can pipeline I/O (you can tell the OS “do IO A, then B, then C and tell me when that’s all done”) and you get fewer memory copies (the kernel reads into your buffer directly without needing to create another copy although this is more nuanced).

Anyway, the better mental model is specifically io_uring is more efficient and thus CPUs spend less time standing around waiting for things to happen at the hardware level (context switching, waiting for locks, etc). If the CPUs weren’t actually spending much time waiting, then you don’t get much benefit. This is the same phenomenon as Jevons paradox in economics; IO gets cheaper so you can do more of it within a given time unit and thus your CPUs end up more often having real work to do.

reply
To clarify, I'm not talking specifically about io_uring but (multithreaded) async concurrency models in general. The explanation needs to be imprecise because not all of them work the same way, so it's impossible to say anything correct about all of them without intentionally leaving out some specifics.
reply
This makes no sense. Epoll is already non-blocking, you never waste time waiting for I/O as long as there is work to do. Io_uring only boosts CPU efficiency (batching of syscalls, for example), it does not reduce blocking.
reply
Your right, I was imprecise with my wording. You waste time doing context switches + mapping/unmapping CPU buffers meaning for a given unit of time you can churn through more I/O meaning your user space CPU time goes up and system time goes down.
reply
In addition to the other discussion. It's important to measure outcomes and not just look at the cpu meter...

At the same load, how did latency look for A vs B.

What was throughput and latency at maximum load like for A vs B. For whichever one had the smaller max throughput, what did latency look like for the other option.

For bonus points while testing: is there another observable metric to indicate available capacity, if cpu % free is less useful.

reply
Boost is so inconvenient, they're huge dynamic libraries that are a pain to build and use. Even when I was already using CMake, getting Boost installed in a way where it could be discovered was super annoying. (I was on Mac, though)
reply
Asio also comes in standalone form and both versions are header-only. Not necessarily directly related to your comment but adding it on, anyway.
reply
deleted
reply
Some (most?) Boost libraries are header-only. Including Boost.Asio nowadays.
reply
You can statically link boost
reply