[HN Gopher] lsr: ls with io_uring
___________________________________________________________________
lsr: ls with io_uring
https://tangled.sh/@rockorager.dev/lsr
Author : mpweiher
Score : 286 points
Date : 2025-07-18 12:40 UTC (10 hours ago)
(HTM) web link (rockorager.dev)
(TXT) w3m dump (rockorager.dev)
| movomito wrote:
| Link doesn't work
| Imustaskforhelp wrote:
| Hm, well I have replied it to some other comment too but the
| link is working fine for me.
|
| Currently downloading zig to build it.
| SillyUsername wrote:
| Love it.
|
| I'm trying to understand why all command line tools don't use
| io_uring.
|
| As an example, all my nvme's on usb 3.2 gen 2 only reach 740MB/s
| peak.
|
| If I use tools with aio or io_uring I get 1005MB/s.
|
| I know I may not be copying many files simultaneously every time,
| but the queue length strategies and the fewer locks also help I
| guess.
| superkuh wrote:
| One reason is so that they work in all linux environments
| rather than just bleeding edge installs from the last couple
| years.
| tyingq wrote:
| Probably historical preference for portability without a bunch
| of #ifdef means platform+version-specific stuff is very late to
| get adopted. Though, at this point, the benefit of portability
| across various posixy platforms is much lower.
| Retr0id wrote:
| Has anyone written an io_uring "polyfill" library with
| fallback to standard posix-y IO? It could presumably be done
| via background worker threads - at a perf cost.
| vlovich123 wrote:
| Seems like a huge lift since io_uring is an ever growing
| set of interfaces that is encompassing more and more of the
| kernel surface area. Also, the problem tends to not
| necessarily be that the io_uring interface isn't available
| at compile time but a) the version you distribute to has a
| kernel with it disabled or you don't have permission to use
| it meaning you need to do LD_preload magic or use a
| framework b) the kernel you're using supports some of the
| interfaces you're trying to use but not all. Not sure how
| you solve that one without using a framework.
|
| But I agree. It would be cool if it was transparent, but
| this is actually what a bunch of io-uring runtimes do,
| using epoll as a fallback (eg in Rust monoio)
| namibj wrote:
| You can just ask io_uring what commands you have
| available to you. Though the way of the background thread
| should be readily available/usable by just indirectly
| calling the syscall (-helper) and replacing it with a
| futex-based handshake/wrapper. If you're not using the
| backend-ordering-imposing link bit, you could probably
| even use minor futex trickery to dispatch multiple
| background threads to snatch up from the submission queue
| in a "grab one at a time" fashion.
| never_inline wrote:
| Poe's law hits again.
| elcapitan wrote:
| iirc io_uring also had some pretty significant security issues
| early on (a couple of years ago). Those should be fixed by now,
| but that probably dampened adoption as well.
| jeffbee wrote:
| Not years ago. io_uring has been a continuous parade of
| security problems, including a high severity one that wasn't
| fixed until a few months ago. Many large organizations have
| patched it out of their kernels on safety basis, which is one
| of the reasons it suffers from poor adoption.
| raesene9 wrote:
| Last I checked it's blocked by most container runtimes
| exactly because of the security problems, and Google blocked
| io_uring across all their services. I've not checked recently
| if that's still the case, but
| https://security.googleblog.com/2023/06/learnings-from-
| kctf-... has some background.
| tln wrote:
| Thats a great speed boost. What tools are these?
| Thaxll wrote:
| io_uring is a security nightmare.
| pjc50 wrote:
| How so?
| Thaxll wrote:
| This is a good read on the topic: https://chomp.ie/Blog+Pos
| ts/Put+an+io_uring+on+it+-+Exploiti...
| sim7c00 wrote:
| you give process direct access to a piece of kernel memory.
| its a reason why there is separation. thats all.
| wtallis wrote:
| Most of the security concerns with io_uring that I've
| seen aren't related to the shared buffers at all but
| simply stem from the fact that io_uring is a mechanism to
| instruct the kernel to do stuff _without making system
| calls_ , so security measures that focus on what system
| calls a process is allowed to do are ineffective.
| loeg wrote:
| This isn't the issue; it's relatively easy to safely
| share some ring buffers. The issue was/is that io_uring
| is rapidly growing the equivalent of ~all historical
| Linux syscall interfaces and sometimes comparable
| security measures were missed on the new interfaces.
| (Also, stuff like seccomp filters on syscalls are kind of
| meaningless for io_uring.)
| duped wrote:
| ...don't you supply the memory in the submission queue?
| or do you mean the queues themselves?
| raesene9 wrote:
| https://security.googleblog.com/2023/06/learnings-from-
| kctf-... - Has some interesting information on that topic.
| marcodiego wrote:
| I updated the Wikipedia article on io_uring to dispute that.
| fpoling wrote:
| io_uring is the asynchronous interface and that requires to use
| even-based architecture to use it effectively. But many
| command-line tools are still written is a straightforward
| sequential style. If C would have async or similar mechanism to
| pretend doing async programming sequentially, it would be
| easier to port. But without that a very significant refactoring
| is necessary.
|
| Besides, io_uring is not yet stable and who knows may be in 10
| years it will be replaced by yet another mechanism to take
| advantage of even newer hardware. So simply waiting for
| io_uring prove it is here to stay is very viable strategy.
| Besides in 10 years we may have tools/AI that will do the
| rewrite automatically...
| mananaysiempre wrote:
| > If C would have async or similar mechanism to pretend doing
| async programming sequentially, it would be easier to port.
|
| The *context() family of formerly-POSIX functions (clownishly
| deprecated as "use pthreads instead") is essentially a full
| implementation of stackful coroutines. Even the arguable
| design botch of them preserving the signal mask (the reason
| why they aren't the go-to option even on Linux) is
| theoretically fixable on the libc level without system calls,
| it's just a lot of work and very few can be bothered to do
| signals well.
|
| As far as stackless coroutines, there's a wide variety of
| libraries used in embedded systems and such (see the recent
| discussion[1] for some links), which are by necessity awkward
| enough that I don't see any of them becoming broadly
| accepted. There were also a number of language extensions,
| among which I'd single out AC[2] (from the Barrelfish
| project) and CPC[3]. I'd love for, say, CPC to catch on, but
| it's been over a decade now.
|
| [1] https://news.ycombinator.com/item?id=44546640
|
| [2] https://users.soe.ucsc.edu/~abadi/Papers/acasync.pdf
|
| [3] https://www.irif.fr/~jch/research/cpc-2012.pdf
| cesarb wrote:
| > I'm trying to understand why all command line tools don't use
| io_uring.
|
| Because it's fairly new. The coreutils package which contains
| the ls command (and the three earlier packages which were
| merged to create it) is decades old; io_uring appeared much
| later. It will take time for the "shared ring buffer" style of
| system call to win over traditional synchronous system calls.
| Agingcoder wrote:
| Iouring is very recent
| fermuch wrote:
| The link isn't working for me. For those who were able to see it:
| does it improve anything by using that instead of what ls does
| now??
| Imustaskforhelp wrote:
| Hm interesting, it worked for me.
| ta988 wrote:
| 70% faster, but more importantly 35x times less syscalls.
| eviks wrote:
| Is there a noticeable benefit of this huge syscall reduction?
| Imustaskforhelp wrote:
| Yes I just checked it after installing strace
|
| strace -c ls gave me this
|
| 100.00 0.002709 13 198 5 total
|
| strace -c eza gave me this
|
| 100.00 0.006125 12 476 48 total
|
| strace -c lsr gave me this
|
| 100.00 0.001277 33 38 total
|
| So seeing the number of syscalls in the calls directory
|
| 198 : ls
|
| 476 : eza
|
| 33 : lsr
|
| A meaningful difference indeed!
| richardwhiuk wrote:
| That's just observing there is a difference, not
| explaining why that's a good thing.
| fpoling wrote:
| syscalls are expensive and their relative latency
| compared with the rest of code only grow especially in
| view of mitigations against cache-related and other other
| hardware bugs.
| loeg wrote:
| Why do you say more importantly? The time is all that
| matters, I think.
| plq wrote:
| %70 faster = you wait less
|
| 35x less system calls = others wait less for the kernel to
| handle their system calls
| loeg wrote:
| > 35x less system calls = others wait less for the kernel
| to handle their system calls
|
| That isn't how it works. There isn't a fixed syscall
| budget distributed among running programs. Internally,
| the kernel is taking many of the same locks and resources
| to satisfy io_uring requests as ordinary syscall
| requests.
| plq wrote:
| More system calls mean more overall OS overhead eg. more
| context switches, or as you say more contention on
| internal locks etc.
|
| Also, more fs-related system calls mean less available
| kernel threads to process these system calls. eg. XFS can
| paralellize mutations only up to its number of allocation
| groups (agcount)
| loeg wrote:
| > More system calls mean more overall OS overhead [than
| the equivalent operations performed with io_uring]
|
| Again, this just isn't true. The same "stat" operations
| are being performed one way or another.
|
| > Also, more fs-related system calls mean less available
| kernel threads to process these system calls.
|
| Generally speaking sync system calls are processed in the
| context of the calling (user) thread. They don't consume
| kernel threads generally. In fact the opposite is true
| here -- io_uring requests are serviced by an internal
| kernel thread pool, so to the extent this matters,
| io_uring requests consume more kernel threads.
| plq wrote:
| > Again, this just isn't true.
|
| Again, it just is true.
|
| More fs-related operations mean less kthreads available
| for _others_. More syscalls means more OS overhead. It 's
| that simple.
| rybosome wrote:
| It improves the latency of ls calls.
| ninkendo wrote:
| I wonder how it performs against an NFS server with lots of
| files, especially one over a kinda-crappy connection. Putting an
| unreliable network service behind blocking POSIX syscalls is one
| of the main reasons NFS is a terrible design choice (as can be
| seen by anyone who's tried to ctrl+c any app that's reading from
| a broken NFS folder), but I wonder if io_uring mitigates the bad
| parts somewhat.
| loeg wrote:
| > as can be seen by anyone who's tried to ctrl+c any app that's
| reading from a broken NFS folder
|
| Theoretically "intr" mounts allowed signals to interrupt
| operations waiting on a hung remote server, but Linux removed
| the option long ago[1] (FreeBSD still supports it)[2]. "soft"
| might be the only workaround on Linux.
|
| [1]: https://man7.org/linux/man-pages/man5/nfs.5.html
|
| [2]:
| https://man.freebsd.org/cgi/man.cgi?query=mount_nfs&sektion=...
| ape4 wrote:
| Samba too
| mprovost wrote:
| The designers of NFS chose to make a distributed system emulate
| a highly consistent and available system (a hard drive), which
| was (and is) a reasonable tradeoff. It didn't require every
| existing tool, such as ls, to deal with things like the server
| rebooting while listing a directory. (The original NFS protocol
| is stateless, so clients can survive server reboots.) What does
| vi do when the server hosting the file you're editing stop
| responding? None of these tools have that kind of error
| handling.
|
| I don't know how io_uring solves this - does it return an error
| if the underlying NFS call times out? How long do you wait for
| a response before giving up and returning an error?
| ninkendo wrote:
| > The designers of NFS chose to make a distributed system
| emulate a highly consistent and available system (a hard
| drive), which was (and is) a reasonable tradeoff
|
| I don't agree that it was a reasonable tradeoff. Making an
| unreliable system emulate a reliable one is the very thing I
| find to be a bad idea. I don't think this is unique to NFS,
| it applies to any network filesystem you try to present as if
| it's a local one.
|
| > What does vi do when the server hosting the file you're
| editing stop responding? None of these tools have that kind
| of error handling.
|
| That's exactly why I don't think it's a good idea to just
| pretend a network connection is actually a local disk.
| Because tools aren't set up to handle issues with it being
| down.
|
| Contrast it with approaches where the client is aware of the
| network connection (like HTTP/GRPC/etc)... the client can
| decide for itself how long it should retry failed requests,
| whether it should bubble up failures to the caller, or work
| "offline" until it gets an opportunity to resync, etc. With
| NFS the syscall just hangs forever by default.
|
| Distributed systems are hard, and NFS (and other similar
| network filesystems) just pretend it isn't hard at all, which
| is great until something goes wrong, and then the abstraction
| leaks.
|
| (Also I didn't say io_uring solves this, but I'm curious as
| to whether its performance would be any better than blocking
| calls.)
| JonChesterfield wrote:
| > Making an unreliable system emulate a reliable one is the
| very thing I find to be a bad idea.
|
| It's the only idea though. We don't know how to make
| reliable systems, other than by cobbling together a lot of
| unreliable ones and hoping the emergent behaviour is more
| reliable than that of the parts.
| mrlongroots wrote:
| I think "making an unreliable system emulate a reliable
| one = bad" is too simplistic a heuristic.
|
| We do this all the time with things like ECC and
| retransmissions and packet recovery. This intrinsically
| is not bad at all, the question is: what abstraction does
| this expose to the higher layer.
|
| With TCP the abstraction we expect is "pretty robust but
| has tail latencies, do not use for automotive networks or
| avionics" and that works out well. The right question IMO
| is always "what kind of tail behaviors does this expose,
| and are the consumers of the abstraction prepared for
| them".
| ninkendo wrote:
| I think a difference in magnitude turns into a difference
| in kind. There's lots of systems where the unreliability
| of the underlying parts is low enough that it can be a
| simple matter of retrying quickly once or twice (bit
| flips in ECC RAM), and others where at least the
| unreliability is well-known enough that software has all
| learned to work around the leaky abstraction (like TCP.
| Although QUIC and other protocols show that maybe it's
| better to move the unreliability up a layer for more
| intelligent handling of the edge cases.)
|
| But the unreliability of "the network" compared to "my
| SATA port" is a whole different ballgame. Filesystems are
| designed for the latter, and when software uses
| filesystems it generally expects a reliability guarantee
| that "the network" can't really provide. Especially on
| mobile internet, wifi, etc... And that's not even getting
| into places where NFS just can't do things that local
| filesystems can do (has anyone figured out how to make
| inotify/fsevents work?) and all the software that subtly
| breaks because of it.
| pvtmert wrote:
| I think it highly depends on your architecture and the
| scale you are pushing.
|
| The other far-edge is the S3, where appending has just been
| possible within the last a few years as far as I can tell.
| Meanwhile editing a file requiring a full download/upload,
| not great either.
|
| For the NFS case, I cannot say it's my favorite, but
| certainly easy to setup and run on your own. Obviously a
| rebooting server may cause certain issues during the
| unavailability, but the NFS server should be in highly-
| available. with NFSv4.1, you may use UDP as the primary
| transport, which allows you to swap/switch servers pretty
| quickly. (Given you connect to a DNS/FQDN rather than the
| IP address)
|
| Another case is the plug and play, with NFS, UNIX
| permissions, ownership/group details, execute bit, etc are
| all preserved nicely...
|
| Besides, you could always have a "cache" server locally.
| Similar to GDrive or OneDrive clients, constantly syncing
| back and forth, caching the data locally, using file-
| handles to determine locks. Works pretty well _at scale_
| (ie. many concurrent users in the case of GDrive or
| OneDrive).
| cwillu wrote:
| Do you have similar thoughts about iscsi?
| Someone wrote:
| > The designers of NFS chose to make a distributed system
| emulate a highly consistent and available system (a hard
| drive),
|
| > The original NFS protocol is stateless,
|
| The protocol is, but the underlying disk isn't.
|
| - A stateless emulation doesn't know of the concept of "open
| file", so "open for exclusive access" isn't possible, and
| ways to emulate that were bolted on.
|
| - In a stateless system, you cannot open a scratch file for
| writing, delete it, and continue using it, in the expectation
| that it will be deleted when you're done using it (Th Unix
| Hater's handbook (https://web.mit.edu/~simsong/www/ugh.pdf)
| says there are hacks inside NFS to make this work, but that
| makes the protocol stateful)
|
| > It didn't require every existing tool, such as ls, to deal
| with things like the server rebooting while listing a
| directory
|
| But see above for an example where every tool that wants to
| do record locking or get exclusive access to a file has to
| know whether it's writing to a NFS disk to figure out how to
| do that.
| Imustaskforhelp wrote:
| Really interesting, the difference is real though I would just
| hope that some better coloring support could be added because I
| have "eza --icons=always -1" command set as my ls and it looks
| really good, whereas when I use lsr -1, yes the fundamental thing
| is same, the difference is in the coloring.
|
| Yes lsr also colors the output but it doesn't know as many things
| as eza does
|
| For example .opus will show up as a music icon and with the right
| color (green-ish in my case?) in eza whereas it would be shown up
| as any normal file in lsr.
|
| Really no regrets though, its quite easy to patch I think but yes
| this is rock solid and really fast I must admit.
|
| Can you please create more such things but for cat and other
| system utilities too please?
|
| Also love that its using tangled.sh which is using atproto, kinda
| interesting too.
|
| I also like that its written in zig which imo feels way more
| easier for me to touch as a novice than rust (sry rustaceans)
| johnisgood wrote:
| As for coloring support, I think the best way would be to
| implement LS_COLORS / dircolors. My GNU ls looks nice.
| hnlmorg wrote:
| "bat" is a pretty good modern "cat"
|
| https://github.com/sharkdp/bat
| Imustaskforhelp wrote:
| So I just ran strace -c cat <file> and strace -c bat <file>
|
| Bat did 445 syscall Cat did 48 syscall
|
| Sure bat does beautify some things a lot but still I just
| wanted to tell this, I want something that can use io_uring
| for cat too I think,
|
| like what's the least number of syscalls that you can use for
| something like cat?
| Bender wrote:
| I am curious what would happen if ls _and other commands_ were
| replaced using io_uring and kernel.io_uring_disabled was set to
| 1. Would it fall back to an older behavior or would the ability
| to disable it be removed?
| rockorager wrote:
| You would have to write your IO to have a fallback. The Ghostty
| project uses `io_uring`, but on kernels where it isn't
| available it falls back to an `epoll` model. That's all handled
| at the library level by libxev.
| yencabulator wrote:
| I just realized that one could probably write a userspace
| io_uring emulator in a library that spawns a thread to read the
| ringbuffer and a worker pool of threads to do the blocking
| operations. You'd need to get the main software to make calls
| to your library instead of the io_uring syscalls, that's it;
| the app logic could remain the same.
|
| Then all the software wanting to use io_uring wouldn't need to
| write their low-level things twice.
| Asmod4n wrote:
| I'm about to start something like this targetting epoll,
| poll, dispatch_io and maybe kqueue this weekend.
| neuroelectron wrote:
| There used to be lsring by Jens Axboe (author of io_uring), but
| it no longer exists. This is more extreme than abandoning the
| project. Perhaps there is some issue with using io_uring this
| way, perhaps vulnerabilities are exposed.
| arghwhat wrote:
| > Perhaps there is some issue with using io_uring this way,
| perhaps vulnerabilities are exposed.
|
| ... no. It's just not interesting or particularly valuable to
| optimize ls, and Jens probably just used it as a demo and
| didn't want to keep it around.
| neuroelectron wrote:
| I'm sure there are uses in Bash scripts that could benefit
| from it but most people would use it directly in a compiled
| program, I suppose, if the performance was a reoccurring
| need.
| neuroelectron wrote:
| _Explicit Vulnerabilities (Documented CVEs and Exploits)_
|
| These are actual discovered vulnerabilities, typically
| assigned CVEs and often exploited in sandbox escapes or
| privilege escalations: 1. CVE-2021-3491 (Kernel 5.11+)
| Type: Privilege escalation Mechanism: Failure to
| check CAP_SYS_ADMIN before registering io_uring restrictions
| allowed unprivileged users to bypass sandboxing.
| Impact: Bypass of security policy mechanisms.
|
| 2. CVE-2022-29582 Type: UAF (Use-After-
| Free) Mechanism: io_uring allowed certain memory
| structures to be freed and reused improperly.
| Impact: Local privilege escalation.
|
| 3. CVE-2023-2598 Type: Race condition
| Mechanism: A race in the io_uring timeout code could lead to
| memory corruption. Impact: Arbitrary code
| execution or kernel crash.
|
| 4. CVE-2022-2602, CVE-2022-1116, etc. Type:
| UAF and out-of-bounds access Impact: Escalation
| from containers or sandboxed processes.
|
| 5. Exploit Tooling: Tools like
| io_uring_shock and custom kernel exploits often target
| io_uring in container escape scenarios (esp. with Docker or
| LXC).
|
| _Implicit Vulnerabilities (Architectural and Latent Risks)_
|
| These are not necessarily exploitable today, but reflect
| deeper systemic design risks or assumptions. 1. Shared Memory
| Abuse io_uring uses shared rings (memory-
| mapped via mmap) between kernel and user space.
| Risk: If ring buffer memory management has reference count
| bugs, attackers could force races, data corruption, or misuse
| stale pointers. 2. User-Controlled Kernel Pointers
| Some features allow user-specified buffers, SQEs, and CQEs to
| reference arbitrary memory (e.g. via
| IORING_OP_PROVIDE_BUFFERS, IORING_OP_MSG_RING).
| Risk: Incomplete validation could allow crafting fake kernel
| structures or triggering speculative attacks. 3.
| Speculative Execution & Side Channels Since
| io_uring relies on pre-submitted work queues and long-lived
| kernel threads, it opens timing side channels.
| Risk: Predictable scheduling or timing leaks, esp. combined
| with hardware speculation (Spectre-class). 4.
| Bypassing seccomp or AppArmor Filters io_uring
| operations can effectively batch or obscure syscall behavior.
| Example: A program restricted from calling sendmsg() directly
| might still use io_uring to perform similar actions.
| Risk: Policy enforcement tools become less effective,
| requiring explicit io_uring filtering. 5. Poor
| Auditability The batched and asynchronous nature
| makes logging or syscall audit trails incomplete or
| confusing. Risk: Harder for defenders or
| monitoring tools to track intent or detect misuse in real
| time. 6. Ring Reuse + Threaded Offload
| With IORING_SETUP_SQPOLL or IORING_SETUP_IOPOLL, I/O workers
| can run in kernel threads detached from user context.
| Risk: Desynchronized security context can lead to privileged
| operations escaping sandbox context (e.g., post-chroot but
| pre-fork). 7. File Descriptor Reuse and Lifecycle
| Mismatch Some operations in io_uring rely on
| fixed file descriptors or registered files. Race conditions
| with FD reuse or closing can cause inconsistencies.
| Risk: UAF, type confusion, or logic bombs triggered by kernel
| state confusion. Emerging Threat Vectors eBPF
| + io_uring Some exploits chain io_uring with
| eBPF to do arbitrary memory reads or writes. e.g., io_uring
| to perform controlled allocations, then eBPF to read or write
| memory. io_uring + userfaultfd
| Combining userfaultfd with io_uring allows very fine-grained
| control over page faults during I/O -- great for fuzzing,
| also for exploit primitives.
| rkangel wrote:
| This was more interesting for the tangled.sh platform it's hosted
| on. Wasn't aware of that!
| nikodunk wrote:
| Same! Just signed up and will be following tangled and this
| repo. I like how tangled is built on atproto (bluesky).
| seanw444 wrote:
| Is there any actual focus on ATProto as a decentralized
| protocol? So far it seems like its only purpose is building
| Bluesky as a centralized service, which I have no interest in
| at all.
| Retr0id wrote:
| Doesn't the existence of tangled answer your question?
| dang wrote:
| One past thread so far:
|
| _Show HN: Tangled - Git collaboration platform built on
| atproto_ - https://news.ycombinator.com/item?id=43234544 -
| March 2025 (15 comments)
| quibono wrote:
| Lovely, I might try doing this for some other "classic" utility!
|
| A bit off-topic too, but I'm new to Zig and curious. This here:
| ``` const allocator = sfb.get(); var cmd:
| Command = .{ .arena = allocator };
|
| ``` means that all allocations need to be written with an
| allocator in mind? I.e. one has to pick an allocator per each
| memory allocation? Or is there a default one?
| kristoff_it wrote:
| Allocator is an interface so you write library code only once,
| and then the caller decides which concrete implementation to
| use.
|
| There's cases where you do want to change your code based on
| the expectation that you will be provided a special kind of
| allocator (e.g. arenas), but that's a more niche thing and in
| any case it all comes together pretty well in practice.
| IggleSniggle wrote:
| Caveat emptor, I don't write Zig but followed its development
| closely for awhile. A core design element of zig is that you
| shouldn't be stuck with one particular memory model. Zig
| encourages passing an allocator context around, where those
| allocators conform to a standardized interface. That means you
| could pass in different allocators with different performance
| characteristics at runtime.
|
| But yes, there is a default allocator, std.heap.page_allocator
| hansvm wrote:
| std.heap.smp_allocator
|
| You should basically only use the page allocator if you're
| writing another allocator.
| SkiFire13 wrote:
| > you shouldn't be stuck with one particular memory model
|
| Nit: an allocator is not a "memory model", and I very much
| want the memory model to not change under my feet.
| throwawaymaths wrote:
| > Zig encourages passing an allocator context around, where
| those allocators conform to a standardized interface.
|
| in libraries. if youre just writing a final product it's
| totally fine to pick one and use it everywhere.
|
| > std.heap.page_allocator
|
| strongly disrecommend using this allocator as "default", it
| will take a trip to kernelland on each allocation.
| danbruc wrote:
| Why does this require inventing lsr as an alternative to ls
| instead of making ls use io_uring? It seems pretty annoying to
| have to install replacements for the most basic command line
| tools. And especially in this case, where you do not even do it
| for additional features, just for getting the exact same thing
| done a bit faster.
| nailer wrote:
| `ls` is in C, `lsr` is in Zig. The `lsr` programmer probably
| doesn't want to make new code in C.
| loeg wrote:
| In addition, the author might not want to sign away their
| rights to the FSF.
| andrepd wrote:
| What on earth are you talking about? Why would this be the
| case?
| scott_w wrote:
| Depending on the implementation (and I don't know which
| `ls` is being referred to), modifying `ls` might mean
| modifying an FSF project which require copyright
| assignment as a condition of patch submissions.
| leni536 wrote:
| That's only the case if the author would want to upstream
| their changes. If they wanted to only fork ls then they
| would only be required to comply with the license,
| without assigning copyright over.
| scott_w wrote:
| That may be the case but then why bother modifying ls
| when you can just write your own exactly as you want it?
| loeg wrote:
| Are you unfamiliar with contributing to GNU projects (ls
| is part of GNU corutils)?
|
| https://www.gnu.org/prep/maintain/maintain.html#Copyright
| -Pa...
| mschuster91 wrote:
| > Why does this require inventing lsr as an alternative to ls
| instead of making ls use io_uring?
|
| Good luck getting that upstreamed and accepted. The more
| foundational the tools (and GNU coreutils definitely is
| foundational), the more difficult that process will be.
|
| Releasing a standalone utility makes iteration much faster,
| partially because one is not bound to the release cycles of
| distributions.
| s1mplicissimus wrote:
| > Releasing a standalone utility makes iteration much faster,
| partially because one is not bound to the release cycles of
| distributions.
|
| which certainly is a valid way or prioritizing. similarly,
| distros/users may prioritize stability, which means the
| theoretical improvement would now be stuck in not-used-land.
| the value of software appears when it's run, not when it's
| written
| KPGv2 wrote:
| > the value of software appears when it's run, not when
| it's written
|
| Have you ever tried to contribute to open source projects?
|
| The question was why wouldn't someone _writing_ software
| not take the route likely to end in rejection /failure. I
| don't know about you, but if I write software, I am not
| going to write it for a project whose managers will make it
| difficult for my PR to be accepted, and that 99% likely it
| never will be.
|
| I will always contribute to the project likely to
| appreciate my work and incorporate it.
|
| I'll share an anecdote: I got involved with a project,
| filed a couple PRs that were accepted (slowly), and then I
| talked about refactoring something so it could be tested
| better and wasn't so fragile and tightly coupled to IO.
| "Sounds great" was the response.
|
| So I did the refactor. Filed a PR and asked for code
| review. The response was (after a long time waiting)
| "thanks but no, we don't want this." PR closed. No
| feedback, nothing.
|
| I don't even use the software anymore. I certainly haven't
| tried to fix any bugs. I don't like being jerked around by
| management, especially when I'm doing it for free.
|
| (For the record, I privately forked the code and run my own
| version that is better because by refactoring and then
| writing tests, I discovered a number of bugs I couldn't be
| arsed to file with the original project.)
| s1mplicissimus wrote:
| > Have you ever tried to contribute to open source
| projects?
|
| yes, and it was often painful enough to make me consider
| very well wether I want to bother contributing. I can
| only imagine how terrible the experience must be at a
| core utility such as ls.
|
| > The question was why wouldn't someone writing software
| not take the route likely to end in rejection/failure
|
| Obviously they wouldn't - in my comment I assumed that
| the lsr author aimed for providing a better ls for people
| and tried to offer a perspective with a different
| definition of what success is.
|
| > I don't like being jerked around by management,
| especially when I'm doing it for free
|
| I get that. The older OSS projects become, the more they
| fossilize too - and that makes it more annoying to
| contribute. But you can try to see it from the
| maintainers perspective too: They have actual people
| relying on the program being stable and are often also
| not paid. Noone is forcing you to contribute to their
| project, but if you don't want to deal with existing
| maintainers, you won't have their users enjoying your
| patchset. Know what you want to achieve and act
| accordingly, is all I'm trying to say.
| mschuster91 wrote:
| > The older OSS projects become, the more they fossilize
| too - and that makes it more annoying to contribute.
|
| Newer ones can be just as braindead, if they came out of
| some commercial entity. CLAs and such.
| WorldMaker wrote:
| In the history of Unix its also a common way to propose tool
| replacements, for instance how `less` became `more` on most
| systems, or `vim` became the new `vi` which in its day became
| the new `ed`.
| nailer wrote:
| > instance how `less` became `more` on most systems
|
| How `more` became `less`.
|
| The name of 'more' was from paging - rather than having
| text scroll off the screen, it would show you one page,
| then ask if you wanted to see 'more' and scroll down.
|
| 'less' is a joke by the less authors. 'less is more' etc.
| yencabulator wrote:
| For a while there was a less competitor named most.
| JdeBP wrote:
| It hasn't gone away.
|
| * https://freshports.org/sysutils/most/
|
| * https://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/misc/m
| ost/i...
|
| * https://packages.debian.org/sid/most
|
| One can even get pg still, with Ilumos-based systems;
| even though that was actually taken out of the SUS years
| ago. This goes to show that what's standard is not the
| same as what exists, of course.
|
| * https://illumos.org/man/1/pg
|
| * https://pubs.opengroup.org/onlinepubs/9699919799.2008ed
| ition...
| JdeBP wrote:
| Yes and no. We don't really have the equivalent of
| comp.sources.unix nowadays, which is where the early
| versions of those occurred, and comp.sources.unix did not
| take just anything. Rich Salz had rules.
|
| Plus, since I actually took stevie and screen and others
| from comp.sources.unix and worked on them, and wasn't able
| to even send my improvements to M. Salz or the original
| authors at all, from my country, I can attest that
| contributing improvements had hurdles just as large to
| overcome back then as there exist now. They're just
| different.
| tiagod wrote:
| You don't have to install it. You can modify ls yourself too.
| bicolao wrote:
| The author answered on lobster thread [1]. This is more of an
| io_uring exercise than an attempt to replace ls.
|
| [1] https://lobste.rs/s/mklbl9/lsr_ls_with_io_uring
| adgjlsfhk1 wrote:
| It's a shame to see uutils doing so poorly here. I feel like
| they're our best hope for an organization to drive this sort of
| core modernization forward, but 2x slower than GNU isn't a good
| start.
| ReDress wrote:
| I've been playing around with io_uring for a while.
|
| Still, I am yet to come across a some tests that simulate typical
| real life application workload.
|
| I heard of fio but are yet to check how exactly it works and
| whether it might be possible to simulate real life application
| workload with it.
| izabera wrote:
| what a "real life application workload" looks like is entirely
| dependent on your use case, but fio is very widely used in the
| storage industry
|
| it's a good first approximation to test the cartesian product
| of
|
| - sequential/random
|
| - reads/writes
|
| - in arbitrary sizes
|
| - with arbitrarily many workers
|
| - with many different backends to perform such i/o including
| io_uring
|
| and its reporting is solid and thorough
|
| implementing the same for your specific workload is often not
| trivial at all
| the8472 wrote:
| io_uring doesn't support getdents though. so the primary benefit
| is bulk statting (ls -l). It'd be nice if we could have a
| getdents in flight while processing the results of the previous
| one.
| loeg wrote:
| POSIX adopting NFS' "readdirplus" operation (getdents + stat)
| could negate some of the benefit towards io_uring, too.
| the8472 wrote:
| but then someone wants statx...
| loeg wrote:
| Yeah. To be clear I don't think Linux or Posix will ever
| adopt readdirplus as an API.
| tln wrote:
| The times seem sublinear, 10k files is less than 10x 1k files.
|
| I remember getting in to a situation during the ext2 and spinning
| rust days where production directories had 500k files. ls
| processes were slow enough to overload everything. ls -F saved me
| there.
|
| And filesystems got a lot better at lots of files. What
| filesystem was used here?
|
| It's interesting how well busybox fares, it's written for size
| not speed iirc?
| SkiFire13 wrote:
| > The times seem sublinear, 10k files is less than 10x 1k files
|
| Two points are not enough to say it's sublinear. It might very
| well be some constant factor that becomes less and less
| important the bigger the linear factor becomes.
|
| Or in other words 10000 _n+C < 10000_(n+C)
| tln wrote:
| The article has data points for n=10,100,1000,10000. Taking
| (n=10,000 - n=10)/(n=1,000 - n=10) would eliminate the
| constant factor and we'd expect about 10.09x higher times for
| a linear algorithm.
|
| But for lsr, it's 9.34. The other tools have factors close to
| 10.09 or higher. Since ls has to sort it's output (unless -F
| is specified) I'd not be too surprised with a little
| superlinearity.
|
| https://docs.google.com/spreadsheets/d/1EAYua3B3UeTGBtAejPw2.
| ..
| otterley wrote:
| Ext2 never got better with large directories even with SSDs
| (this includes up to ext4). The benchmarks don't include the
| filesystem type, which is actually extremely important when it
| comes to the performance of reading directories.
| maplant wrote:
| This seems more interesting as demonstration of the amortized
| performance increase you'd expect from using io_uring, or as a
| tutorial for using it. I don't understand why I'd switch from
| using something like eza. If I'm listing 10,000 files the
| difference is between 40ms and 20ms. I absolutely would not
| notice that for a single invocation of the command.
| 0x000xca0xfe wrote:
| Well I have a directory with a couple million JSON files and
| ls/du take minutes.
|
| Most of the coreutils are not fast enough to actually utilize
| modern SSDs.
| otterley wrote:
| What's the filesystem type? Ext4 suffers terrible lookup
| performance with large directories, while xfs absolutely
| flies.
| 0x000xca0xfe wrote:
| Yup, default ext4 and most files are <4KB, so it's extra
| bad.
|
| Thanks for the comment, didn't know that!
| rockorager wrote:
| Yeah, I wrote this as a fun little experiment to learn more
| io_uring usage. The practical savings of using this are tiny,
| maybe 5 seconds over your entire life. That wasn't the point
| haha
| JuettnerDistrib wrote:
| I'd be curious to know if this helps on supercomputers, which
| are notorious for frequently hanging for a few seconds on an
| ls -l.
| mrlongroots wrote:
| It could, but important to keep in mind that the filesystem
| architecture there is also very different with a parallel
| filesystem with disaggregated data and metadata.
|
| When you run `ls -l` you could potentially be enumerating a
| directory with one file per rank, or worse, one file per
| particle or something. You could try making the read fast,
| but I also think that it makes no sense to have that many
| files: you can do things to reduce the number of files on
| disk. Also many are trying to push for distributed object
| stores instead of parallel filesystems... fun space.
| maplant wrote:
| It's a very cool experiment. Just wanted to perhaps steer the
| conversation towards those things rather than whether or not
| this was a good ls replacement because like you say that
| feels like it was missing the point
| api wrote:
| Why isn't it possible -- or is it -- to make libc just use uring
| instead of syscall?
|
| Yes I know uring is an async interface, but it's trivial to
| implement sync behavior on top of a single chain of async send-
| wait pairs, like doing a simple single threaded "conversational"
| implementation of a network protocol.
|
| It wouldn't make a difference in most individual cases but
| overall I wonder how big a global speed boost you'd get by
| removing a ton of syscalls?
|
| Or am I failing to understand something about the performance
| nuances here?
| ninkendo wrote:
| In order to make this work, libc would have to:
|
| - Start some sort of async executor thread to service the
| io_uring requests/responses
|
| - Make it so every call to "normal" syscalls causes the calling
| thread to sleep until the result is available (that's 1
| syscall)
|
| - When the executor thread gets a result, have it wake up the
| original thread (that's another syscall)
|
| So you're basically turning 1 syscall into 2 in order to
| emulate the legacy syscalls.
|
| io_uring only makes sense if you're already async. Emulating
| sync on top of async is nearly always a terrible idea.
| wtallis wrote:
| You don't need to start spawning new threads to use io_uring
| as a backend for synchronous IO APIs. You just need to set up
| the rings once, then when the program does an fwrite or
| whatever, that gets implemented as sending a submission queue
| entry followed by a single io_uring_enter syscall that
| informs the kernel there's something in the submission queue,
| and using the arguments indicating that the calling process
| wants to block until there's something in the completion
| queue.
| ninkendo wrote:
| > using the arguments indicating the calling process wants
| to block
|
| Nice to know io_uring has facilities for backwards
| compatibility with blocking code here. But yeah, that's
| still a syscall, and given that the whole benefit of
| io_uring is in avoiding (or at least, coalescing) syscalls,
| I doubt having libc "just" use io_uring is going to give
| any tangible benefit.
| loeg wrote:
| In addition to sibling's concern about syscall amplification,
| the async just isn't useful to the application (from a latency
| perspective) if you just serialize a bunch of sync requests
| through it.
| yencabulator wrote:
| Not speaking of ls which is more about metadata operations, but
| general file read/write workloads:
|
| io_uring requires API changes because you don't call it like
| the old read(please_fill_this_buffer). You maintain a pool of
| buffer that belong to the ringbuffer, and reads take buffers
| from the pool. You consume the data from the buffer and return
| it to the pool.
|
| With the older style, you're required to maintain
| O(pending_reads) buffers. With the io_uring style, you have a
| pool of O(num_reads_completing_at_once) (I assume with
| backpressure but haven't actually checked).
| api wrote:
| In a single threaded flow your buffer pool is just the buffer
| you were given, and you don't return until the call
| completes. There are no actual concurrent calls in the ring.
| All you're doing is using io_uring to avoid syscall.
|
| Other replies lead me to believe it's not worth doing though,
| that it would not actually save syscalls and might make
| things worse.
| yencabulator wrote:
| Can you use io_uring in a way that doesn't gain the
| benefits of using it? Yes. Does the traditional C/POSIX API
| force you into that pattern? Almost certainly.
| rockorager wrote:
| Author of the project here! I have a little write up on this
| here: https://rockorager.dev/log/lsr-ls-but-with-io-uring
| jeffbee wrote:
| How much of the speedup over GNU ls is due to lacking
| localization features? Your results table is pretty much
| consistent with my local observations: in a dir with 13k files,
| `ls -al` needs 33ms. But 25% of that time is spent by libc in
| `strcoll`. Under `LC_ALL=C` it takes just 27ms, which is
| getting closer to the time of your program.
| rockorager wrote:
| I didn't include `busybox` in my initial table, so it isn't
| on the blog post but the repo has the data...but I am 99%
| sure busybox does not have locale support, so I think GNU ls
| without locale support would probably be closer to busybox.
|
| Locales also bring in a lot more complicated sorting - so
| that could be a factor also.
| tavianator wrote:
| My bfs project also uses io_uring:
| https://github.com/tavianator/bfs/blob/main/src/ioq.c
|
| I'm curious how lsr compares to bfs -ls for example. bfs only
| uses io_uring when multiple threads are enabled, but maybe it's
| worth using it even for bfs -j1
| rockorager wrote:
| Oh that's cool. `find` is another tool I thought could
| benefit from io_uring like `ls`. I think it's definitely
| worth enabling io_uring for single threaded applications for
| the batching benefit. The kernel will still spin up a thread
| pool to get the work done concurrently, but you don't have to
| manage that in your codebase.
| tavianator wrote:
| I did try it a while ago and it wasn't profitable, but that
| was before I added stat() support. Batching those is
| probably good
| mshockwave wrote:
| and grep / ripgrep. Or did ripgrep migrate to using
| io_uring already?
| burntsushi wrote:
| No, ripgrep doesn't use io_uring. Idk if it ever will.
| porridgeraisin wrote:
| Curious: Why? Is it not a good fit for what ripgrep does?
| Isn't the sort of "streaming" "line at a time" I/O that
| ripgrep does a good fit for async io?
| burntsushi wrote:
| For many workloads, ripgrep spends the vast majority of
| its time searching through files.
|
| But more practically, it would be a terror to implement.
| ripgrep is built on top of platform specific standard
| file system APIs. io_uring would mean a whole heap of
| code to work with a different syscall pattern _in
| addition_ to the existing code pattern for non-Linux
| targets.
|
| So to even figure out whether it would be worth doing
| that, you would need to do a whole bunch of work just to
| test it. And because of my first point above, there is a
| hard limit on how much of an impact it could even
| theoretically have.
|
| Where I would expect this to help is to batch syscalls
| during directory tree traversal. But I have nonidea how
| much it would help, if at all.
| Sesse__ wrote:
| I believe that io_uring does not support getdents
| (despite multiple patch series being proposed). So you'd
| get async stat(), if you need them, but nothing else.
| dang wrote:
| (Thanks - we'll make that the main link (since it has more
| background info) and include the repo thread at the top as
| well.)
| benreesman wrote:
| This is fantastic stuff. I'm doing a C++ project right now that
| I'm doing with an eye to eventual migration in whole or in part
| to Zig. My little `libevring` thing is pretty young and I'd be
| very open to replacing it with `ourio`.
|
| What's your feeling on having C/C++ bindings in the project as
| a Zig migration path for such projects?
| rockorager wrote:
| I think exposing a C lib would be very nice. Feel free to
| open a discussion or issue on the Github.
| ChuckMcM wrote:
| Nice writeup. I suspect you're measuring the cost of
| abstraction. Specifically, routines that can handle lots of
| things (like locale based strings and utf8 character) have more
| things to do before they can produce results. This was
| something I ran into head on at Sun when we did the I18N[1]
| project.
|
| In my experience there was a direct correlation between the
| number of different environments where a program would "just
| work" and its speed. The original UNIX ls(1) which had maximum
| sized filenames, no pesky characters allowed, all representable
| by 7-bit ASCII characters, and only the 12 bits of meta data
| that God intended[2] was really quite fast. You add things like
| a VFS which is mapping the source file system into the
| parameters of the "expected" file system that adds delay.
| You're mapping different character sets? adds delay. Colors for
| the display? Adds delay. Small costs that add up.
|
| 1: The first time I saw a long word like 'internationalization'
| reduced to first and last letter and the count of letters in
| between :-).
|
| 2: Those being Read, Write, and eXecute for user, group, and
| other, setuid, setgid, and 'sticky' :-)
| cb321 wrote:
| At those time scales, you would be better off using `tim` (
| https://github.com/c-blake/bu/blob/main/doc/tim.md ) than
| hyperfine { and not just because that is your name! Lol. That
| is just a happy coincidence by clipping one letter off of the
| word "time". :-) } even though being in Nim might make it more
| of a challenge.
| jasonjmcghee wrote:
| I find it funny that there are icons for .mjs and .cjs file
| extensions but not .c, .h, .sh
| swiftcoder wrote:
| Kind of fascinating that slashing syscalls by ~35x (versus the
| `ls -la` benchmark) is "only" worth a 2x speedup
| bogwog wrote:
| I vaguely remember some benchmark I read a while back for some
| other io_uring project, and it suggested that io_uring syscalls
| are more expensive than whatever the other syscalls were that
| it was being used to replace. It's still a big improvement,
| even if not as big as you'd hope.
|
| I wish I could remember the post, but I've had that impression
| in the back of my mind ever since.
| Galanwe wrote:
| These syscalls are mostly through VDSO, so not very costly
| wahern wrote:
| The only VDSO-capable calls are clock_gettime, getcpu,
| getrandom, gettimeofday, and time. (Other architectures have
| some more, mostly related to signals and CPU cache flushing.)
| mnw21cam wrote:
| Love the idea and execution, don't love the misplaced
| apo'strophe's.
| rockorager wrote:
| Oh no - where at?
| nbf_1995 wrote:
| Technically, the first, third, and fifth occurrence of "it's"
| should be "its". The dog chased its tail.
|
| I didn't notice when I read the article though. The original
| commenter is being pedantic.
| pvtmert wrote:
| > I have no idea what lsd is doing. I haven't read the source
| code, but from viewing it's strace, it is calling clock_gettime
| around 5 times per file. Why? I don't know. Maybe it's doing
| internal timing of steps along the way?
|
| Maybe calculating "X minutes/hours/days/weeks ago" thing for each
| timestamp? (access, create, modify, ...). Could just be an old
| artifact of another library function...
| namibj wrote:
| This shouldn't be an actual syscall these days; it should be
| handled by vDSO (`man 7 vDSO`). Maybe zig doesn't use that,
| though.
| buybackoff wrote:
| A little offtop, but do you know a number in usecs that io_uring
| can save on enterprise grade servers, with 10G NICs, for socket
| latency overheads vs LD_PRELOAD when hardware supports that?
| Let's say it's Mellanox 4 or 5. My understanding is that each
| gives around 10us savings, maybe less. Based on some
| benchmarking, which was not focused on any of those explicitly
| but had some imprecise experiments. It also looks like they do
| not add up. Do you have a number based on real experience?
___________________________________________________________________
(page generated 2025-07-18 23:00 UTC)