<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>rockorager.dev</title><description>Writing about terminals, Zig, and asynchronous I/O by Tim Culverhouse.</description><link>https://rockorager.dev/</link><language>en</language><item><title>lsr: ls but with io_uring</title><link>https://rockorager.dev/blog/lsr-ls-but-with-io-uring/</link><guid isPermaLink="true">https://rockorager.dev/blog/lsr-ls-but-with-io-uring/</guid><description>lsr: ls but with io_uring</description><pubDate>Tue, 06 May 2025 16:15:50 GMT</pubDate><content:encoded>&lt;p&gt;As an excercise in syscall golf, I wrote an implementation of &lt;code&gt;ls(1)&lt;/code&gt; which uses my IO library, &lt;a href=&quot;https://github.com/rockorager/ourio&quot;&gt;ourio&lt;/a&gt; to perform as much of the IO as possible. What I ended up with is something that is faster than any version or alternative to &lt;code&gt;ls&lt;/code&gt; I tested, and also performs an &lt;strong&gt;order of magnitude fewer syscalls&lt;/strong&gt;. I&amp;#39;m calling it &lt;a href=&quot;https://tangled.sh/@rockorager.dev/lsr&quot;&gt;lsr&lt;/a&gt;. Let&amp;#39;s start with the benchmarks, then we&amp;#39;ll see how we got there.&lt;/p&gt;&lt;figure class=&quot;emdash-image&quot; data-astro-cid-nsqbt2nj&gt;&lt;img src=&quot;https://rockorager.dev/_emdash/api/media/file/01M3837SPFX0QAV0G5RW43A6H2.webp&quot; alt=&quot;screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-cid-nsqbt2nj&gt;&lt;/figure&gt;&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;a href=&quot;https://rockorager.dev/blog/lsr-ls-but-with-io-uring/#benchmarks&quot; class=&quot;heading-link&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;h3 id=&quot;time&quot;&gt;Time&lt;a href=&quot;https://rockorager.dev/blog/lsr-ls-but-with-io-uring/#time&quot; class=&quot;heading-link&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Data gathered with &lt;code&gt;hyperfine&lt;/code&gt; on a directory of &lt;code&gt;n&lt;/code&gt; plain files.&lt;/p&gt;&lt;table data-astro-cid-t6mhkqx2&gt;&lt;thead data-astro-cid-t6mhkqx2&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;Program&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=10&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=100&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=1,000&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=10,000&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-astro-cid-t6mhkqx2&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;lsr -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;372.6 µs&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;634.3 µs&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;2.7 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;22.1 ms&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;ls -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;1.4 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;1.7 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;4.7 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;38.0 ms&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;eza -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;2.9 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;3.3 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;6.6 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;40.2 ms&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;lsd -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;2.1 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;3.5 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;17.0 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;153.4 ms&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;uutils ls -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;2.9 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;3.6 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;11.3 ms&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;89.6 ms&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id=&quot;syscalls&quot;&gt;Syscalls&lt;a href=&quot;https://rockorager.dev/blog/lsr-ls-but-with-io-uring/#syscalls&quot; class=&quot;heading-link&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Data gathered with &lt;code&gt;strace -c&lt;/code&gt; on a directory of &lt;code&gt;n&lt;/code&gt; plain files. (Lower is better)&lt;/p&gt;&lt;table data-astro-cid-t6mhkqx2&gt;&lt;thead data-astro-cid-t6mhkqx2&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;Program&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=10&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=100&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=1,000&lt;/p&gt;&lt;/th&gt;&lt;th scope=&quot;col&quot; data-astro-cid-t6mhkqx2&gt;&lt;p data-astro-cid-cpitzeoz&gt;n=10,000&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-astro-cid-t6mhkqx2&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;lsr -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;20&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;28&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;105&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;848&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;ls -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;405&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;675&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;3,377&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;30,396&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;eza -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;319&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;411&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;1,320&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;10,364&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;lsd -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;508&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;1,408&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;10,423&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;100,512&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr data-astro-cid-t6mhkqx2&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;uutils ls -al&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;445&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;986&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;6,397&lt;/p&gt;&lt;/td&gt;&lt;td style=&quot;text-align:center&quot; data-astro-cid-t6mhkqx2=&quot;true&quot;&gt;&lt;p data-astro-cid-cpitzeoz&gt;10,005&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id=&quot;how-we-got-there&quot;&gt;How we got there&lt;a href=&quot;https://rockorager.dev/blog/lsr-ls-but-with-io-uring/#how-we-got-there&quot; class=&quot;heading-link&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Let&amp;#39;s start with how &lt;code&gt;lsr&lt;/code&gt; works. To list directory contents, we basically have 3 stages to the program:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Parse args&lt;/li&gt;&lt;li&gt;Gather data&lt;/li&gt;&lt;li&gt;Print data&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;All of the IO involved happens in the second step. Wherever possible, &lt;code&gt;lsr&lt;/code&gt; utilizes io_uring to pull in the data it needs. To get to that point, it means that we open the target directory with io_uring, if we need local time, user data, or group data, we open (and read) those files with io_uring. We do all &lt;code&gt;stat&lt;/code&gt; calls via io_uring, and as needed we do the equivalent of an &lt;code&gt;lstat&lt;/code&gt; via io_uring. In practice, this means that the number of syscalls we have should be drastically smaller than equivalent programs because we are able to batch the &lt;code&gt;stat&lt;/code&gt; syscall. The results clearly show this...&lt;code&gt;lsr&lt;/code&gt; has at least an order of magnitude fewer syscalls than it&amp;#39;s closest equivalent, being &lt;code&gt;uutils ls&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We also use the zig stdlib StackFallbackAllocator. This let&amp;#39;s &lt;code&gt;lsr&lt;/code&gt; allocate memory it needs up front, but fallback to a different allocator when it&amp;#39;s exhausted the fixed allocation. We allocate 1MB up front, which is more than enough for typical usage. This further reduces syscalls by reducing mmap usage.&lt;/p&gt;&lt;p&gt;As a result of working directly with io_uring, we also bypass several libc related pitfalls. Namely, we have no dynamic linking - &lt;code&gt;ls&lt;/code&gt; has some considerable overhead in loading libc and related libraries...but it also has the benefit of having locale support. &lt;code&gt;lsr&lt;/code&gt; does not boast such a feature. Despite being statically linked, &lt;code&gt;lsr&lt;/code&gt; is still smaller than GNU &lt;code&gt;ls&lt;/code&gt;: 138.7KB vs 79.3KB when built with ReleaseSmall.&lt;/p&gt;&lt;h2 id=&quot;anomolies-and-thoughts&quot;&gt;Anomolies and Thoughts&lt;a href=&quot;https://rockorager.dev/blog/lsr-ls-but-with-io-uring/#anomolies-and-thoughts&quot; class=&quot;heading-link&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I have no idea what &lt;code&gt;lsd&lt;/code&gt; is doing. I haven&amp;#39;t read the source code, but from viewing it&amp;#39;s &lt;code&gt;strace&lt;/code&gt;, it is calling &lt;code&gt;clock_gettime&lt;/code&gt; around 5 times &lt;strong&gt;per file&lt;/strong&gt;. Why? I don&amp;#39;t know. Maybe it&amp;#39;s doing internal timing of steps along the way?&lt;/p&gt;&lt;p&gt;Sorting ends up being a massive part of the workload. I suspect this is where &lt;code&gt;uutils ls&lt;/code&gt; is getting slowed down, since it is doing pretty good on a syscall basis. &lt;code&gt;lsr&lt;/code&gt; spends about 30% of it&amp;#39;s runtime sorting, the rest is the IO loop.&lt;/p&gt;&lt;p&gt;This ended up being a pretty fun project to write, and didn&amp;#39;t take too much time either. I am shocked at how much io_uring can be used to reduce syscalls...&lt;code&gt;ls&lt;/code&gt; is a pretty basic example but you can only imagine how much of an effect this would have on something like a server.&lt;/p&gt;&lt;p&gt;Also - I&amp;#39;m using &lt;a href=&quot;https://tangled.sh/&quot;&gt;tangled.sh&lt;/a&gt; for this project. They have a really cool idea, and I want to see how the PR workflow is so...if you have any bugs or changes, please visit the &lt;a href=&quot;https://tangled.sh/@rockorager.dev/lsr&quot;&gt;repo&lt;/a&gt;. All you need is an atproto account + app password. I suspect more icons will be needed, feel free to make an issue for icon requests!&lt;/p&gt;</content:encoded></item></channel></rss>