<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>rockorager.dev - blog</title>
      <link>https://rockorager.dev/blog/</link>
      <description>Writing about terminals, Zig, and asynchronous I/O by Tim Culverhouse.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://rockorager.dev/blog/index.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Tue, 06 May 2025 11:15:50 -0500</lastBuildDate>
      <item>
          <title>lsr: ls but with io_uring</title>
          <pubDate>Tue, 06 May 2025 11:15:50 -0500</pubDate>
          <author>Tim Culverhouse</author>
          <link>https://rockorager.dev/blog/lsr-ls-but-with-io-uring/</link>
          <guid>https://rockorager.dev/blog/lsr-ls-but-with-io-uring/</guid>
          <description xml:base="https://rockorager.dev/blog/lsr-ls-but-with-io-uring/">&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 rel=&quot;external&quot; 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&#39;m calling it
&lt;a rel=&quot;external&quot; href=&quot;https://tangled.sh/@rockorager.dev/lsr&quot;&gt;lsr&lt;/a&gt;. Let&#39;s start with the benchmarks,
then we&#39;ll see how we got there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/lsr-ls-but-with-io-uring/screenshot.webp&quot; alt=&quot;screenshot&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;a href=&quot;#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;#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&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Program&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=10&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=100&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=1,000&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=10,000&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;lsr -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;372.6 µs&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;634.3 µs&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2.7 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;22.1 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;ls -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;1.4 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;1.7 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;4.7 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;38.0 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;eza -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2.9 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;3.3 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;6.6 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;40.2 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;lsd -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2.1 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;3.5 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;17.0 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;153.4 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;uutils ls -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;2.9 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;3.6 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;11.3 ms&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;89.6 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;syscalls&quot;&gt;Syscalls&lt;a href=&quot;#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&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Program&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=10&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=100&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=1,000&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;n=10,000&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;lsr -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;20&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;28&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;105&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;848&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;ls -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;405&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;675&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;3,377&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;30,396&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;eza -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;319&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;411&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;1,320&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;10,364&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;lsd -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;508&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;1,408&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;10,423&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;100,512&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;uutils ls -al&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;445&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;986&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;6,397&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;10,005&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;#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&#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&#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&#39;s &lt;code&gt;lsr&lt;/code&gt; allocate
memory it needs up front, but fallback to a different allocator when it&#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;#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&#39;t read the source code, but from
viewing it&#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&#39;t know. Maybe it&#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&#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&#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&#39;m using &lt;a rel=&quot;external&quot; 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 rel=&quot;external&quot; 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;
</description>
      </item>
    </channel>
</rss>

