<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Performance on Clément Joly – Open-Source, Rust &amp; SQLite</title><link>https://joly.pw/tags/performance/</link><description>Recent content in Performance on Clément Joly – Open-Source, Rust &amp; SQLite</description><image><title>Clément Joly – Open-Source, Rust &amp; SQLite</title><url>https://joly.pw/images/open-graph-home-original.png</url><link>https://joly.pw/images/open-graph-home-original.png</link></image><generator>Hugo -- 0.154.3</generator><language>en</language><copyright>Clément Joly</copyright><atom:link href="https://joly.pw/tags/performance/index.xml" rel="self" type="application/rss+xml"/><item><title>How We Built Network Analytics V2</title><link>https://joly.pw/blog/how-we-built-network-analytics-v2/</link><pubDate>Tue, 02 May 2023 15:34:19 +0100</pubDate><guid>https://joly.pw/blog/how-we-built-network-analytics-v2/</guid><description>&lt;p&gt;I co-authored this post on the Cloudflare Blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/building-network-analytics-v2/"&gt;How we built Network Analytics v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Archived copies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20230502165250/https://blog.cloudflare.com/building-network-analytics-v2/"&gt;WaybackMachine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://archive.is/x90WN"&gt;Archive.is&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://perma.cc/Q64U-92NL"&gt;Perma.cc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><content:encoded><![CDATA[<p>I co-authored this post on the Cloudflare Blog:</p>
<ul>
<li><a href="https://blog.cloudflare.com/building-network-analytics-v2/">How we built Network Analytics v2</a></li>
</ul>
<p>Archived copies:</p>
<ul>
<li><a href="https://web.archive.org/web/20230502165250/https://blog.cloudflare.com/building-network-analytics-v2/">WaybackMachine</a></li>
<li><a href="https://archive.is/x90WN">Archive.is</a></li>
<li><a href="https://perma.cc/Q64U-92NL">Perma.cc</a></li>
</ul>
]]></content:encoded></item><item><title>Should I Compress My Initramfs?</title><link>https://joly.pw/blog/should-i-compress-my-initramfs/</link><pubDate>Wed, 31 Aug 2022 06:47:08 +0100</pubDate><guid>https://joly.pw/blog/should-i-compress-my-initramfs/</guid><description>When decompressing is faster than reading from the disk.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>As a small start-up time optimization, you can pick the best suited compression algorithm for the initial ramdisk.</p>
  </div>



<h2 id="the-initial-ramdisk">The Initial Ramdisk</h2>
<p>When a Linux system boots, it needs to mount the root filesystem <code>/</code>.
This may be relatively complicated, as it may be on a software RAID, on LVM, encrypted…
To keep things manageable, an <a href="https://en.wikipedia.org/wiki/Initial_ramdisk">initial ramdisk</a> can be used to get a small environment that has all the required modules and configuration to load the root filesystem.
On <a href="https://archlinux.org/">Arch Linux</a>, this initial ramdisk is generated using <a href="https://wiki.archlinux.org/title/Mkinitcpio">mkinitcpio</a>.
It takes multiple parameters to tune various aspects of the system and of the generated ramdisk.</p>
<h3 id="compression">Compression</h3>
<p>One such parameter is <code>COMPRESSION</code>.
It compresses the ramdisk to make the resulting image smaller.
The <a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">manpage</a> reads:</p>




  <figure>
    <blockquote cite="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">
      <h4 id="compression-1">COMPRESSION</h4>
<p>Defines a program to filter the generated image through. The kernel understands the compression formats yielded by the zstd, gzip, bzip2, lz4, lzop, lzma, and xz compressors. If unspecified, this setting defaults to zstd compression. In order to create an uncompressed image, define this variable as cat.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Another reason to compress the image is that it may reduce the start-up time.
To understand why, imagine that the image is 100 MiB in size and only 20 MiB after compression.
Let&rsquo;s say that the disk reads 10 MiB<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> per second and that the CPU can decompress the full image in 1 second.
If we keep the image uncompressed, the disk will need <em>10 seconds</em> to read the uncompressed image, while it needs only 2 seconds to read the compressed image.
Adding the decompression time, the compressed version require only <em>3 seconds</em>.</p>
<h3 id="trade-offs">Trade-offs</h3>
<p>The above example is quite simple, but it illustrates the trade-off between a bigger image that the disk will take longer to read and a smaller image that may take longer to decompress. It is thus more of a spectrum, where more CPU-intensive compression (and decompression) methods could result in a smaller image and less read from the disk but more CPU time:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 760 73"
      >
      <g transform='translate(8,16)'>
<path d='M 16,32 L 712,32' fill='none' stroke='currentColor'></path>
<polygon points='24.000000,32.000000 12.000000,26.400000 12.000000,37.599998' fill='currentColor' transform='rotate(180.000000, 16.000000, 32.000000)'></polygon>
<polygon points='720.000000,32.000000 708.000000,26.400000 708.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 712.000000, 32.000000)'></polygon>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='368' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='376' y='52' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='384' y='52' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='656' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='664' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='672' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='680' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='688' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='688' y='52' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='696' y='20' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='696' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='704' y='20' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='704' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='712' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='712' y='20' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='712' y='52' fill='currentColor' style='font-size:1em'>d</text>
</g>

    </svg>
  
