← Back to context

Comment by marginalia_nu

5 days ago

If you're doing contiguous readahead in userspace, why not just use preadv? It'll limit you to doing readahead up until the next resident page, but at least in my experiments in Marginalia's index, preadv beats io_uring in all cases you can use a single preadv call to do the full read.

Not sure what you mean. Nothing about preadv lets you indicate you only want to read what's already in the page cache. And io_uring and preadv aren't orthogonal - you can give io_uring a preadv op to do the scattered read instead of issuing separate read OPs although I'm not 100% sure how much of a win that is in practice.

Also, I think you misunderstood the blog as it's describing application read-ahead which is what you have to do when using O_DIRECT.

  • > Nothing about preadv lets you indicate you only want to read what's already in the page cache.

    preadv2 + RWF_NOWAIT

    • That’s not going to work for read ahead unless you’re really only requesting 1 page at a time which is terrible for performance across the board. That syscall will return EAGAIN if any data is missing to satisfy the read. Meaning if you issue a request for 128kib, it’ll error even if it has 124kib.

      There really aren’t optimal user space APIs. What you want ideally is a way to register a callback on a memory region so that the kernel is able to treat it like page cache and then on a fault you regenerate it using said callback. Thats even more efficient because usually you have to do some processing of the on disk representation (eg if it’s compressed).

  • So I have a buffer pool with O_DIRECT reads.

    I implement read-ahead in the application by (optionally) preadv:ing a single read into multiple destination buffers in the pool, leaving them unpinned, since as long as you aren't up against the bandwidth limit of the drive, a larger read is generally as fast as multiple smaller one on modern hardware.

    I've tried doing this with io_uring as well, but found just eating the preadv syscall cost was faster.

    • > a larger read is generally as fast as multiple smaller one on modern hardware.

      Not always if by modern you mean NVMe drives. One synchronous preadv() for 256 KiB gives the kernel/device one big request but 16 independent asynchronous 16 KiB reads can be serviced concurrently. So the latter gives the NVMe controller 16 operations it can schedule in parallel. So depending on the workload and hardware, offsets, filesystem and request sizes that can give you lower aggregate latency or higher throughput.

      7 replies →

    • Do this with io_uring with the preadv syscall. It’ll be the same or faster (faster only if you can do something else while waiting for I/O or you can submit multiple requests simultaneously - a single io_uring will be basically identical)

      5 replies →

I found out that using mmap and just telling uring to read form there to beat anything else.

  • Depends a lot on the memory pressure. If you can be fairly certain the data is (or will be) resident in memory, mmap is basically unbeatable. If you can't (because the data is larger than RAM or there's other stuff competing for RAM), mmap can have gnarly system-wide performance implications[1].

    [1] Mandatory mmap=poop-emoji link: https://db.cs.cmu.edu/mmap-cidr2022/

    • In my case: its a PROT_READ, MAP_PRIVAT map and i cannot get a SIGBUS since i tell the kernel to handle it all for me, thanks to liburing, instead i get a short send in that case.

  • Why would you use uring to read from an mmap? Couldn't you just memcpy?

    • To be more precise: i use that map to send assets out directly to clients from a zip file.

      Its a new web server i am building and its the fastest way i could find out.

      Just switching from epoll to liburing made the server ~45% faster too, its ridiculous. It can serve 10 gigabyte per second with a single thread, or around 10 million responses per second with h2 and 32 multiplexed requests.

      I had to write a new http load generator for that since i couldn't find one which could generate enough load to saturate my server or be fast enough to withstand it.

      6 replies →

  • Databases vendors usually find that read outperforms mmap. Mmap being fast is a myth.

    • Tried every way to get contents of a file as fast to a client socket as possible.

      mmap and io_uring_prep_send were faster than everything else, no matter the size as long as you keep the map around for the lifetime of the process.

      for one off sends when a file is smaller than 256kb then io_uring_prep_read + prep_send are faster than everything else.

      1 reply →

    • Note that grandparent actually didn't say they used mmap to actually get the memory, just mapping it.