</div>
<p>Then, the question is: is it worth compressing an image more (or at all), to get a faster start-up time?</p>
<h2 id="protocol">Protocol</h2>
<p>To answer this question <strong>on a particular machine</strong><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, let’s compare the time required to read and decompress various initial ramdisks.</p>
<p>I’m using the <code>linux</code> package in version <em>5.18.15-arch1-2</em> from the Arch Linux repository. Then, I generate (<code>sudo mkinitcpio -p linux</code>) various images with the following parameters in <code>/etc/mkinitcpio.conf</code>:</p>
<ul>
<li><code>COMPRESSION=&quot;cat&quot;</code></li>
<li><code>COMPRESSION=&quot;lz4&quot;</code></li>
<li><code>COMPRESSION=&quot;zstd&quot;</code></li>
</ul>
<p>Each image is copied in a directory and renamed according to the compression used: <code>cp /boot/initramfs-linux.img initramfs-linux.img.zstd</code>.
The result is as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ file *.img*
</span></span><span style="display:flex;"><span>initramfs-linux.img:      ASCII cpio archive <span style="color:#56b6c2">(</span>SVR4 with no CRC<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>initramfs-linux.img.lz4:  LZ4 compressed data <span style="color:#56b6c2">(</span>v0.1-v0.9<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>initramfs-linux.img.zstd: Zstandard compressed data <span style="color:#56b6c2">(</span>v0.8+<span style="color:#56b6c2">)</span>, Dictionary ID: None
</span></span></code></pre></div><p>The images are compressing quite well too:</p>
<table id="files-size">
  <thead>
      <tr>
          <th>File</th>
          <th>Size (MiB)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>initramfs-linux.img</td>
          <td>61M</td>
      </tr>
      <tr>
          <td>initramfs-linux.img.lz4</td>
          <td>32M</td>
      </tr>
      <tr>
          <td>initramfs-linux.img.zstd</td>
          <td>22M</td>
      </tr>
  </tbody>
</table>
<p>We could compare more algorithms and compression level, but compression levels would need to be passed through <code>COMPRESSION_OPTIONS</code>, which the <a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">manpage</a> discourages, as it can result in an unbootable image.</p>
<h2 id="results">Results</h2>
<p>Let’s run some decompression commands and compare their run-time with <a href="https://github.com/sharkdp/hyperfine">hyperfine</a>. On a quiet computer:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ hyperfine <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    --prepare <span style="color:#98c379">&#39;sync; echo 3 | sudo tee /proc/sys/vm/drop_caches&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;lz4 -d &lt;./initramfs-linux.img.lz4&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;zstd -d &lt;initramfs-linux.img.zstd&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;cat &lt;initramfs-linux.img&#39;</span>
</span></span></code></pre></div><p>Note that the command has a <code>--prepare 'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches'</code> argument.
This empties the OS file system caches to be closer to start-up conditions: when the computer starts, everything has to be read from the disk as the RAM is basically empty.
Without this <code>--prepare</code> argument, we get much shorter times, e.g. 45ms for lz4.</p>
<p>Here are the results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>lz4 -d &lt;./initramfs-linux.img.lz4</code></td>
          <td style="text-align: right">137.9 ± 13.5</td>
          <td style="text-align: right">122.8</td>
          <td style="text-align: right">157.4</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>zstd -d &lt;initramfs-linux.img.zstd</code></td>
          <td style="text-align: right">164.9 ± 13.4</td>
          <td style="text-align: right">153.6</td>
          <td style="text-align: right">187.9</td>
          <td style="text-align: right">1.20 ± 0.15</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>cat &lt;initramfs-linux.img</code></td>
          <td style="text-align: right">175.9 ± 19.0</td>
          <td style="text-align: right">157.9</td>
          <td style="text-align: right">218.8</td>
          <td style="text-align: right">1.28 ± 0.19</td>
      </tr>
  </tbody>
</table>
<p>Lz4 is slightly faster, followed by zstd and no compression at all with <code>cat</code>.
If we go back to the <a href="#files-size">sizes</a> table, the trade-off between a smaller image but a slower decompression is clear.
Despite a ~30% smaller file size, zstd is still a bit slower to decompress than lz4, while no compression at all is even worse.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The above results are based on runs on a particular machine.
As mentioned different machines will yield different results, depending on the relative performance of the disk and the CPU.
It’s also a pretty small improvement in the grand scheme of things: only a few tens of milliseconds on a process that takes a couple seconds.
But I found it to be a nice example of how compression can make things faster, compared to no compression at all, because CPU nowadays are so fast.</p>
<hr>
<h2 id="appendix-recording-of-the-hyperfine-run">Appendix: Recording of the <code>hyperfine</code> Run</h2>
<p>This was done on a different run from the table above, as running the benchmark through Asciinema is sometimes a bit less stable):
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/should-i-compress-my-initramfs/hyperfine.json", document.getElementById('demo3'), {
"autoPlay":  false ,"loop":  false ,"poster": "npt:0:16",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://joly.pw/blog/should-i-compress-my-initramfs/hyperfine.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>
</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <ul>
<li>2022-08-02: Slightly reworded the intro to account for <a href="https://fosstodon.org/@pixelherodev/108927525223368261">https://fosstodon.org/@pixelherodev/108927525223368261</a></li>
<li>2022-11-13: As pointed out in <a href="https://www.reddit.com/r/archlinux/comments/ytk6t3/comment/iw74omr/">this comment on Reddit</a>, sometimes one wants to trade CPU time for disc space:




  <figure>
    <blockquote >
      <p>Another point: by default, Windows made a 100Mo EFI partition on my system. To conserve the dual boot capabilities without having to modify my partition scheme in ways I am not comfortable with, I have to compress my initramfs or it will literally not fit in the partition.</p>

    </blockquote>
    
  </figure>



</li>
<li>2022-11-14: Clarify that the 10 MiB reading speed is made up for the example.</li>
</ul>
  </div>



<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That’s a completely made up number for the sake of the example.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The conclusions will in all likelihood change depending on the machine, namely the relative performance of the CPU and the disk.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>From UltiSnips to LuaSnip</title><link>https://joly.pw/blog/ultisnips-to-luasnip/</link><pubDate>Sun, 15 May 2022 07:06:49 +0100</pubDate><guid>https://joly.pw/blog/ultisnips-to-luasnip/</guid><description>Shaving off a few milliseconds from Neovim startup time.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> is fast and doesn’t have to be complicated. Give it a try!</p>
  </div>







  
  
  
    
    
  
  

  <details class="alert alert-note">
    <summary class="alert-heading">
      ℹ️
      
        About UltiSnips
      
    </summary>
    <p>Even if that article shows how <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> shines, I have great respect for the work that has gone into <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. It is still a reliable, reasonably fast plugin given the constraint it operates in (in particular, Vim compatibility requires a fair amount of Vimscript). I’ve written this article shortly after trying LuaSnip and I’m still very much evaluating it.</p>
  </details>



<h2 id="introduction">Introduction</h2>
<p><a href="https://en.wikipedia.org/wiki/Snippet_(programming)">Snippets</a> are a convenient feature of some text editors to insert and adapt reusable pieces of code. For instance, snippets for <code>for</code> loops are common, to get the tedious bits of the syntax out of the way.</p>
<p>To get this feature in Vim back in the days, I started using <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. There are <a href="https://github.com/honza/vim-snippets">default snippets sets</a> for it, and it’s easy to write custom snippets. These custom snippet can call Bash or Python scripts if you need more dynamic replacements. UltiSnips has been very powerful and has served me quite well over the past decade or so, and I have kept it when I migrated to NeoVim a few years ago.</p>
<h2 id="startup-time">Startup time</h2>
<p>Every once in a while though, I run the excellent <a href="https://github.com/rhysd/vim-startuptime">vim-startuptime</a> command to assess the impact of various configurations and plugins on the startup time of Neovim. With UltiSnips and the corresponding completion plugins in my configuration, the first few lines are:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 104.218300 msec
</span></span><span style="display:flex;"><span>Total Max:     109.719000 msec
</span></span><span style="display:flex;"><span>Total Min:     99.533000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>64.218600 70.419000 61.066000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 6.255900  7.238000  5.764000: loading packages
</span></span><span style="display:flex;"><span> 3.939000  5.338000  3.533000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 3.651100  4.003000  3.381000: loading rtp plugins
</span></span><span style="display:flex;"><span> 2.761600  3.957000  2.474000: expanding arguments
</span></span><span style="display:flex;"><span> 2.683900  3.035000  2.566000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.418700  3.610000  2.131000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 2.389600  2.751000  2.228000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 1.954900  2.293000  1.702000: loading after plugins
</span></span><span style="display:flex;"><span> 1.842500  2.015000  1.763000: BufEnter autocommands
</span></span><span style="display:flex; background-color:#3d4148"><span> 1.583350  3.532000  0.018000: ~/.local/share/nvim/site/pack/paqs/start/ultisnips/plugin/UltiSnips.vim
</span></span><span style="display:flex;"><span> 1.027700  1.170000  0.831000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex; background-color:#3d4148"><span> 0.968200  1.071000  0.860000: ~/.local/share/nvim/site/pack/paqs/start/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua
</span></span><span style="display:flex;"><span> 0.859000  1.014000  0.702000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.557200  0.817000  0.493000: init highlight
</span></span><span style="display:flex;"><span> 0.553200  0.898000  0.322000: opening buffers</span></span></code></pre></div>
<p>The <code>init.lua</code> line covers a lot of different plugins and mappings set up in that file. Besides the <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> colorscheme<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, the next biggest contributor is <code>~/…/ultisnips/plugin/UltiSnips.vim</code>. The script to integrate UltiSnips with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a>, <code>~/…/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua</code>, is not far behind. In total, we spend nearly 4 ms of the startup time for UltiSnips-related files, on top of the setup done in <code>init.lua</code>. That feels suboptimal, so I’ve been looking for a possible snippet plugin alternative.</p>
<h2 id="installing-luasnip">Installing LuaSnip</h2>
<p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> aims to be a faster<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> snippet engine, with support of treesitter in the snippets. It can also understand the LSP snippet “format”. Finally, it’s a pure Lua plugin, without any Python requirement, contrary to UltiSnips. That makes installation on various system much easier.</p>
<p>Its drawbacks for me are that it does not support the same snippet definition as UltiSnips and even in its own SnipMate-like syntax, backticks to execute code are not supported. As a result, I’ll have to migrate my (admittedly very small) snippet collection. Conversely, if I want to do more advanced things, I’ll have to learn the relatively complex “VS Studio Code” snippets in JSON or even the pure Lua snippets.</p>
<p>That’s said, I believe I can get the LuaSnip benefits without writing Lua snippets or JSON ones, at least to start. So let’s try and do that, using only the SnipMate-like syntax!</p>
<h2 id="overview-of-the-configuration-changes">Overview of the Configuration Changes</h2>
<p>I’ve made the following changes.</p>
<ol>
<li>Remove UltiSnips and its associated plugins, namely for completion with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a> and the telescope integration.</li>
<li>Remove the UltiSnips mappings.</li>
<li>Add LuaSnip (following the instructions in the <a href="https://github.com/L3MON4D3/LuaSnip">Readme</a>), <a href="https://github.com/saadparwaiz1/cmp_luasnip">its cmp integration</a>, a <a href="https://github.com/rafamadriz/friendly-snippets">set of snippets</a> and a <a href="https://github.com/benfowler/telescope-luasnip.nvim">telescope integration</a> along with some mappings (requires nvim 0.7+<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>):
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">set</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;C-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip forward jump&#34;</span> })
</span></span><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">se</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;M-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#56b6c2">-</span><span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip backward jump&#34;</span> })
</span></span></code></pre></div></li>
<li><a href="#migrate-my-snippet-collection">Migrate my existing snippets</a>, see below.</li>
<li>Add code to load the snippet set and my own snippet collection:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_vscode&#34;</span>).<span style="color:#e06c75">lazy_load</span>()
</span></span><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_snipmate&#34;</span>).<span style="color:#e06c75">lazy_load</span>({ <span style="color:#e06c75">paths</span> <span style="color:#56b6c2">=</span> {<span style="color:#98c379">&#34;./snippets&#34;</span>} })
</span></span></code></pre></div></li>
</ol>
<h3 id="after">After</h3>
<p>Let’s run vim-startuptime again. Here are the new top contributors:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 98.251400 msec
</span></span><span style="display:flex;"><span>Total Max:     100.159000 msec
</span></span><span style="display:flex;"><span>Total Min:     96.519000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>60.469100 62.341000 59.619000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 5.991100  6.219000  5.865000: loading packages
</span></span><span style="display:flex;"><span> 4.088400  4.162000  3.942000: loading after plugins
</span></span><span style="display:flex;"><span> 3.635700  3.752000  3.415000: loading rtp plugins
</span></span><span style="display:flex;"><span> 3.561800  3.936000  3.220000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 2.550500  2.628000  2.492000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.454200  2.525000  2.401000: expanding arguments
</span></span><span style="display:flex;"><span> 2.363000  2.420000  2.270000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 2.271100  2.337000  2.146000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 1.701000  1.767000  1.635000: BufEnter autocommands
</span></span><span style="display:flex;"><span> 1.676700  2.049000  1.366000: opening buffers
</span></span><span style="display:flex;"><span> 1.034100  1.280000  0.782000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex;"><span> 0.901000  1.021000  0.799000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.549500  0.789000  0.514000: init highlight</span></span></code></pre></div></p>
<p>The snippet infrastructure is now absent from that list! More importantly, the overall startup time is down, and we can be confident that the new calls to load the snippets in <code>init.lua</code> are not costlier than UltiSnips settings before, because the <code>init.lua</code> line is down as well.</p>
<p>As a bonus, the snippet expansion feels slightly snappier with LuaSnip, although it might be an illusion and I don’t have hard numbers to back this claim.</p>
<h2 id="migrate-my-snippet-collection">Migrate my Snippet Collection</h2>
<p>Back to the topic of migrating existing UltiSnips snippets: LuaSnip will loudly complain when given UltiSnips snippets but it’s relatively easy to rewrite those snippets to the SnipMate-like format that LuaSnip understands.</p>
<h3 id="two-syntaxes">Two Syntaxes</h3>
<p>On the one hand, UltiSnips snippets roughly follow this syntax</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet trigger &#34;Comment&#34; option
snippet content
endsnippet
</code></pre><p>And so, my markdown snippets would look like this:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">priority 10

snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>On the other hand, LuaSnip uses a simplified version of SnipMate snippets:</p>
<pre tabindex="0"><code># Comment
snippet toggle
  snippet content
</code></pre><h3 id="simple-snippets">Simple Snippets</h3>
<p>Since I heavily rely on snippet sets, I have only about 30 snippets defined in my own snippet collection. Most of them are really simple, with only a few lines and almost no interactive text. So for most of those, the process has been to simply remove the <code>priority …</code> lines and then a simple substitution command<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> did the trick.</p>
<h3 id="advanced-snippets-with-environment-variables">Advanced Snippets With Environment Variables</h3>
<p>However, there is also the case of the snippets calling external commands, like <code>date</code> in the example below:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>The problem is, to call arbitrary commands, one needs to define the snippets in Lua.</p>
<p>It turns out though that nearly all my snippets calling external command were actually inserting a date. And luckily LuaSnip defines “environment variables” holding just the values I need like <code>$CURRENT_MONTH</code>. So that snippet becomes:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet"># “Bullet Journal”-styled date for today
snippet bjtoday
  # ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} $CURRENT_DAY_NAME_SHORT
</code></pre><p>and we get to keep the simple SnipMate-like syntax!</p>
<p>You can find more of those environment variables in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/util/environ.lua">the sources</a>.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-08-03: See also <a href="https://github.com/smjonas/snippet-converter.nvim">snippet-converter.nvim</a> to convert between various snippet formats.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
  </div>



<h2 id="whats-next">What’s Next</h2>
<p>I now have a slightly faster NeoVim and the snippet syntax that I use the most is simpler. However, there is more to explore!</p>
<p>So far, I’ve steered clear of writing more complicated JSON and Lua snippets. The latter would be necessary to unlock smart snippets using treesitter <a href="https://changelog.com/podcast/457#t=01:00:01.17">context</a>. I’ll look into that next, in particular to generate go error handling code.</p>
<p>This is also a very simple configuration, the impact on startup time of LuaSnip might go up slightly as we use more advanced feature, but during my tests, even using all available features, it was much lighter than UltiSnips.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-07-31: <a href="https://joly.pw/tags/luasnip/">Follow-up posts</a> dig deeper in other aspects of LuaSnip</p>
  </div>



<h2 id="resources">Resources</h2>
<ul>
<li>Many more details are covered in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md">LuaSnip documentation</a>.</li>
<li>The <a href="https://github.com/garbas/vim-snipmate/blob/master/doc/SnipMate.txt">SnipMate help</a> contains the SnipMate snippet syntax, if you are unfamiliar with it.</li>
<li>The <a href="https://github.com/microsoft/language-server-protocol/blob/main/snippetSyntax.md">LSP snippet documentation</a> is also helpful.</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That run is actually on a fork of <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> that contains a massive speed up. The changes of that fork are being up streamed in <a href="https://github.com/navarasu/onedark.nvim/pull/76">this PR</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>According to that comment from the author for instance: <a href="https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664">https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>This code snippet uses new APIs introduced in Neovim 0.7 and the newly freed <code>&lt;C-i&gt;</code>, see <a href="https://gpanders.com/blog/whats-new-in-neovim-0-7/">https://gpanders.com/blog/whats-new-in-neovim-0-7/</a> for more details.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>For instance: <code>%s/^\(snippet \+\S\+\) &quot;\(.*\)&quot; \w\+\n\(\_.\{-}\)endsnippet/# \2^M\1^M \3^M</code>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Thanks to <a href="https://www.reddit.com/user/Miserable-Ad-7341">Miserable-Ad-7341</a> for <a href="https://www.reddit.com/r/neovim/comments/weonip/from_ultisnips_to_luasnip/iipheov/">pointing this out</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Git ls-files is Faster Than Fd and Find</title><link>https://joly.pw/blog/git-ls-files-is-faster-than-fd-and-find/</link><pubDate>Thu, 04 Nov 2021 06:06:21 +0000</pubDate><guid>https://joly.pw/blog/git-ls-files-is-faster-than-fd-and-find/</guid><description>Git ls-files is up to 5 times faster than fd or find in this benchmark, but why?</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>In the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/">Linux Git repository</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/tldr.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.9 ± 0.5</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">18.2</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.1 ± 0.7</td>
          <td style="text-align: right">92.4</td>
          <td style="text-align: right">95.7</td>
          <td style="text-align: right">5.52 ± 0.16</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">85.8 ± 7.5</td>
          <td style="text-align: right">81.1</td>
          <td style="text-align: right">111.3</td>
          <td style="text-align: right">5.08 ± 0.47</td>
      </tr>
  </tbody>
</table>
<p><code>git ls-files</code> is more than <em>5 times faster</em> than both <code>fd --no-ignore</code> and <code>find</code>!</p>
  </div>



<h2 id="introduction">Introduction</h2>
<p>In my <a href="https://joly.pw/blog/my-setup/nvim-0-5/">editor</a> I changed my mapping to open files from <code>fd</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> to <code>git ls-files</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> and I noticed it felt faster after the change. But that’s intriguing, given <code>fd</code>’s goal to be <a href="https://github.com/sharkdp/fd#benchmark">very fast</a>. Git on the other hand is primarily a source code management system (SCM), it’s main business<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> is not to help you list your files! Let’s run some benchmarks to make sure.</p>
<h2 id="benchmarks">Benchmarks</h2>
<p>Is <code>git ls-files</code> actually faster than <code>fd</code> or is that just an illusion? In our benchmark, we will use:</p>
<ul>
<li><code>fd</code> 8.2.1</li>
<li><code>git</code> 2.33.0</li>
<li><code>find</code> 4.8.0</li>
<li><code>hyperfine</code> 1.11.0</li>
</ul>
<p>We run the benchmarks with disk-cache filled, we are not measuring the cold cache case. That’s because in your editor, you may use the commands mentioned multiple times and would benefit from cache. The results are similar for an in memory repo, which confirms cache filling.</p>
<p>Also, you work on those files, so they should be in cache to a degree. We also make sure to be on a quiet PC, with CPU power-saving deactivated. Furthermore, the CPU has 8 cores with hyper-threading, so <code>fd</code> uses 8 threads. Last but not least, unless otherwise noted, the files in the repo are only the ones committed, for instance, no build artifacts are present.</p>
<h3 id="a-test-git-repository">A Test Git Repository</h3>
<p>We first need a Git repository. I’ve chosen to clone<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/">Linux kernel repo</a> because it is a fairly big one and a <a href="https://github.blog/2020-12-22-git-clone-a-data-driven-study-on-cloning-behaviors/">reference</a> for Git performance measurements. This is important to ensure searches take a non-trivial amount of time: as hyperfine rightfully points out, short run times (less than 5 ms) are more difficult to accurately compare.</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">git</span> clone <span style="color:#e06c75">--depth</span> <span style="color:#d19a66">1</span> <span style="color:#e06c75">--recursive</span> ssh://git@github.com/torvalds/linux.git ~/ghq/github.com/torvalds/linux
</span></span><span style="display:flex;"><span><span style="color:#c678dd">cd</span> ~/ghq/github.com/torvalds/linux
</span></span></code></pre></div><h4 id="choosing-the-commands">Choosing the commands</h4>
<p>We want to evaluate <code>git ls-files</code> versus <code>fd</code> and <code>find</code>. However, getting exactly the same list of file is not a trivial task:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Output lines</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">72219</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">77039</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">76705</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden</code></td>
          <td style="text-align: right">77038</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd</code></td>
          <td style="text-align: right">72363</td>
      </tr>
  </tbody>
</table>
<p>After some more tries, it turns out that this command gives exactly<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> the same output as <code>git ls-files</code>:</p>
<pre tabindex="0"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink
</code></pre><p>It is a fairly complicated command, with various criteria on the files to print and that could translate to an unfair advantage to <code>git ls-files</code>. Consequently, we will also use the simpler examples in the table above.</p>
<h3 id="hyperfine">Hyperfine</h3>
<p><a href="https://github.com/sharkdp/hyperfine">Hyperfine</a> is a great tool to compare various commands: it has a colored and markdown output, attempts to detect outliers, tunes the number of run… Here is an <a href="https://asciinema.org/">asciinema</a><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> showing its output<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>:</p>
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/git-ls-files-is-faster-than-fd-and-find/hyperfine.json", document.getElementById('demo3'), {
"cols": "103","loop": "true","preload":  1 ,"rows": "32","speed": "4",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://joly.pw/blog/git-ls-files-is-faster-than-fd-and-find/hyperfine.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<h3 id="first-results">First Results</h3>
<p>For our first benchmark, on an SSD with <a href="https://en.wikipedia.org/wiki/Btrfs">btrfs</a>, with commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ad347abe4a9876b1f65f408ab467137e88f77eb4"><code>ad347abe4a…</code></a> checked out, we run:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">1</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden&#39;</span> <span style="color:#98c379">&#39;fd&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><p>This yields the following results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.9 ± 0.6</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">19.2</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.2 ± 0.5</td>
          <td style="text-align: right">92.5</td>
          <td style="text-align: right">94.8</td>
          <td style="text-align: right">5.50 ± 0.19</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.6 ± 7.8</td>
          <td style="text-align: right">80.5</td>
          <td style="text-align: right">115.7</td>
          <td style="text-align: right">5.11 ± 0.49</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden</code></td>
          <td style="text-align: right">121.0 ± 6.2</td>
          <td style="text-align: right">112.3</td>
          <td style="text-align: right">132.3</td>
          <td style="text-align: right">7.14 ± 0.44</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd</code></td>
          <td style="text-align: right">231.6 ± 22.3</td>
          <td style="text-align: right">200.8</td>
          <td style="text-align: right">272.5</td>
          <td style="text-align: right">13.68 ± 1.40</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.9 ± 5.0</td>
          <td style="text-align: right">77.5</td>
          <td style="text-align: right">95.3</td>
          <td style="text-align: right">4.78 ± 0.34</td>
      </tr>
  </tbody>
</table>
<p>As mentioned in the TL;DR, <code>git ls-files</code> is at least 5 times faster than its closest competitor! Let’s find out why that is.</p>
<h2 id="how-does-git-store-files-in-a-repository">How Does Git Store Files in a Repository</h2>
<p>To try to understand where this performance advantage of <code>git ls-files</code> comes from, let’s look into how files are stored in a repository. This is a quick overview, you can find more details about Git’s storage internals in <a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects">this section of the Pro Git book</a>.</p>
<h3 id="git-objects">Git Objects</h3>
<p>Git builds its own internal representation of the file system tree in the repository:</p>
<figure>
    <img loading="lazy" src="./data-model-2.png"
         alt="Internal Git representation of the file system tree" width="800" height="593"/> <figcaption>
            Internal Git representation of the file system tree<p>From the Pro Git book, written by Scott Chacon and Ben Straub and published by Apress, licensed under the <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution Non Commercial Share Alike 3.0</a> license, copyright 2021.</p>
        </figcaption>
</figure>

<p>In the figure above, each tree object contains a list of folder or names and references to these (among other things). This representation is then stored by its hash in the <code>.git</code> folder, like so:</p>
<pre tabindex="0"><code>.git/objects
├── 65
│  └── 107a3367b67e7a50788f575f73f70a1e61c1df
├── e6
│  └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
├── f0
│  └── f1a67ce36d6d87e09ea711c62e88b135b60411
├── info
└── pack
</code></pre><p>As a result, to list the content of a folder, it seems Git has to access the corresponding tree object, stored in a file contained in a folder with the beginning of the hash. But doing that for the currently checked out files all the time would be slow, especially for frequently used commands like <code>git status</code>. Fortunately, git also maintains an <em>index</em> for files in the current working directory.</p>
<h3 id="git-index">Git Index</h3>
<p>This <a href="https://git-scm.com/docs/index-format">index</a>, lists (among other things) each file in the repository with file-system metadata like last modification time. More details and examples are provided <a href="https://medium.com/hackernoon/understanding-git-index-4821a0765cf">here</a>.</p>
<p>So, it seems that the index has everything <code>ls-files</code> requires. Let’s check it is used by <code>ls-files</code></p>
<h2 id="strace">Strace</h2>
<p>Let’s ensure that <code>ls-files</code> uses only the index, without scanning many files in the repo or the <code>.git</code> folder. That would explain its performance advantage, as reading a file is cheaper than traversing many folders. To this end, we’ll use <code>strace</code><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">strace</span> <span style="color:#e06c75">-e</span> !write git ls-files<span style="color:#56b6c2">&gt;</span>/dev/null <span style="color:#d19a66">2</span><span style="color:#56b6c2">&gt;</span>/tmp/a
</span></span></code></pre></div><p>It turns out the <a href="https://git-scm.com/docs/index-format"><code>.git/index</code></a> is read:</p>
<pre tabindex="0"><code>openat(AT_FDCWD, &#34;.git/index&#34;, O_RDONLY) = 3
</code></pre><p>And we are not reading objects in the <code>.git</code> folder or files in the repository. A quick check of Git’s source code <a href="https://github.com/git/git/blob/33be431c0c7284c1adf0fe49f7838dbc8aee6ea9/builtin/ls-files.c#L761">confirms</a> this. We now have an explanation for the speed <code>git ls-files</code> displays in our benchmarks!</p>
<h2 id="other-scenarios">Other Scenarios</h2>
<p>However, listing file in a fully committed repository is not the most common case when you work on your code: as you make changes, a larger portion of the files are changed or added. How does <code>git ls-files</code> compare in these other scenarios?</p>
<h3 id="with-changes">With Changes</h3>
<p>When there are changes to some files, we shouldn’t see any significant performance difference: the index is still usable directly to get the names of the files in the repository, we don’t really care about whether their content changed.</p>
<p>To check this, let’s change all the C files in the kernel sources (using some <a href="https://fishshell.com/">fish</a> shell scripting):</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#c678dd">for</span> <span style="color:#e06c75">f</span> <span style="color:#c678dd">in</span> <span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">fd</span> <span style="color:#e06c75">-e</span> c<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#c678dd">echo</span> <span style="color:#d19a66">1</span> <span style="color:#56b6c2">&gt;&gt;</span> <span style="color:#e06c75">$f</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">git</span> <span style="color:#e5c07b">status</span> <span style="color:#56b6c2">|</span> <span style="color:#61afef;font-weight:bold">wc</span> <span style="color:#e06c75">-l</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">28350</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">2</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.8 ± 0.5</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">18.9</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.5 ± 0.7</td>
          <td style="text-align: right">92.7</td>
          <td style="text-align: right">95.5</td>
          <td style="text-align: right">5.55 ± 0.17</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.1 ± 7.3</td>
          <td style="text-align: right">80.9</td>
          <td style="text-align: right">112.6</td>
          <td style="text-align: right">5.12 ± 0.46</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.8 ± 6.6</td>
          <td style="text-align: right">77.8</td>
          <td style="text-align: right">115.0</td>
          <td style="text-align: right">4.80 ± 0.42</td>
      </tr>
  </tbody>
</table>
<p>We see the same numbers as before and it is again consistent with <a href="https://github.com/git/git/blob/33be431c0c7284c1adf0fe49f7838dbc8aee6ea9/builtin/ls-files.c#L761">ls-files source code</a>.</p>
<p>Run <code>git checkout -f @</code> after this to remove the changes made to the files.</p>
<h3 id="with-new-files-and--o">With New Files and <code>-o</code></h3>
<p>With yet uncommitted files, there are two subcases:</p>
<ul>
<li>files were created and added (with <code>git add</code>): then the files are in index and reading the index is enough for <code>ls-files</code>, like above,</li>
<li>files were created but not added: these files are not present in the index, but without the <code>-o</code> flag, <code>ls-files</code> won’t output them either, so it can still use the index, as before.</li>
</ul>
<p>So the only case that needs further investigations is the use of <code>-o</code>. Since we don’t have baseline results yet for <code>-o</code>, let’s first see how it compares without any unadded new files.</p>
<h4 id="without-any-unadded-new-files-baseline">Without any Unadded New Files (Baseline)</h4>
<p>When we haven’t added any new files in the repository:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">3</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;git ls-files -o&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.7 ± 0.5</td>
          <td style="text-align: right">16.1</td>
          <td style="text-align: right">17.9</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>git ls-files -o</code></td>
          <td style="text-align: right">69.1 ± 0.7</td>
          <td style="text-align: right">67.8</td>
          <td style="text-align: right">70.8</td>
          <td style="text-align: right">4.12 ± 0.12</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">94.3 ± 0.5</td>
          <td style="text-align: right">93.4</td>
          <td style="text-align: right">95.3</td>
          <td style="text-align: right">5.63 ± 0.16</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.6 ± 7.0</td>
          <td style="text-align: right">80.8</td>
          <td style="text-align: right">106.0</td>
          <td style="text-align: right">5.17 ± 0.44</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.8 ± 7.4</td>
          <td style="text-align: right">77.9</td>
          <td style="text-align: right">118.0</td>
          <td style="text-align: right">4.82 ± 0.46</td>
      </tr>
  </tbody>
</table>
<p>That suggests that <code>git ls-files -o</code> is performing some more work besides “just” reading the index. With <code>strace</code>, we see lines like:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">strace</span> <span style="color:#e06c75">-e</span> !write git ls-files <span style="color:#e06c75">-o</span><span style="color:#56b6c2">&gt;</span>/dev/null <span style="color:#d19a66">2</span><span style="color:#56b6c2">&gt;</span>/tmp/a
</span></span><span style="display:flex;"><span>…
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">openat</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">AT_FDCWD</span>, <span style="color:#98c379">&#34;Documentation/&#34;</span>, O_RDONLY<span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_NONBLOCK</span><span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_CLOEXEC</span><span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_DIRECTORY</span><span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">4</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">newfstatat</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">4</span>, <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#56b6c2">{</span><span style="color:#e06c75">st_mode</span><span style="color:#56b6c2">=</span>S_IFDIR<span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">0755</span>, <span style="color:#e06c75">st_size</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">1446</span>, ...<span style="color:#56b6c2">}</span>, AT_EMPTY_PATH<span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">getdents64</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">4</span>, 0x55df0a6e6890 /* <span style="color:#d19a66">99</span> entries */, <span style="color:#d19a66">32768</span><span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">3032</span>
</span></span><span style="display:flex;"><span>…
</span></span></code></pre></div><h4 id="with-unadded-new-files">With Unadded New Files</h4>
<p>Let’s add some files now:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#c678dd">for</span> <span style="color:#e06c75">f</span> <span style="color:#c678dd">in</span> <span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">seq</span> <span style="color:#d19a66">1</span> <span style="color:#d19a66">1000</span><span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#61afef;font-weight:bold">touch</span> <span style="color:#e06c75">$f</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span></code></pre></div><p>And compare with our baseline:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">4</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;git ls-files -o&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.8 ± 0.5</td>
          <td style="text-align: right">16.1</td>
          <td style="text-align: right">18.0</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>git ls-files -o</code></td>
          <td style="text-align: right">69.9 ± 1.2</td>
          <td style="text-align: right">68.1</td>
          <td style="text-align: right">72.6</td>
          <td style="text-align: right">4.17 ± 0.14</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">94.5 ± 0.6</td>
          <td style="text-align: right">93.4</td>
          <td style="text-align: right">96.3</td>
          <td style="text-align: right">5.64 ± 0.17</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.8 ± 7.5</td>
          <td style="text-align: right">81.5</td>
          <td style="text-align: right">114.4</td>
          <td style="text-align: right">5.18 ± 0.48</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">81.0 ± 4.5</td>
          <td style="text-align: right">78.6</td>
          <td style="text-align: right">96.3</td>
          <td style="text-align: right">4.83 ± 0.31</td>
      </tr>
  </tbody>
</table>
<p>There is little to no statically significant difference to our baseline, which highlights that much of the time is spent on things relatively independent of the number of files processed. It’s also worth noting that there is relatively little speed difference between <code>git ls-files -o</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code>.</p>
<p>Using <code>strace</code>, we can establish that all commands but <code>git ls-files</code> were reading all files in the repository. By comparing the <code>strace</code> outputs of <code>git ls-files -o</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code> (the two commands that print the same file list), we can see that they make similar system calls for each file in the repository. How to explain the (small) time difference between the two? I haven’t found convincing reasons in git source code for this case. It might be that the use of the <code>index</code> gives <code>ls-files</code> a head start.</p>
<h2 id="conclusions">Conclusions</h2>
<p>I’m now using <code>git ls-files</code> in my <a href="https://joly.pw/blog/my-setup/nvim-0-5/">keyboard driven text editor</a> instead of <code>fd</code> or <code>find</code>. It is faster, although the perceived difference described in the Introduction is probably due to spikes in latency on a cold cache. The selection of files is also narrowed down with <code>ls-files</code> to the ones I care about. That’s said, I’ve still kept the <code>fd</code>-based file listing as a fallback, as sometimes I’m not in a Git repository.</p>
<p>After all, Git is already building an index, why not use it to speed up your jumping from file to file!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>With <a href="https://github.com/nvim-telescope/telescope.nvim">Telescope.nvim</a> <code>:Telescope find_files</code>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>With <a href="https://github.com/nvim-telescope/telescope.nvim">Telescope.nvim</a> <code>:Telescope git_files show_untracked=false</code>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>That’s not to say git is slow, on the contrary, when one reads the release notes, it’s obvious that a lot of performance optimization work is done.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Using a shallow clone makes it faster for you to reproduce results locally. However, running the benchmarks again on a full clone does not significantly change the results.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Using the <code>diff</code> command on the outputs of <code>git ls-files</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>This is inserted in this page using my <a href="https://joly.pw/gohugo-asciinema/?ref=git-faster-fd-find">asciinema hugo module</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>This output has been edited to remove the warning about outliers. These warning appeared only with <code>asciinema</code>, probably because it is disturbing the benchmark. This also explains why the values in this “asciicast” are different from the tables in the rest of the article: I’ve used values from runs outside asciinema for these tables.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>See also <a href="https://jvns.ca/blog/2014/04/20/debug-your-programs-like-theyre-closed-source/">https://jvns.ca/blog/2014/04/20/debug-your-programs-like-theyre-closed-source/</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>GoHugo Asciinema</title><link>https://joly.pw/gohugo-asciinema/</link><pubDate>Sat, 21 Aug 2021 16:25:33 +0100</pubDate><guid>https://joly.pw/gohugo-asciinema/</guid><description>⏯️ Insert the Asciinema Player in your Hugo site with ease.</description><content:encoded><![CDATA[
<p style="display: flex; justify-content: space-between">
  <a href="https://github.com/cljoly/gohugo-asciinema" data-goatcounter-click="ext-github-gohugo-asciinema" data-goatcounter-title="cljoly/gohugo-asciinema">
    <span class="svgicon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
    stroke-linecap="round" stroke-linejoin="round">
    <path
        d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22">
    </path>
</svg></span>&nbsp;cljoly/gohugo-asciinema
  </a>
  <a class="badges" href="https://github.com/cljoly/gohugo-asciinema" data-goatcounter-click="ext-stargithub-gohugo-asciinema" data-goatcounter-title="stars cljoly/gohugo-asciinema">
    <img src="https://img.shields.io/github/stars/cljoly/gohugo-asciinema?style=social" alt="Github stars for gohugo-asciinema">
  </a>
</p>


<div class="badges">


<p>

  <img alt="Min Hugo Version: 0.77.0" loading="lazy" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjkiIGhlaWdodD0iMjAiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0ibWluIEh1Z28gdmVyc2lvbjogMC43OC4wIj48dGl0bGU+bWluIEh1Z28gdmVyc2lvbjogMC43OC4wPC90aXRsZT48bGluZWFyR3JhZGllbnQgaWQ9InMiIHgyPSIwIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjYmJiIiBzdG9wLW9wYWNpdHk9Ii4xIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9InIiPjxyZWN0IHdpZHRoPSIxNjkiIGhlaWdodD0iMjAiIHJ4PSIzIiBmaWxsPSIjZmZmIi8+PC9jbGlwUGF0aD48ZyBjbGlwLXBhdGg9InVybCgjcikiPjxyZWN0IHdpZHRoPSIxMjQiIGhlaWdodD0iMjAiIGZpbGw9IiM1NTUiLz48cmVjdCB4PSIxMjQiIHdpZHRoPSI0NSIgaGVpZ2h0PSIyMCIgZmlsbD0iIzlmOWY5ZiIvPjxyZWN0IHdpZHRoPSIxNjkiIGhlaWdodD0iMjAiIGZpbGw9InVybCgjcykiLz48L2c+PGcgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IlZlcmRhbmEsR2VuZXZhLERlamFWdSBTYW5zLHNhbnMtc2VyaWYiIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIGZvbnQtc2l6ZT0iMTEwIj48aW1hZ2UgeD0iNSIgeT0iMyIgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiBocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUJtYVd4c1BTSWpSa1kwTURnNElpQnliMnhsUFNKcGJXY2lJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JajQ4ZEdsMGJHVStTSFZuYnp3dmRHbDBiR1UrUEhCaGRHZ2daRDBpVFRFeExqYzFOQ0F3WVRNdU9UazRJRE11T1RrNElEQWdNREF0TWk0d05Ea3VOVGsyVERNdU16TWdOQzQxTXpKaE5DNHlOVElnTkM0eU5USWdNQ0F3TUMweUxqQXhOeUF6TGpZeE5YWTRMakF6WXpBZ01TNDBOek11TnprZ01pNDRNemdnTWk0d05qY2dNeTQxTnpSc05pNDBPRFlnTXk0M016TmhNeTQ0T0NBekxqZzRJREFnTURBekxqZ3pOUzR3TVRoc055NHdORE10TXk0NU5qWmhNeTQ0TVRjZ015NDRNVGNnTUNBd01ERXVPVFF6TFRNdU16SXpWamN1TnpVeVlUTXVOVGNnTXk0MU55QXdJREF3TFRFdU56YzBMVE11TURnMFRERXpMamd4Tnk0MU5ERmhNeTQ1T1RnZ015NDVPVGdnTUNBd01DMHlMakEyTXkwdU5UUjZiUzR3TWpJZ01TNDJOelJqTGpReE15MHVNREEyTGpneU9DNHhJREV1TWk0ek1UVnNOeTR3T1RVZ05DNHhNamRqTGpVNE5DNHpOQzQ1TkRFdU9UWXVPVFFnTVM0Mk16VjJPQzQwTmpKak1DQXVOemMwTFM0ME1UUWdNUzQwT0RRdE1TNHdPRGtnTVM0NE5qUnNMVGN1TURReUlETXVPVFkyWVRJdU1UazVJREl1TVRrNUlEQWdNREV0TWk0eE56a3RMakF4YkMwMkxqUTROUzB6TGpjek5HRXlMalEwTnlBeUxqUTBOeUF3SURBeExURXVNakk0TFRJdU1USXpkaTA0TGpBell6QXRMamc1TXk0ME5qRXRNUzQzTWlBeExqSXlNUzB5TGpFNWJEWXVNemMyTFRNdU9UTTFZVEl1TXpJeklESXVNekl6SURBZ01ERXhMakU1TFM0ek5EZDZiUzAwTGpjZ015NDRORFJXTVRndU16ZG9NaTQyT1hZdE5TNDJNbWcwTGpRMmRqVXVOakpvTWk0Mk9UWldOUzQxTVRob0xUSXVOamsyZGpRdU5qZ3hhQzAwTGpRMlZqVXVOVEU0V2lJdlBqd3ZjM1puUGc9PSIvPjx0ZXh0IGFyaWEtaGlkZGVuPSJ0cnVlIiB4PSI3MTUiIHk9IjE1MCIgZmlsbD0iIzAxMDEwMSIgZmlsbC1vcGFjaXR5PSIuMyIgdHJhbnNmb3JtPSJzY2FsZSguMSkiIHRleHRMZW5ndGg9Ijk3MCI+bWluIEh1Z28gdmVyc2lvbjwvdGV4dD48dGV4dCB4PSI3MTUiIHk9IjE0MCIgdHJhbnNmb3JtPSJzY2FsZSguMSkiIGZpbGw9IiNmZmYiIHRleHRMZW5ndGg9Ijk3MCI+bWluIEh1Z28gdmVyc2lvbjwvdGV4dD48dGV4dCBhcmlhLWhpZGRlbj0idHJ1ZSIgeD0iMTQ1NSIgeT0iMTUwIiBmaWxsPSIjMDEwMTAxIiBmaWxsLW9wYWNpdHk9Ii4zIiB0cmFuc2Zvcm09InNjYWxlKC4xKSIgdGV4dExlbmd0aD0iMzUwIj4wLjc4LjA8L3RleHQ+PHRleHQgeD0iMTQ1NSIgeT0iMTQwIiB0cmFuc2Zvcm09InNjYWxlKC4xKSIgZmlsbD0iI2ZmZiIgdGV4dExlbmd0aD0iMzUwIj4wLjc4LjA8L3RleHQ+PC9nPjwvc3ZnPg==">
<a href="https://github.com/cljoly/gohugo-asciinema/releases.atom">

  <img alt="RSS feed of new versions" loading="lazy" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzkiIGhlaWdodD0iMjAiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0ic3Vic2NyaWJlOiB3aXRoIFJTUyI+PHRpdGxlPnN1YnNjcmliZTogd2l0aCBSU1M8L3RpdGxlPjxsaW5lYXJHcmFkaWVudCBpZD0icyIgeDI9IjAiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNiYmIiIHN0b3Atb3BhY2l0eT0iLjEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iLjEiLz48L2xpbmVhckdyYWRpZW50PjxjbGlwUGF0aCBpZD0iciI+PHJlY3Qgd2lkdGg9IjEzOSIgaGVpZ2h0PSIyMCIgcng9IjMiIGZpbGw9IiNmZmYiLz48L2NsaXBQYXRoPjxnIGNsaXAtcGF0aD0idXJsKCNyKSI+PHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjIwIiBmaWxsPSIjNTU1Ii8+PHJlY3QgeD0iODAiIHdpZHRoPSI1OSIgaGVpZ2h0PSIyMCIgZmlsbD0iI2ZmYTUwMCIvPjxyZWN0IHdpZHRoPSIxMzkiIGhlaWdodD0iMjAiIGZpbGw9InVybCgjcykiLz48L2c+PGcgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IlZlcmRhbmEsR2VuZXZhLERlamFWdSBTYW5zLHNhbnMtc2VyaWYiIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIGZvbnQtc2l6ZT0iMTEwIj48aW1hZ2UgeD0iNSIgeT0iMyIgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiBocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUJtYVd4c1BTSWpSa1pCTlRBd0lpQnliMnhsUFNKcGJXY2lJSFpwWlhkQ2IzZzlJakFnTUNBeU5DQXlOQ0lnZUcxc2JuTTlJbWgwZEhBNkx5OTNkM2N1ZHpNdWIzSm5Mekl3TURBdmMzWm5JajQ4ZEdsMGJHVStVbE5UUEM5MGFYUnNaVDQ4Y0dGMGFDQmtQU0pOTVRrdU1UazVJREkwUXpFNUxqRTVPU0F4TXk0ME5qY2dNVEF1TlRNeklEUXVPQ0F3SURRdU9GWXdZekV6TGpFMk5TQXdJREkwSURFd0xqZ3pOU0F5TkNBeU5HZ3ROQzQ0TURGNlRUTXVNamt4SURFM0xqUXhOV014TGpneE5DQXdJRE11TWpreklERXVORGM1SURNdU1qa3pJRE11TWprMUlEQWdNUzQ0TVRNdE1TNDBPRFVnTXk0eU9TMHpMak13TVNBekxqSTVRekV1TkRjZ01qUWdNQ0F5TWk0MU1qWWdNQ0F5TUM0M01YTXhMalEzTlMwekxqSTVOQ0F6TGpJNU1TMHpMakk1TlhwTk1UVXVPVEE1SURJMGFDMDBMalkyTldNd0xUWXVNVFk1TFRVdU1EYzFMVEV4TGpJME5TMHhNUzR5TkRRdE1URXVNalExVmpndU1EbGpPQzQzTWpjZ01DQXhOUzQ1TURrZ055NHhPRFFnTVRVdU9UQTVJREUxTGpreGVpSXZQand2YzNablBnPT0iLz48dGV4dCBhcmlhLWhpZGRlbj0idHJ1ZSIgeD0iNDk1IiB5PSIxNTAiIGZpbGw9IiMwMTAxMDEiIGZpbGwtb3BhY2l0eT0iLjMiIHRyYW5zZm9ybT0ic2NhbGUoLjEpIiB0ZXh0TGVuZ3RoPSI1MzAiPnN1YnNjcmliZTwvdGV4dD48dGV4dCB4PSI0OTUiIHk9IjE0MCIgdHJhbnNmb3JtPSJzY2FsZSguMSkiIGZpbGw9IiNmZmYiIHRleHRMZW5ndGg9IjUzMCI+c3Vic2NyaWJlPC90ZXh0Pjx0ZXh0IGFyaWEtaGlkZGVuPSJ0cnVlIiB4PSIxMDg1IiB5PSIxNTAiIGZpbGw9IiMwMTAxMDEiIGZpbGwtb3BhY2l0eT0iLjMiIHRyYW5zZm9ybT0ic2NhbGUoLjEpIiB0ZXh0TGVuZ3RoPSI0OTAiPndpdGggUlNTPC90ZXh0Pjx0ZXh0IHg9IjEwODUiIHk9IjE0MCIgdHJhbnNmb3JtPSJzY2FsZSguMSkiIGZpbGw9IiNmZmYiIHRleHRMZW5ndGg9IjQ5MCI+d2l0aCBSU1M8L3RleHQ+PC9nPjwvc3ZnPg=="></a>
<a href="https://github.com/cljoly/gohugo-asciinema/tags/">
<img alt="Asciinema Player Version" loading="lazy" src="https://img.shields.io/github/v/tag/cljoly/gohugo-asciinema?label=asciinema%20player%20version&logo=asciinema"></a>
<a href="https://cj.rs/riss">

  <img alt="Powered by RISS (cj.rs/riss)" loading="lazy" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQiIGhlaWdodD0iMjAiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0icG93ZXJlZCBieTogcmlzcyI+PHRpdGxlPnBvd2VyZWQgYnk6IHJpc3M8L3RpdGxlPjxsaW5lYXJHcmFkaWVudCBpZD0icyIgeDI9IjAiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNiYmIiIHN0b3Atb3BhY2l0eT0iLjEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iLjEiLz48L2xpbmVhckdyYWRpZW50PjxjbGlwUGF0aCBpZD0iciI+PHJlY3Qgd2lkdGg9IjEwNCIgaGVpZ2h0PSIyMCIgcng9IjMiIGZpbGw9IiNmZmYiLz48L2NsaXBQYXRoPjxnIGNsaXAtcGF0aD0idXJsKCNyKSI+PHJlY3Qgd2lkdGg9Ijc1IiBoZWlnaHQ9IjIwIiBmaWxsPSIjNTU1Ii8+PHJlY3QgeD0iNzUiIHdpZHRoPSIyOSIgaGVpZ2h0PSIyMCIgZmlsbD0iIzlmOWY5ZiIvPjxyZWN0IHdpZHRoPSIxMDQiIGhlaWdodD0iMjAiIGZpbGw9InVybCgjcykiLz48L2c+PGcgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IlZlcmRhbmEsR2VuZXZhLERlamFWdSBTYW5zLHNhbnMtc2VyaWYiIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIGZvbnQtc2l6ZT0iMTEwIj48dGV4dCBhcmlhLWhpZGRlbj0idHJ1ZSIgeD0iMzg1IiB5PSIxNTAiIGZpbGw9IiMwMTAxMDEiIGZpbGwtb3BhY2l0eT0iLjMiIHRyYW5zZm9ybT0ic2NhbGUoLjEpIiB0ZXh0TGVuZ3RoPSI2NTAiPnBvd2VyZWQgYnk8L3RleHQ+PHRleHQgeD0iMzg1IiB5PSIxNDAiIHRyYW5zZm9ybT0ic2NhbGUoLjEpIiBmaWxsPSIjZmZmIiB0ZXh0TGVuZ3RoPSI2NTAiPnBvd2VyZWQgYnk8L3RleHQ+PHRleHQgYXJpYS1oaWRkZW49InRydWUiIHg9Ijg4NSIgeT0iMTUwIiBmaWxsPSIjMDEwMTAxIiBmaWxsLW9wYWNpdHk9Ii4zIiB0cmFuc2Zvcm09InNjYWxlKC4xKSIgdGV4dExlbmd0aD0iMTkwIj5yaXNzPC90ZXh0Pjx0ZXh0IHg9Ijg4NSIgeT0iMTQwIiB0cmFuc2Zvcm09InNjYWxlKC4xKSIgZmlsbD0iI2ZmZiIgdGV4dExlbmd0aD0iMTkwIj5yaXNzPC90ZXh0PjwvZz48L3N2Zz4="></a></p>

</div>


<p>Want to insert a short demo of your tool or of a command execution on your website?
You could insert a video, but then visitors can’t copy text, and it’ll make your page quite heavy.
The <a href="https://github.com/asciinema/asciinema-player">Asciinema player</a> solves all of that, by replaying a terminal session stored in a text file.</p>
<p>This Hugo module makes it very easy to use the Asciinema Player on your Hugo-powered static website.</p>
<h2 id="install">Install</h2>
<h3 id="hugo-module">Hugo Module</h3>
<ol>
<li>
<p>Make sure the <a href="https://go.dev">go compiler</a> is installed as well as <a href="https://gohugo.io">Hugo</a>.
To check that, run the commands below. If the programs are installed, they should not return an error. Otherwise, see the <a href="https://go.dev/doc/install">go install instructions</a> and <a href="https://gohugo.io/getting-started/quick-start/">Hugo install instructions</a>:</p>
<pre tabindex="0"><code>go version
</code></pre><pre tabindex="0"><code>hugo version
</code></pre></li>
<li>
<p>If you haven’t already, you need to initialize Hugo modules:</p>
<pre tabindex="0"><code>hugo mod init example.com/my-awesome-website
</code></pre><p>This needs to be done once per Hugo site.
Read the <a href="https://gohugo.io/hugo-modules/use-modules/">Hugo documentation</a> for details and background.</p>
</li>
</ol>
<h3 id="install-this-module">Install This Module</h3>
<p>Once Hugo modules are set up (see the <a href="#hugo-module">previous section</a>), you can install the asciinema module itself.</p>
<ol>
<li>
<p>Run:</p>
<pre tabindex="0"><code>hugo mod get -u -v cj.rs/gohugo-asciinema
</code></pre></li>
<li>
<p>Edit your Hugo config to add the module reference.</p>
<p>For instance, if you use a <code>config.toml</code> config file, add:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">module</span>]
</span></span><span style="display:flex;"><span>[[<span style="color:#e06c75">module</span>.<span style="color:#e06c75">imports</span>]]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">path</span> = <span style="color:#98c379">&#34;cj.rs/gohugo-asciinema&#34;</span>
</span></span></code></pre></div><p>For <code>config.yml</code>, add:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#e06c75">module</span>:
</span></span><span style="display:flex;"><span><span style="color:#e06c75">imports</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#e06c75">path</span>: cj.rs/gohugo-asciinema
</span></span></code></pre></div></li>
</ol>
<h2 id="use">Use</h2>
<p>Use this <a href="https://gohugo.io/content-management/shortcodes/">shortcode</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>{{&lt; asciicast src=&#34;https://joly.pw/blog/cargo-info-in-neovim/demo.json&#34; poster=&#34;npt:0:04&#34; autoPlay=true loop=true &gt;}}
</span></span></code></pre></div><p>It&rsquo;s rendered like this:
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/cargo-info-in-neovim/demo.json", document.getElementById('demo3'), {
"autoPlay":  true ,"loop":  true ,"poster": "npt:0:04",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://joly.pw/blog/cargo-info-in-neovim/demo.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>
</p>
<h3 id="defaults">Defaults</h3>
<p>You can set default values for the player in your <code>config.toml</code> or <code>params.toml</code> etc.. For example:</p>
<h4 id="configtoml"><code>config.toml</code></h4>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">params</span>.<span style="color:#e06c75">asciinema</span>.<span style="color:#e06c75">defaults</span>]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">theme</span> = <span style="color:#98c379">&#34;solarized-dark&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">loop</span> = <span style="color:#e5c07b">true</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">autoPlay</span> = <span style="color:#e5c07b">true</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">speed</span> = <span style="color:#d19a66">5.0</span>
</span></span></code></pre></div><h4 id="paramstoml"><code>params.toml</code></h4>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">asciinema</span>.<span style="color:#e06c75">defaults</span>]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">theme</span> = <span style="color:#98c379">&#34;solarized-dark&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">loop</span> = <span style="color:#e5c07b">true</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">autoPlay</span> = <span style="color:#e5c07b">true</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">speed</span> = <span style="color:#d19a66">5.0</span>
</span></span></code></pre></div><h3 id="notes">Notes</h3>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p><code>src</code> is the only required argument. All the other arguments are parameters in the object passed as the third argument of <a href="https://docs.asciinema.org/manual/player/quick-start/#basic-usage"><code>AsciinemaPlayer.create</code></a>.</p>
  </div>







  
  
  
  

  <div class="alert alert-warning">
    <p class="alert-heading">
      ⚠️
      
        Warning
      
    </p>
    <p><code>src</code> is known to sometimes cause problem with relative URLs. Your best bet is to use absolute URLs or at least from the root of the site, as in the above example.</p>
  </div>







  
  
  
  

  <div class="alert alert-important">
    <p class="alert-heading">
      🟣
      
        Important
      
    </p>
    <p>Numbers and booleans should be passed <strong>without</strong> being enclosed in <code>&quot;</code>, i.e. <code>autoPlay=true</code>, <em>not</em> <code>autoPlay=&quot;true&quot;</code>.</p>
  </div>



<h3 id="advanced-features">Advanced Features</h3>
<h4 id="preloading">Preloading</h4>
<p>If you want to instruct the browser to <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload">preload</a> the CSS and JS of the Asciinema Player, only when the page contains an asciicast, you can add the following line in the <code>&lt;head&gt;</code> tag of your template:</p>
<pre tabindex="0"><code>{{- partial &#34;asciinema_css_js_smart_preload&#34; . -}}
</code></pre><p>Themes often offer ways to do this easily.
For instance with <a href="https://github.com/adityatelange/hugo-PaperMod/">Hugo PaperMod</a>, it’s in <a href="https://github.com/adityatelange/hugo-PaperMod/blob/b5f7118d826e663bfe76f56eba2baa028a384325/layouts/partials/extend_head.html">this file</a>.</p>
<p>This partial will also cause <a href="https://developers.cloudflare.com/pages/configuration/early-hints/">some services</a> to send <a href="https://developers.cloudflare.com/pages/platform/early-hints/">Early Hints</a>.
That can further improve the page load time.</p>
<h2 id="features">Features</h2>
<ul>
<li>Displays a message when JavaScript is disabled in the user browser</li>
<li>Fingerprinted assets, to improve caching and ultimately your site performance</li>
<li>Optional <a href="#preloading">preloading</a> of JS and CSS assets</li>
<li>Easy update with <code>hugo mod get -u cj.rs/gohugo-asciinema</code></li>
</ul>
<h2 id="how-are-the-sources-of-the-player-generated">How Are the Sources of the Player Generated?</h2>
<p>The Asciinema Player version is fetched from the official repository in the corresponding version.
Then, if prebuilt JS/CSS files are provided they are used, so that you can verify that this module is actually distributing the original code.
If not, these files are generated following the instructions from Asciinema Player Readme.</p>
<h2 id="contribute">Contribute</h2>
<p>Contributions (documentation or code improvements in particular) are welcome, see <a href="https://cj.rs/docs/contribute/">contributing</a>!</p>
<p>To test your code changes locally, you can change your configuration so that your local version is loaded.
Here is an example in TOML:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">module</span>]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">replacements</span> = <span style="color:#98c379">&#34;cj.rs/gohugo-asciinema -&gt; /some/path/gohugo-asciinema&#34;</span>
</span></span><span style="display:flex;"><span>[[<span style="color:#e06c75">module</span>.<span style="color:#e06c75">imports</span>]]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">path</span> = <span style="color:#98c379">&#34;cj.rs/gohugo-asciinema&#34;</span>
</span></span></code></pre></div><p>Please consider sending a PR with your patches, it’s always appreciated and will save you the trouble of maintaining the changes on your own!</p>
<h3 id="license">License</h3>
<p>The code of this module (everything but the folder <code>assets</code>) is under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>, like the asciinema player itself.</p>
<p>The <code>assets</code> folder contains the asciinema player. This code is Copyright Marcin Kulik, licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>. See the <a href="https://github.com/asciinema/asciinema-player">upstream repository</a> for the full sources.</p>
]]></content:encoded></item><item><title>SQLite Pragma Cheatsheet for Performance and Consistency</title><link>https://joly.pw/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/</link><pubDate>Fri, 07 May 2021 17:33:23 +0000</pubDate><guid>https://joly.pw/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/</guid><description>Last updated: March 18, 2024.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <h3 id="when-opening-the-db">When Opening the DB</h3>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">journal_mode</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">wal</span>; <span style="color:#7f848e">-- different implementation of the atomicity properties
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">synchronous</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">normal</span>; <span style="color:#7f848e">-- synchronise less often to the filesystem
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">foreign_keys</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">on</span>; <span style="color:#7f848e">-- check foreign key reference, slightly worst performance
</span></span></span></code></pre></div><p>And check <code>user_version</code> to apply any migrations, for instance with this <a href="https://joly.pw/rusqlite_migration/">Rust library</a>.</p>
<h3 id="when-closing-the-db">When Closing the DB</h3>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">analysis_limit</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">400</span>; <span style="color:#7f848e">-- make sure pragma optimize does not take too long
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">optimize</span>; <span style="color:#7f848e">-- gather statistics to improve query optimization
</span></span></span></code></pre></div>
  </div>



<h2 id="introduction">Introduction</h2>
<p>SQL pragma are statements (like <code>SELECT …</code> or <code>CREATE TABLE …</code>) that change the database behaviors or call a special functions. This post is a short list of <a href="https://sqlite.org">SQLite</a> pragma I use in my projects built on SQLite, to get better performance and more consistency.</p>
<h2 id="performance">Performance</h2>
<h3 id="file-system-interactions">File system Interactions</h3>
<p>The following pragma statements set the way SQLite deals with the file system.</p>
<h4 id="journal_mode-wal">journal_mode wal</h4>
<p>By default, when applying changes, SQLite uses a rollback journal, which is basically a copy of the data before the changes. Changing the <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a> to “Write-Ahead Log” is known to bring significantly better performance in most cases. It also allows concurrent readers with one writer. To activate it, run:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">journal_mode</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">wal</span>;
</span></span></code></pre></div><p>Some less common use cases are incompatible with this journal mode, for instance having a database on a network file system. The full list of drawbacks is <a href="https://sqlite.org/wal.html">listed in the documentation</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<h4 id="synchronous-normal">synchronous normal</h4>
<p>To ensure integrity of the database, one of the mechanism SQLite uses is the file system synchronization operations. However, these synchronizations are quite costly. With WAL journal mode enabled, your database will still be consistent while synchronizing less often:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">synchronous</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">normal</span>;
</span></span></code></pre></div><p>Compared to the default <a href="https://sqlite.org/pragma.html#pragma_synchronous"><code>synchronous = full</code></a>, committed transactions could be rolled back if there is a power loss (although not if the application crashes).</p>
<h3 id="optimize">Optimize</h3>
<p>To execute statement as efficiently as possible, SQLite has a <a href="https://sqlite.org/queryplanner.html">query planner</a>, which tries to read the tables to provide good performance (for instance by evaluating the <code>WHERE</code> clauses that select the fewest rows first).</p>
<p>This query planner sometimes needs to know whether a column has many values or only a few, repeated values (like a boolean column would). To know this, it can’t read the whole table, as that may prove as costly as running the part of the query that is being optimized, so it uses some statistics collected in internal tables.</p>
<p>Using <a href="https://sqlite.org/pragma.html#pragma_optimize">optimize</a> right before closing the database connexion collects the statistics for some table columns. These columns are chosen mainly based on the queries executed during the connexion: if a query had benefited from more accurate statistics, the corresponding columns are analyzed.</p>
<p>For instance:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">analysis_limit</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">400</span>;
</span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">optimize</span>;
</span></span></code></pre></div><p>In the above example, pragma <a href="https://sqlite.org/pragma.html#pragma_analysis_limit">analysis_limit</a> ensures that <code>optimize</code> won’t run for too long, by limiting the number of rows read.</p>
<h3 id="allow-using-more-memory">Allow Using More Memory</h3>
<p>These two pragma <em>could</em> result in better performance, depending on your hardware and software configuration.</p>
<ul>
<li>Keep temporary storage in memory: <a href="https://sqlite.org/pragma.html#pragma_temp_store"><code>PRAGMA temp_store = 2</code></a>. However, setting this pragma does not guarantee that the temporary storage will be held in memory. Please refer to the documentation for other parameters that may change the final outcome.</li>
<li>Keep more of the database pages in memory: for instance, use 32 MiB of memory for this purpose with <a href="https://sqlite.org/pragma.html#pragma_cache_size"><code>PRAGMA cache_size = -32000</code></a>. Note that the OS cache is already keeping parts of the database file in RAM, so this might end up wasting memory.</li>
</ul>
<h2 id="consistency">Consistency</h2>
<h3 id="user_version">user_version</h3>
<p>The database&rsquo;s schema can evolve over time. Your application could start with a <code>car</code> table to represent a car but later on, you realize you want to represent bicycles, so you add a <code>bicycle</code> table.</p>
<p>To keep track of the different versions of the schema, some libraries maintain an internal table with a single row with a version number. It then performs migrations as needed. In our example, version 1 would have only the <code>car</code> table and version 2, also the <code>bicycle</code> table. The migration from version 1 to version 2 add the <code>bicycle</code> table.</p>
<p>SQLite offers the <a href="https://sqlite.org/pragma.html#pragma_user_version">user_version</a> pragma, to keep track of these versions. It is an integer <a href="https://sqlite.org/fileformat2.html#user_version_number">at a fixed offset</a> in the database file. It is simpler and more efficient than maintaining a table with versions, in particular because the table has to be found in the database file while the integer is available right away.</p>
<p>The drawback is that this is not portable between database engines. If you only use SQLite though, it is almost always the best option. To use it, you can write some code to check the value of the <code>user_version</code> pragma value right after opening the database. If it is lower than expected, then atomically 1) run the necessary migrations and 2) increment the <code>user_version</code> pragma value. I wrote a library to ease this task in <a href="https://joly.pw/rusqlite_migration/">rust</a> and here is an <a href="https://levlaz.org/sqlite-db-migrations-with-pragma-user_version/">example in python</a>.</p>
<h3 id="foreign_keys-on">foreign_keys on</h3>
<p>This may come as a surprise for folks with experience with other database system, but SQLite does not enforce foreign key constraints by default. Consequently, a foreign key can point to a row that does not exist.</p>
<p>This can be fixed with pragma <a href="https://sqlite.org/pragma.html#pragma_foreign_keys">foreign_keys</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">foreign_keys</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">ON</span>;
</span></span></code></pre></div><p>This comes at a performance cost however, because more checks are performed when inserting values with foreign keys. The cost is usually negligible but your mileage may vary.</p>
<p><em>Note</em>: pragma <a href="https://sqlite.org/pragma.html#pragma_foreign_key_check">foreign_key_check</a> can be used to check a particular table for violated foreign key constraint. This can be useful before enabling <code>foreign_keys</code> on a database with existing data.</p>
<h3 id="strict">STRICT</h3>




  
  
  
  

  <div class="alert alert-important">
    <p class="alert-heading">
      🟣
      
        Important
      
    </p>
    <p>Requires SQLite version <a href="https://www.sqlite.org/releaselog/3_37_1.html">3.37.0 (2021-11-27)</a></p>
  </div>



<p>Another peculiarity of SQLite is dynamic typing through <a href="https://www.sqlite.org/datatype3.html">type affinity</a>. When you define a column of type <code>INTEGER</code> in most other database engines, a value of type <code>TEXT</code> inserted in that column will be converted or return an error. But with SQLite, type <a href="https://www.sqlite.org/datatype3.html#type_affinity">affinities</a> mean that if the value is not of the expected type, it will be converted if possible or stored with a different type if necessary. That’s handy when prototyping, but you may want a stricter behavior for consistency with other major SQL databases or when working with strictly typed languages.</p>
<p>To this end, SQLite supports the <a href="https://www.sqlite.org/stricttables.html">STRICT</a> keyword at table creation. For instance, if a table <code>t</code> is created like so:</p>
<pre tabindex="0"><code>CREATE TABLE t(a INTEGER) STRICT;
</code></pre><p>then</p>
<pre tabindex="0"><code>INSERT INTO t VALUES(1);
INSERT INTO t VALUES(&#39;1&#39;);
</code></pre><p>are successful but</p>
<pre tabindex="0"><code>INSERT INTO t VALUES(&#39;f&#39;);
</code></pre><p>is not and returns <code>Runtime error: cannot store TEXT value in INTEGER column t.a (19)</code>.</p>
<p><strong>Note</strong>: Without the <code>STRICT</code> keyword at table creation, this last <code>INSERT INTO …</code> statement would have been successful and would have just returned a string.</p>
<h2 id="go-deeper">Go deeper</h2>
<p>The full list of supported pragma with detailed descriptions is available in the <a href="https://sqlite.org/pragma.html">documentation</a>. Some <a href="https://kerkour.com/sqlite-for-servers">more options</a> can be tweaked for the specific scenario of running a SQLite for a server.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A significant improvement to WAL mode is <a href="https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md">being developped</a>, where the WAL file won’t grow to unbound sizes.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>