<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Tenderlove Making</title>
  <link rel="self" href="https://tenderlovemaking.com/atom.xml"/>
  <link href="https://tenderlovemaking.com/"/>
  <updated>2026-01-19T15:18:23-08:00</updated>
  <author>
    <name>Aaron Patterson</name>
    <email>tenderlove@ruby-lang.org</email>
  </author>
  <rights> © Aaron Patterson - tenderlove@ruby-lang.org</rights>
  <id>https://tenderlovemaking.com/</id>
  
    <entry>
    <title>Bainbridge Island Mochi Tsuki</title>
    <link rel="alternate" href="https://tenderlovemaking.com/shots/2026-01-19-bainbridge-island-mochi-tsuki/"/>
    <id>https://tenderlovemaking.com/shots/2026-01-19-bainbridge-island-mochi-tsuki/</id>
    <published>2026-01-19T15:18:23-08:00</published>
    <updated>2026-01-19T15:18:23-08:00</updated>
    <content type="html">&lt;img src="/shots/2026-01-19-bainbridge-island-mochi-tsuki/images/P1100094.jpeg" alt="Seattle waterfront skyline featuring the Space Needle and Great Wheel ferris wheel, viewed through a ferry window across Elliott Bay." /&gt;&lt;img src="/shots/2026-01-19-bainbridge-island-mochi-tsuki/images/P1100122.jpeg" alt="A group of people gathered outdoors watching a young man wearing a headband demonstrate mochi pounding with large wooden mallets (kine) at what appears to be a traditional mochitsuki event." /&gt;&lt;img src="/shots/2026-01-19-bainbridge-island-mochi-tsuki/images/P1100141.jpeg" alt="A man wearing a headband pours water into a traditional stone mortar (usu) during a mochi-pounding demonstration as a crowd watches outdoors." /&gt;&lt;img src="/shots/2026-01-19-bainbridge-island-mochi-tsuki/images/P1100366.jpeg" alt="A taiko drummer in a red and black vest performs with large wooden bachi sticks on a traditional Japanese drum during a group performance." /&gt;&lt;img src="/shots/2026-01-19-bainbridge-island-mochi-tsuki/images/P1100750.jpeg" alt="A taiko drummer wearing traditional black and bronze performance attire plays a large wooden drum with wooden bachi sticks during a performance." /&gt;&lt;p&gt;Last weekend we took the ferry to Bainbridge Island to see a &lt;a href=&#34;https://bijac.org/event/mochi-tsuki-2026/&#34;&gt;Mochi Tsuki event&lt;/a&gt;.
I read about the &lt;a href=&#34;https://bijac.org&#34;&gt;history of Japanese immigrants on Bainbridge island&lt;/a&gt;.
I saw people making mochi, a &lt;a href=&#34;https://seattlekokontaiko.org&#34;&gt;taiko drummer group&lt;/a&gt;, and traditional dances.
It was a lot of fun, and I learned a lot.  Definitely recommend this event!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Pixoo64 Ruby Client</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2026/01/01/pixoo64-ruby-client/"/>
    <id>https://tenderlovemaking.com/2026/01/01/pixoo64-ruby-client/</id>
    <published>2026-01-01T19:17:26-08:00</published>
    <updated>2026-01-01T19:17:26-08:00</updated>
    <content type="html">&lt;p&gt;I bought a &lt;a href=&#34;https://divoom.com/products/pixoo-64&#34;&gt;Pixoo64 LED Display&lt;/a&gt; to play around with, and I love it!
It connects to WiFi and has an on-board HTTP API so you can program it.
I made &lt;a href=&#34;https://github.com/tenderlove/pixoo-rb&#34;&gt;a Ruby client for it&lt;/a&gt; that even includes code to convert PNG files to the binary format the sign wants.&lt;/p&gt;
&lt;p&gt;One cool thing is that the display can be configured to fetch data from a remote server, so I configured mine to fetch PM2.5 and CO2 data for my office.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what it&amp;rsquo;s looking like so far:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/2026/01/01/pixoo64-ruby-client/images/P1010032.jpeg&#34; alt=&#34;LED sign that has a cat and PM2.5 data on it&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Yes, this is how I discovered I need to open a window 😂&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Cat Pics</title>
    <link rel="alternate" href="https://tenderlovemaking.com/shots/2025-12-30-cat-pics/"/>
    <id>https://tenderlovemaking.com/shots/2025-12-30-cat-pics/</id>
    <published>2025-12-30T18:00:58-08:00</published>
    <updated>2025-12-30T18:00:58-08:00</updated>
    <content type="html">&lt;img src="/shots/2025-12-30-cat-pics/images/PC300360.jpeg" alt="An orange cat peeks out from inside a light blue felt cat bed with a circular entrance opening." /&gt;&lt;img src="/shots/2025-12-30-cat-pics/images/PC300384.jpeg" alt="An orange cat with bright green eyes peers out from inside a blue felted cat tunnel." /&gt;&lt;img src="/shots/2025-12-30-cat-pics/images/PC300388.jpeg" alt="An orange cat with wide green eyes peeks out from inside a blue felted cat tunnel." /&gt;&lt;p&gt;Did a few cat pics tonight!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Can Bundler Be as Fast as uv?</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2025/12/29/can-bundler-be-as-fast-as-uv/"/>
    <id>https://tenderlovemaking.com/2025/12/29/can-bundler-be-as-fast-as-uv/</id>
    <published>2025-12-29T12:26:00-08:00</published>
    <updated>2025-12-29T12:26:00-08:00</updated>
    <content type="html">&lt;p&gt;At RailsWorld earlier this year, I got nerd sniped by someone.
They asked &amp;ldquo;why can&amp;rsquo;t Bundler be as fast as uv?&amp;rdquo;
Immediately my inner voice said &amp;ldquo;YA, WHY CAN&amp;rsquo;T IT BE AS FAST AS UV????&amp;rdquo;&lt;/p&gt;
&lt;p&gt;My inner voice likes to shout at me, especially when someone asks a question so obvious I should have thought of it myself.
Since then I&amp;rsquo;ve been thinking about and investigating this problem, going so far as to &lt;a href=&#34;https://www.xoruby.com/event/portland/&#34;&gt;give a presentation at XO Ruby Portland about Bundler performance&lt;/a&gt;.
I firmly believe the answer is &amp;ldquo;Bundler &lt;em&gt;can&lt;/em&gt; be as fast as uv&amp;rdquo; (where &amp;ldquo;as fast&amp;rdquo; has a margin of error lol).&lt;/p&gt;
&lt;p&gt;Fortunately, Andrew Nesbitt recently wrote a post called &lt;a href=&#34;https://nesbitt.io/2025/12/26/how-uv-got-so-fast.html&#34;&gt;&amp;ldquo;How uv got so fast&amp;rdquo;&lt;/a&gt;, and I thought I would take this opportunity to review some of the highlights of the post and how techniques applied in uv can (or can&amp;rsquo;t) be applied to Bundler / RubyGems.
I&amp;rsquo;d also like to discuss some of the existing bottlenecks in Bundler and what we can do to fix them.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t read Andrew&amp;rsquo;s post, I &lt;a href=&#34;https://nesbitt.io/2025/12/26/how-uv-got-so-fast.html&#34;&gt;highly recommend giving it a read&lt;/a&gt;.
I&amp;rsquo;m going to quote some parts of the post and try to reframe them with RubyGems / Bundler in mind.&lt;/p&gt;
&lt;h2 id=&#34;rewrite-in-rust&#34;&gt;Rewrite in Rust?&lt;/h2&gt;
&lt;p&gt;Andrew opens the post talking about rewriting in Rust:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;uv installs packages faster than pip by an order of magnitude. The usual explanation is “it’s written in Rust.” That’s true, but it doesn’t explain much. Plenty of tools are written in Rust without being notably fast. The interesting question is what design decisions made the difference.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is such a good quote.
I&amp;rsquo;m going to address &amp;ldquo;rewrite in Rust&amp;rdquo; a bit later in the post.
But suffice to say, I think if we eliminate bottlenecks in Bundler such that the only viable option for performance improvements is to &amp;ldquo;rewrite in Rust&amp;rdquo;, then I&amp;rsquo;ll call it a success.
I think rewrites give developers the freedom to &amp;ldquo;think outside the box&amp;rdquo;, and try techniques they might not have tried.
In the case of &lt;code&gt;uv&lt;/code&gt;, I think it gave the developers a good way to say &amp;ldquo;if we don&amp;rsquo;t have to worry about backwards compatibility, what could we achieve?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I suspect it would be possible to write a uv in Python (PyUv?) that approaches the speeds of uv, and in fact much of the blog post goes on to talk about performance improvements that &lt;em&gt;aren&amp;rsquo;t&lt;/em&gt; related to Rust.&lt;/p&gt;
&lt;h2 id=&#34;installing-code-without-evaling&#34;&gt;Installing code without eval&amp;rsquo;ing&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;pip’s slowness isn’t a failure of implementation. For years, Python packaging required executing code to find out what a package needed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I didn&amp;rsquo;t know this about Python packages, and it doesn&amp;rsquo;t really apply to Ruby Gems so I&amp;rsquo;m mostly going to skip this section.&lt;/p&gt;
&lt;p&gt;Ruby Gems are tar files, and one of the files in the tar file is a YAML representation of the GemSpec.
This YAML file declares all dependencies for the Gem, so RubyGems can know, without evaling anything, what dependencies it needs to install before it can install any particular Gem.
Additionally, RubyGems.org provides an API for asking about dependency information, which is actually the normal way of getting dependency info (again, no &lt;code&gt;eval&lt;/code&gt; required).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s only one other thing from this section I&amp;rsquo;d like to quote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PEP 658 (2022) put package metadata directly in the Simple Repository API, so resolvers could fetch dependency information without downloading wheels at all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fortunately RubyGems.org already provides the same information about gems.&lt;/p&gt;
&lt;p&gt;Reading through the number of PEPs required as well as the amount of time it took to get the standards in place was very eye opening for me.
I can&amp;rsquo;t help but applaud folks in the Python community for doing this.
It seems like a mountain of work, and they should really be proud of themselves.&lt;/p&gt;
&lt;h2 id=&#34;what-uv-drops&#34;&gt;What uv drops&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m mostly going to skip this section except for one point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ignoring requires-python upper bounds. When a package says it requires python&amp;lt;4.0, uv ignores the upper bound and only checks the lower. This reduces resolver backtracking dramatically since upper bounds are almost always wrong. Packages declare python&amp;lt;4.0 because they haven’t tested on Python 4, not because they’ll actually break. The constraint is defensive, not predictive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think this is very very interesting.
I don&amp;rsquo;t know how much time Bundler spends on doing &amp;ldquo;required Ruby version&amp;rdquo; bounds checking, but it feels like if uv can do it, so can we.&lt;/p&gt;
&lt;h2 id=&#34;optimizations-that-dont-need-rust&#34;&gt;Optimizations that don’t need Rust&lt;/h2&gt;
&lt;p&gt;I really love that Andrew pointed out optimizations that could be made that don&amp;rsquo;t involve Rust.
There are three points in this section that I want to pull out:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Parallel downloads. pip downloads packages one at a time. uv downloads many at once. Any language can do this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is absolutely true, and is a place where Bundler could improve.
Bundler currently has a problem when it comes to parallel downloads, and needs a small architectural change as a fix.&lt;/p&gt;
&lt;p&gt;The first problem is that Bundler tightly couples &lt;em&gt;installing&lt;/em&gt; a gem with &lt;em&gt;downloading&lt;/em&gt; the gem.
You can read &lt;a href=&#34;https://github.com/ruby/rubygems/blob/c00ca53960d21d0680df4ea7dde8ddf1bedaa774/bundler/lib/bundler/source/rubygems.rb#L165&#34;&gt;the installation code here&lt;/a&gt;, but I&amp;rsquo;ll summarize the method in question below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;install&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  path &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_gem_if_not_cached
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;Bundler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RubyGemsGemInstaller&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;install path, dest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem with this method is that it inextricably links &lt;em&gt;downloading&lt;/em&gt; the gem with &lt;em&gt;installing&lt;/em&gt; it.
This is a problem because we could be downloading gems while installing other gems, but we&amp;rsquo;re forced to wait because the installation method couples the two operations.
Downloading gems can trivially be done in parallel since the &lt;code&gt;.gem&lt;/code&gt; files are just archives that can be fetched independently.&lt;/p&gt;
&lt;p&gt;The second problem is the queuing system in the installation code.
After gem resolution is complete, and Bundler knows what gems need to be installed, it queues them up for installation.
You can find &lt;a href=&#34;https://github.com/ruby/rubygems/blob/c00ca53960d21d0680df4ea7dde8ddf1bedaa774/bundler/lib/bundler/installer/parallel_installer.rb#L188-L201&#34;&gt;the queueing code here&lt;/a&gt;.
The code takes some effort to understand. Basically it allows gems to be installed in parallel, but only gems that have already had their dependencies installed.&lt;/p&gt;
&lt;p&gt;So for example, if you have a dependency tree like &amp;ldquo;gem &lt;code&gt;a&lt;/code&gt; depends on gem &lt;code&gt;b&lt;/code&gt; which depends on gem &lt;code&gt;c&lt;/code&gt;&amp;rdquo; (&lt;code&gt;a -&amp;gt; b -&amp;gt; c&lt;/code&gt;), then no gems will be installed (or downloaded) in parallel.&lt;/p&gt;
&lt;p&gt;To demonstrate this problem in an easy-to-understand way, I built a &lt;a href=&#34;https://github.com/tenderlove/slow-gemserver&#34;&gt;slow Gem server&lt;/a&gt;.
It generates a dependency tree of &lt;code&gt;a -&amp;gt; b -&amp;gt; c&lt;/code&gt; (&lt;code&gt;a&lt;/code&gt; depends on &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt; depends on &lt;code&gt;c&lt;/code&gt;), then starts a Gem server.
The Gem server takes 3 seconds to return any Gem, so if we point Bundler at this Gem server and then profile Bundler, we can see the impact of the queueing system and download scheme.&lt;/p&gt;
&lt;p&gt;In my test app, I have the following Gemfile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://localhost:9292&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we profile Bundle install with Vernier, we can see the following swim lanes in the marker chart:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/2025/12/29/can-bundler-be-as-fast-as-uv/images/serial.png&#34; alt=&#34;gem install swim lanes (serial)&#34;&gt;
&lt;/p&gt;
&lt;p&gt;The above chart is showing that we get no parallelism during installation.
We spend 3 seconds downloading the &lt;code&gt;c&lt;/code&gt; gem, then we install it.
Then we spend 3 seconds downloading the &lt;code&gt;b&lt;/code&gt; gem, then we install it.
Finally we spend 3 seconds downloading the &lt;code&gt;a&lt;/code&gt; gem, and we install it.&lt;/p&gt;
&lt;p&gt;Timing the &lt;code&gt;bundle install&lt;/code&gt; process shows we take over 9 seconds to install (3 seconds per gem):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; rm -rf x; rm -f Gemfile.lock; time GEM_PATH=(pwd)/x GEM_HOME=(pwd)/x bundle install
Fetching gem metadata from http://localhost:9292/...
Resolving dependencies...
Fetching c 1.0.0
Installing c 1.0.0
Fetching b 1.0.0
Installing b 1.0.0
Fetching a 1.0.0
Installing a 1.0.0
Bundle complete! 1 Gemfile dependency, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

________________________________________________________
Executed in   11.80 secs      fish           external
   usr time  341.62 millis  231.00 micros  341.38 millis
   sys time  223.20 millis  712.00 micros  222.49 millis
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Contrast this with a Gemfile containing &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt;, and &lt;code&gt;f&lt;/code&gt;, which have no dependencies, but still take 3 seconds to download:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://localhost:9292&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;d&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;e&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;/2025/12/29/can-bundler-be-as-fast-as-uv/images/parallel.png&#34; alt=&#34;gem install swim lanes (parallel)&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Timing &lt;code&gt;bundle install&lt;/code&gt; for the above Gemfile shows it takes about 4 seconds:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; rm -rf x; rm -f Gemfile.lock; time GEM_PATH=(pwd)/x GEM_HOME=(pwd)/x bundle install
Fetching gem metadata from http://localhost:9292/.
Resolving dependencies...
Fetching d 1.0.0
Fetching e 1.0.0
Fetching f 1.0.0
Installing e 1.0.0
Installing f 1.0.0
Installing d 1.0.0
Bundle complete! 3 Gemfile dependencies, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

________________________________________________________
Executed in    4.14 secs      fish           external
   usr time  374.04 millis    0.38 millis  373.66 millis
   sys time  368.90 millis    1.09 millis  367.81 millis
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We were able to install the same number of gems in a fraction of the time.
This is because Bundler is able to download &lt;em&gt;siblings&lt;/em&gt; in the dependency tree in parallel, but unable to handle other relationships.&lt;/p&gt;
&lt;p&gt;There is actually a good reason that Bundler insists dependencies are installed before the gems themselves: native extensions.
When installing native extensions, the installation process &lt;em&gt;must&lt;/em&gt; run Ruby code (the &lt;code&gt;extconf.rb&lt;/code&gt; file).
Since the &lt;code&gt;extconf.rb&lt;/code&gt; could require dependencies be installed in order to run, we must install dependencies first.
For example &lt;a href=&#34;https://rubygems.org/gems/nokogiri&#34;&gt;&lt;code&gt;nokogiri&lt;/code&gt; depends on &lt;code&gt;mini_portile2&lt;/code&gt;&lt;/a&gt;, but &lt;code&gt;mini_portile2&lt;/code&gt; is only used during the installation process, so it needs to be installed before &lt;code&gt;nokogiri&lt;/code&gt; can be compiled and installed.&lt;/p&gt;
&lt;p&gt;However, if we were to &lt;em&gt;decouple downloading from installation&lt;/em&gt; it would be possible for us to maintain the &amp;ldquo;dependencies are installed first&amp;rdquo; business requirement but speed up installation.
In the &lt;code&gt;a -&amp;gt; b -&amp;gt; c&lt;/code&gt; case, we &lt;em&gt;could have been&lt;/em&gt; downloading gems &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; at the same time as gem &lt;code&gt;c&lt;/code&gt; (or even while waiting on &lt;code&gt;c&lt;/code&gt; to be installed).&lt;/p&gt;
&lt;p&gt;Additionally, pure Ruby gems don&amp;rsquo;t need to execute any code on installation.
If we knew that we were installing a pure Ruby gem, it would be possible to relax the &amp;ldquo;dependencies are installed first&amp;rdquo; business requirement and get even more performance increases.
The above &lt;code&gt;a -&amp;gt; b -&amp;gt; c&lt;/code&gt; case &lt;em&gt;could&lt;/em&gt; install all three gems in parallel since none of them execute Ruby code during installation.&lt;/p&gt;
&lt;p&gt;I would propose we split installation in to 4 discrete steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the gem&lt;/li&gt;
&lt;li&gt;Unpack the gem&lt;/li&gt;
&lt;li&gt;Compile the gem&lt;/li&gt;
&lt;li&gt;Install the gem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Downloading and unpacking can be done trivially in parallel.
We should unpack the gem to a temporary folder so that if the process crashes or the machine loses power, the user isn&amp;rsquo;t stuck with a half-installed gem.
After we unpack the gem, we can discover whether the gem is a native extension or not.
If it&amp;rsquo;s not a native extension, we &amp;ldquo;install&amp;rdquo; the gem simply by moving the temporary folder to the &amp;ldquo;correct&amp;rdquo; location.
This step could even be a &amp;ldquo;hard link&amp;rdquo; step as discussed in the next point.&lt;/p&gt;
&lt;p&gt;If we discover that the gem is a native extension, then we can &amp;ldquo;pause&amp;rdquo; installation of that gem until its dependencies are installed, then resume (by compiling) at an appropriate time.&lt;/p&gt;
&lt;p&gt;Side note: &lt;a href=&#34;https://github.com/gel-rb/gel&#34;&gt;&lt;code&gt;gel&lt;/code&gt;, a Bundler alternative&lt;/a&gt;, works mostly in this manner today.
Here is a timing of the &lt;code&gt;a -&amp;gt; b -&amp;gt; c&lt;/code&gt; case from above:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; rm -f Gemfile.lock; time gel install
Fetching sources....
Resolving dependencies...
Writing lockfile to /Users/aaron/git/gemserver/app/Gemfile.lock
Installing c (1.0.0) 
Installing a (1.0.0)
Installing b (1.0.0)
Installed 3 gems  

________________________________________________________
Executed in    4.07 secs      fish           external
   usr time  289.22 millis    0.32 millis  288.91 millis
   sys time  347.04 millis    1.36 millis  345.68 millis
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lets move on to the next point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Global cache with hardlinks. pip copies packages into each virtual environment. uv keeps one copy globally and uses hardlinks&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think this is a great idea, but I&amp;rsquo;d actually like to split the idea in two.
First, RubyGems and Bundler should have a combined, global cache, full stop.
I think that global cache should be in &lt;code&gt;$XDG_CACHE_HOME&lt;/code&gt;, and we should store &lt;code&gt;.gem&lt;/code&gt; files there when they are downloaded.&lt;/p&gt;
&lt;p&gt;Currently, both Bundler and RubyGems will use a Ruby version specific cache folder.
In other words, if you do &lt;code&gt;gem install rails&lt;/code&gt; on two different versions of Ruby, you get two copies of Rails and all its dependencies.&lt;/p&gt;
&lt;p&gt;Interestingly, &lt;a href=&#34;https://github.com/ruby/rubygems/issues/7249&#34;&gt;there is an open ticket to implement this&lt;/a&gt;, it just needs to be done.&lt;/p&gt;
&lt;p&gt;The second point is hardlinking on installation.
The idea here is that rather than unpacking the gem multiple times, once per Ruby version, we simply unpack once and then hard link per Ruby version.
I like this idea, but I think it should be implemented after some technical debt is paid: namely implementing a global cache and unifying Bundler / RubyGems code paths.&lt;/p&gt;
&lt;p&gt;On to the next point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PubGrub resolver&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Actually Bundler already uses a Ruby implementation of the PubGrub resolver.
You can see it &lt;a href=&#34;https://github.com/ruby/rubygems/tree/569c5c7450137839ff7c536f160b06a43ea3dfe4/bundler/lib/bundler/vendor/pub_grub&#34;&gt;here&lt;/a&gt;.
Unfortunately, RubyGems &lt;a href=&#34;https://github.com/ruby/rubygems/tree/569c5c7450137839ff7c536f160b06a43ea3dfe4/lib/rubygems/vendor/molinillo&#34;&gt;still uses the molinillo resolver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In other words you use a different resolver depending on whether you do &lt;code&gt;gem install&lt;/code&gt; or &lt;code&gt;bundle install&lt;/code&gt;.
I don&amp;rsquo;t really think this is a big deal since the vast majority of users will be doing &lt;code&gt;bundle install&lt;/code&gt; most of time.
However, I do think this discrepancy is some technical debt that should be addressed, and I think this should be addressed via unification of RubyGems and Bundler codebases (today they both live in the same repository, but the code isn&amp;rsquo;t necessarily combined).&lt;/p&gt;
&lt;p&gt;Lets move on to the next section of Andrew&amp;rsquo;s post:&lt;/p&gt;
&lt;h2 id=&#34;where-rust-actually-matters&#34;&gt;Where Rust actually matters&lt;/h2&gt;
&lt;p&gt;Andrew first mentions &amp;ldquo;Zero-copy deserialization&amp;rdquo;.
This is of course an important technique, but I&amp;rsquo;m not 100% sure where we would utilize it in RubyGems / Bundler.
I think that today we parse the YAML spec on installation, and that could be a target.
But I also think we could install most gems without looking at the YAML gemspec at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thread-level parallelism. Python’s GIL forces parallel work into separate processes, with IPC overhead and data copying.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is an interesting point.
I&amp;rsquo;m not sure what work pip needed to do in separate processes.
Installing a pure Ruby, Ruby Gem is mostly an IO bound task, with some ZLIB mixed in.
Both of these things (IO and ZLIB processing) release Ruby&amp;rsquo;s GVL, so it&amp;rsquo;s possible for us to do things truly in parallel.
I imagine this is similar for Python / pip, but I really have no idea.&lt;/p&gt;
&lt;p&gt;Given the stated challenges with Python&amp;rsquo;s GIL, you might wonder whether Ruby&amp;rsquo;s GVL presents similar parallelism problems for Bundler.
I don&amp;rsquo;t think so, and in fact I think Ruby&amp;rsquo;s GVL gets kind of a bad rap.
It prevents us from running CPU bound Ruby code in parallel.
Ractors address this, and Bundler could possibly leverage them in the future, but since installing Gems is mostly an IO bound task I&amp;rsquo;m not sure what the advantage would be (possibly the version solver, but I&amp;rsquo;m not sure what can be parallelized in there).
The GVL does allow us to run IO bound work in parallel with CPU bound Ruby code.
CPU bound native extensions are allowed to &lt;em&gt;release the GVL&lt;/em&gt;, allowing Ruby code to run in parallel with the native extension&amp;rsquo;s CPU bound code.&lt;/p&gt;
&lt;p&gt;In other words, Ruby&amp;rsquo;s GVL allows us to &lt;em&gt;safely&lt;/em&gt; run work in parallel.
That said, the GVL can work against us because releasing and acquiring the GVL &lt;em&gt;takes time&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you have a system call that is very fast, releasing and acquiring the GVL could end up being a large percentage of that call.
For example, if you do &lt;code&gt;File.binwrite(file, buffer)&lt;/code&gt;, and the buffer is very small, you could encounter a situation where GVL book keeping is the majority of the time.
A bummer is that Ruby Gem packages usually contain lots of very small files, so this problem &lt;em&gt;could&lt;/em&gt; be impacting us.
The good news is that this problem can be solved in Ruby itself, and &lt;a href=&#34;https://github.com/ruby/ruby/pull/15529&#34;&gt;indeed some work is being done on it today&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No interpreter startup. Every time pip spawns a subprocess, it pays Python’s startup cost.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Obviously Ruby has this same problem.
That said, we only start Ruby subprocesses when installing native extensions.
I think native extensions make up the minority of gems installed, and even when installing a native extension, it isn&amp;rsquo;t Ruby startup that is the bottleneck.
Usually the bottleneck is compilation / linking time (as we&amp;rsquo;ll see in the next post).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Compact version representation. uv packs versions into u64 integers where possible, making comparison and hashing fast.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a cool optimization, but I don&amp;rsquo;t think it&amp;rsquo;s actually Rust specific.
Comparing integers is &lt;em&gt;much&lt;/em&gt; faster than comparing version objects.
The idea is that you take a version number, say &lt;code&gt;1.0.0&lt;/code&gt;, and then pack each part of the version in to a single integer.
For example, we could represent &lt;code&gt;1.0.0&lt;/code&gt; as &lt;code&gt;0x0001_0000_0000_0000&lt;/code&gt; and &lt;code&gt;1.1.0&lt;/code&gt; as &lt;code&gt;0x0001_0001_0000_0000&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;It should be possible to use this trick in Ruby and encode versions to integer immediates, which would unlock performance in the resolver.
Rust has an advantage here - compiled native code comparing u64s will always be faster than Ruby, even with immediates.
However, I would bet that with the YJIT or ZJIT in play, this gap could be closed enough that no end user would notice the difference between a Rust or Ruby implementation of Bundler.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&#34;https://github.com/ruby/rubygems/pull/9085&#34;&gt;started refactoring the &lt;code&gt;Gem::Version&lt;/code&gt; object&lt;/a&gt; so that we might start doing this, but we ended up reverting it because of backwards compatibility (I am jealous of &lt;code&gt;uv&lt;/code&gt; in that regard).
I think the right way to do this is to refactor the solver entry point and ensure all version requirements are encoded as integer immediates before entering the solver.
We could keep the &lt;code&gt;Gem::Version&lt;/code&gt; API as &amp;ldquo;user facing&amp;rdquo; and design a more internal API that the solver uses.
I am very interested in reading the version encoding scheme in uv.
My intuition is that minor numbers tend to get larger than major numbers, so would minor numbers have more dedicated bits?
Would it even matter with 64 bits?&lt;/p&gt;
&lt;h2 id=&#34;wrapping-this-up&#34;&gt;Wrapping this up&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m going to quote Andrew&amp;rsquo;s last 2 paragraphs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;uv is fast because of what it doesn’t do, not because of what language it’s written in. The standards work of PEP 518, 517, 621, and 658 made fast package management possible. Dropping eggs, pip.conf, and permissive parsing made it achievable. Rust makes it a bit faster still.&lt;/p&gt;
&lt;p&gt;pip could implement parallel downloads, global caching, and metadata-only resolution tomorrow. It doesn’t, largely because backwards compatibility with fifteen years of edge cases takes precedence. But it means pip will always be slower than a tool that starts fresh with modern assumptions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think these are very good points.
The difference is that in RubyGems and Bundler, we already have the infrastructure in place for writing a &amp;ldquo;fast as uv&amp;rdquo; package manager.
The difficult part is dealing with backwards compatibility, and navigating two legacy codebases.
I think this is the real advantage the uv developers had.
That said, I am very optimistic that we could &amp;ldquo;repair the plane mid-flight&amp;rdquo; so to speak, and have the best of both worlds: backwards compatibility &lt;em&gt;and&lt;/em&gt; speed.&lt;/p&gt;
&lt;p&gt;I mentioned at the top of the post I would address &amp;ldquo;rewrite it in Rust&amp;rdquo;, and I think Andrew&amp;rsquo;s own quote mostly does that for me.
I think we could have 99% of the performance improvements while still maintaining a Ruby codebase.
Of course if we rewrote it in Rust, you could squeeze an extra 1% out, but would it be worthwhile?  I don&amp;rsquo;t think so.&lt;/p&gt;
&lt;p&gt;I have a lot more to say about this topic, and I feel like this post is getting kind of long, so I&amp;rsquo;m going to end it here.
Please look out for part 2, which I&amp;rsquo;m tentatively calling &amp;ldquo;What makes Bundler / RubyGems slow?&amp;rdquo;
This post was very &amp;ldquo;can we make RubyGems / Bundler do what uv does?&amp;rdquo; (the answer is &amp;ldquo;yes&amp;rdquo;).
In part 2 I want to get more hands-on by discussing how to profile Bundler and RubyGems, what specifically makes them slow in the real world, and what we can do about it.&lt;/p&gt;
&lt;p&gt;I want to end this post by saying &amp;ldquo;thank you&amp;rdquo; to Andrew for writing such a great post about &lt;a href=&#34;https://nesbitt.io/2025/12/26/how-uv-got-so-fast.html&#34;&gt;how uv got so fast&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Happy Holidays</title>
    <link rel="alternate" href="https://tenderlovemaking.com/shots/2025-12-22-happy-holidays/"/>
    <id>https://tenderlovemaking.com/shots/2025-12-22-happy-holidays/</id>
    <published>2025-12-22T16:01:02-08:00</published>
    <updated>2025-12-22T16:01:02-08:00</updated>
    <content type="html">&lt;img src="/shots/2025-12-22-happy-holidays/images/holidays.jpeg" alt="A man wearing a Santa hat and festive red sweater holds an orange cat against a teal background with 'Happy Holidays' text above." /&gt;&lt;p&gt;Happy holidays everyone!  Have a great rest of the year!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Seattle Waterfront</title>
    <link rel="alternate" href="https://tenderlovemaking.com/shots/2025-12-16-seattle-waterfront/"/>
    <id>https://tenderlovemaking.com/shots/2025-12-16-seattle-waterfront/</id>
    <published>2025-12-16T16:49:54-08:00</published>
    <updated>2025-12-16T16:49:54-08:00</updated>
    <content type="html">&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130523.jpeg" alt="A seagull perches on a rooftop overlooking Seattle's waterfront with the Great Wheel and Puget Sound in the background." /&gt;&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130570.jpeg" alt="A large Ferris wheel rises behind a waterfront building with 'Seattle Harbor Cruise' signage at dusk." /&gt;&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130589.jpeg" alt="A fire hazard warning sign in the foreground with shipping container cranes visible across the water under a cloudy sky at dusk." /&gt;&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130697.jpeg" alt="People and stairs" /&gt;&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130703.jpeg" alt="People walk up and down a long outdoor staircase at dusk, with some figures motion-blurred while bright street lights glow above in the autumn trees." /&gt;&lt;img src="/shots/2025-12-16-seattle-waterfront/images/PC130751.jpeg" alt="A brightly illuminated Ferris wheel on a waterfront pier glows with pink and purple lights against a dramatic sunset sky as a ferry passes by in the water." /&gt;&lt;p&gt;Went to the Seattle waterfront over the weekend to watch the sunset (at like 4pm lol).
Unfortunately it was pretty cloudy out, but I had a good time.&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Seattle Downtown Library</title>
    <link rel="alternate" href="https://tenderlovemaking.com/shots/2025-11-30-public-library/"/>
    <id>https://tenderlovemaking.com/shots/2025-11-30-public-library/</id>
    <published>2025-11-30T14:05:50-08:00</published>
    <updated>2025-11-30T14:05:50-08:00</updated>
    <content type="html">&lt;img src="/shots/2025-11-30-public-library/images/PB090028.jpeg" alt="Blue chair, red table" /&gt;&lt;img src="/shots/2025-11-30-public-library/images/PB090054.jpeg" alt="Overhead view of a library floor with wooden shelving units displaying books arranged horizontally and vertically." /&gt;&lt;img src="/shots/2025-11-30-public-library/images/PB090068.jpeg" alt="Library lobby with geometric glass and metal architecture features. Purple seating below dramatic angular skylights casting geometric shadows on the floor." /&gt;&lt;img src="/shots/2025-11-30-public-library/images/PB090102.jpeg" alt="Two people stand in a dimly lit corridor with green vertical panels on the left and glowing red light on the right, viewed from behind." /&gt;&lt;img src="/shots/2025-11-30-public-library/images/PB090119.jpeg" alt="A pair of bright yellow escalators with numbered indicators dimly lit" /&gt;&lt;p&gt;I want to try posting more images to my blog, so here&amp;rsquo;s my first try.
Instagram doesn&amp;rsquo;t really seem like a good place to post photos anymore, so I figured I&amp;rsquo;d try on my blog.
I&amp;rsquo;d like to get my blog working with &lt;a href=&#34;https://posseparty.com&#34;&gt;Posse Party&lt;/a&gt; at some point, I just need to figure out the API keys, and then I can cross post this to Instagram anyway.&lt;/p&gt;
&lt;p&gt;Recently I went on a photo walk to the Seattle downtown public library.
These images are from that photo walk!
I&amp;rsquo;ve been living in Seattle since before the library was built, and I never took the chance to actually go visit, so this was a good opportunity.
I feel like when you live somewhere, you don&amp;rsquo;t take the opportunity to visit all of the cool stuff there, and going on local photo walks seems like a good way for me to visit more of the city.&lt;/p&gt;
&lt;p&gt;Anyway, the Seattle public library is really great and I recommend anyone to visit!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Apple Photos App Corrupts Images</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2025/09/17/apple-photos-app-corrupts-images/"/>
    <id>https://tenderlovemaking.com/2025/09/17/apple-photos-app-corrupts-images/</id>
    <published>2025-09-17T08:59:23+09:00</published>
    <updated>2025-09-17T08:59:23+09:00</updated>
    <content type="html">&lt;p&gt;The Apple Photos app sometimes corrupts images when importing from my camera.
I just wanted to make a blog post about it in case anyone else runs into the problem.
I&amp;rsquo;ve seen other references to this online, but most of the people gave up trying to fix it, and none of them went as far as I did to debug the issue.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll try to describe the problem, and the things I&amp;rsquo;ve tried to do to fix it.
But also note that I&amp;rsquo;ve (sort of) given up on the Photos app too.
Since I can&amp;rsquo;t trust it to import photos from my camera, I switched to a different workflow.&lt;/p&gt;
&lt;p&gt;Here is a screenshot of a corrupted image in the Photos app:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/2025/09/17/apple-photos-app-corrupts-images/images/corrupt-image.png&#34; alt=&#34;screenshot of a corrupt image&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;how-i-used-to-import-images&#34;&gt;How I used to import images&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve got an &lt;a href=&#34;https://en.wikipedia.org/wiki/OM_System_OM-1&#34;&gt;OM System OM-1&lt;/a&gt; camera.
I used to shoot in RAW + jpg, then when I would import to Photos app, I would check the &amp;ldquo;delete photos after import&amp;rdquo; checkbox in order to empty the SD card.
Turns out &amp;ldquo;delete after import&amp;rdquo; was a huge mistake.&lt;/p&gt;
&lt;h2 id=&#34;getting-corrupted-images&#34;&gt;Getting corrupted images&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure I&amp;rsquo;d been getting corrupted images for a while, but it would only be 1 or 2 images out of thousands, so I thought nothing of it (it was probably my fault anyway, right?)&lt;/p&gt;
&lt;p&gt;But the problem really got me upset when last year I went to a family member&amp;rsquo;s wedding and took tons of photos.
Apple Photos combines RAW + jpg photos so you don&amp;rsquo;t have a bunch of duplicates, and when you view the images in the photos app, it just shows you the jpg version by default.
After I imported all of the wedding photos I noticed some of them were corrupted.
Upon closer inspection, I found that it sometimes had corrupted the jpg, sometimes corrupted the RAW file, and sometimes both.
Since I had been checking the &amp;ldquo;delete after import&amp;rdquo; box, I didn&amp;rsquo;t know if the images on the SD card were corrupted &lt;em&gt;before&lt;/em&gt; importing or not.
After all, the files had been deleted so there was no way to check.&lt;/p&gt;
&lt;p&gt;I estimate I completely lost about 30% of the images I took that day.&lt;/p&gt;
&lt;p&gt;Losing so many photos really rattled me, but I wanted to figure out the problem so I didn&amp;rsquo;t lose images in the future.&lt;/p&gt;
&lt;h2 id=&#34;narrowing-down-the-problem&#34;&gt;Narrowing down the problem&lt;/h2&gt;
&lt;p&gt;I was worried this was somehow a hardware problem.
Copying files seems so basic, I didn&amp;rsquo;t think there was any way a massively deployed app like Photos could fuck it up (especially since its main job is managing photo files).
So, to narrow down the issue I changed out all of the hardware.
Here are all the things I did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Switched USB-C cables&lt;/li&gt;
&lt;li&gt;Bought a new SD card direct from the manufacturer (to eliminate the possibility of buying a bootleg SD card)&lt;/li&gt;
&lt;li&gt;Switched to only shooting in RAW (if importing messes up 30% of my images, but I cut the number of images I import by half, then that should be fewer corrupted images right? lol)&lt;/li&gt;
&lt;li&gt;Bought a new laptop&lt;/li&gt;
&lt;li&gt;Bought a new camera: the OM System OM-1 MKii&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did each of these steps over time, as to only change one variable at a time, and still the image corruption persisted.
I didn&amp;rsquo;t really want to buy a new camera, the MKii is not really a big improvement over the OM-1, but we had a family trip coming up and the idea that pressing the shutter button on the camera might not actually record the image didn&amp;rsquo;t sit well with me.&lt;/p&gt;
&lt;h2 id=&#34;finally-a-smoking-gun&#34;&gt;Finally a smoking gun&lt;/h2&gt;
&lt;p&gt;Since I had replaced literally all of the hardware involved, I knew it must be a software problem.
I stopped checking the &amp;ldquo;delete after import&amp;rdquo; button, and started reviewing all of the photos after import.
After verifying none of them were corrupt, then I would format the SD card.
I did this for months without finding any corrupt files.
At this point I figured it was somehow a race condition or something when copying the photo files and deleting them at the same time.&lt;/p&gt;
&lt;p&gt;However, after I got home from RailsConf and imported my photos, I found one corrupt image (the one above).
I was able to verify that the image was &lt;em&gt;not&lt;/em&gt; corrupt on the SD card, so the camera was working fine (meaning I probably didn&amp;rsquo;t need to buy a new camera body at all).&lt;/p&gt;
&lt;p&gt;I tried deleting the corrupt file and re-importing the original to see if it was something about that particular image, but it re-imported just fine.
In other words, it seems like the Photos app will corrupt files randomly.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know if this is a problem that is specific to OM System cameras, and I&amp;rsquo;m not particularly interested in investing in a new camera system just to find out.&lt;/p&gt;
&lt;p&gt;If I compare the corrupted image with the non-corrupted image, the file sizes are exactly the same, but the bytes are different:&lt;/p&gt;
&lt;p&gt;Checksums:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aaron@tc ~/Downloads&amp;gt; md5sum P7110136-from-camera.ORF Exports/P7110136.ORF 
17ce895fd809a43bad1fe8832c811848  P7110136-from-camera.ORF
828a33005f6b71aea16d9c2f2991a997  Exports/P7110136.ORF
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;File sizes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aaron@tc ~/Downloads&amp;gt; ls -al P7110136-from-camera.ORF Exports/P7110136.ORF
-rw-------@ 1 aaron  staff  18673943 Jul 12 04:38 Exports/P7110136.ORF
-rwx------  1 aaron  staff  18673943 Jul 17 09:29 P7110136-from-camera.ORF*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;P7110136-from-camera.ORF&lt;/code&gt; is the non-corrupted file, and &lt;code&gt;Exports/P7110136.ORF&lt;/code&gt; is the corrupted file from Photos app.
Here&amp;rsquo;s a screenshot of the preview of the non-corrupted photo:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/2025/09/17/apple-photos-app-corrupts-images/images/non-corrupt.png&#34; alt=&#34;screenshot of non-corrupt image&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gist.github.com/tenderlove/25853f50ab46a58738ff2cc22d682f2b&#34;&gt;Here is the binary diff between the files&lt;/a&gt;.
I ran both files through &lt;code&gt;xxd&lt;/code&gt; then diffed them.
Also if anyone cares to look, I&amp;rsquo;ve posted the RAW files &lt;a href=&#34;https://github.com/tenderlove/corrupt-raws&#34;&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;my-new-workflow&#34;&gt;My new workflow&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not going to put any more effort into debugging this problem, but I wanted to blog about it in case anyone else is seeing the issue.
I take a lot of photos, and to be frank, most of them are not very good.
I don&amp;rsquo;t want to look through a bunch of bad photos every time I look at my library, so culling photos is important.
Culling photos in the Photos app is way too cumbersome, so I&amp;rsquo;ve switched to using &lt;a href=&#34;https://www.darktable.org&#34;&gt;Darktable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My current process is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Import images to Darktable&lt;/li&gt;
&lt;li&gt;Delete the ones I don&amp;rsquo;t like&lt;/li&gt;
&lt;li&gt;Process ones I do like&lt;/li&gt;
&lt;li&gt;Export both the jpg and the original raw file&lt;/li&gt;
&lt;li&gt;Import those to the Photos app so they&amp;rsquo;re easy to view and share&lt;/li&gt;
&lt;li&gt;Periodically format my SD card&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ve not seen any file corruption when importing to Darktable, so I am convinced this is a problem with the Photos app.
But now, since all of my images land in Darktable before making their way to the Photos app, I don&amp;rsquo;t really care anymore.
The bad news is that I&amp;rsquo;ve spent a lot of time and money trying to debug this.
I guess the good news is that now I have redundant hardware!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>File preallocation on macOS in Ruby</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2025/09/10/file-preallocation-on-macos-in-ruby/"/>
    <id>https://tenderlovemaking.com/2025/09/10/file-preallocation-on-macos-in-ruby/</id>
    <published>2025-09-10T07:12:21+09:00</published>
    <updated>2025-09-10T07:12:21+09:00</updated>
    <content type="html">&lt;p&gt;I haven&amp;rsquo;t blogged in a while, so I figured I should do that.
Jet lag has blessed me with some free time this morning, so I figured I would make some content in order to feed the AI bots.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been messing around with pre-allocating files on the file system on macOS.
This is useful in cases where you have a large file you need to copy, and you want to copy it quickly.
For example, a tar implementation where the tar file might contain large files you need to copy.&lt;/p&gt;
&lt;p&gt;Here is the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fcntl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# typedef struct fstore {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#     u_int32_t fst_flags;      /* IN: flags word */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#     int       fst_posmode;    /* IN: indicates offset field */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#     off_t     fst_offset;     /* IN: start of the region */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#     off_t     fst_length;     /* IN: size of the region */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#     off_t     fst_bytesalloc; /* OUT: number of bytes allocated */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# } fstore_t;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fmt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Fcntl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;F_ALLOCATECONTIG&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;Fcntl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;F_PEOFPOSMODE&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, size, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fmt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LlQQQ&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;File&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;wb&amp;#34;&lt;/span&gt;) { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;fd&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  fd&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fcntl(&lt;span style=&#34;color:#66d9ef&#34;&gt;Fcntl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;F_PREALLOCATE&lt;/span&gt;, bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  fd&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;truncate size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you run this script, you&amp;rsquo;ll find a file named &amp;ldquo;foo&amp;rdquo; with the size 1234.
For this code to work, you&amp;rsquo;ll need to be on macOS, and using &lt;a href=&#34;https://github.com/ruby/fcntl/pull/30&#34;&gt;this branch&lt;/a&gt; of the fcntl gem (though hopefully my patch will make it upstream and you can just use the fcntl gem).&lt;/p&gt;
&lt;p&gt;I tried implementing this as a performance optimization, but unfortunately the performance optimization didn&amp;rsquo;t work out, so I&amp;rsquo;m probably not going to use this code IRL.
Rather than leaving the code to rot on my computer, I figured I&amp;rsquo;d make a blog post so at least people can search for it, or I can train the LLMs in my image (lol).&lt;/p&gt;
&lt;p&gt;Anyway, thanks for reading my very niche content!
Have a good day!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Monkey Patch Detection in Ruby</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2024/10/16/monkey-patch-detection-in-ruby/"/>
    <id>https://tenderlovemaking.com/2024/10/16/monkey-patch-detection-in-ruby/</id>
    <published>2024-10-16T16:13:00-07:00</published>
    <updated>2024-10-16T16:13:00-07:00</updated>
    <content type="html">&lt;p&gt;&lt;a href=&#34;https://tenderlovemaking.com/2024/09/29/eliminating-intermediate-array-allocations/&#34;&gt;My last post&lt;/a&gt; detailed one way that CRuby will eliminate some intermediate array allocations when using methods like &lt;code&gt;Array#hash&lt;/code&gt; and &lt;code&gt;Array#max&lt;/code&gt;.
Part of the technique hinges on detecting when someone monkey patches array.
Today, I thought we&amp;rsquo;d dive a little bit in to how CRuby detects and de-optimizes itself when these &amp;ldquo;important&amp;rdquo; methods get monkey patched.&lt;/p&gt;
&lt;h2 id=&#34;monkey-patching-problem&#34;&gt;Monkey Patching Problem&lt;/h2&gt;
&lt;p&gt;The optimization in the previous post made the assumption that the implementation &lt;code&gt;Array#max&lt;/code&gt; was the original definition (as defined in Ruby itself).
But the Ruby language allows us to reopen classes, redefine any methods we want, and that those methods will &amp;ldquo;just work&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;For example, if someone were to reopen &lt;code&gt;Array&lt;/code&gt; and define a new &lt;code&gt;max&lt;/code&gt; method, we would need to respect that monkey patch:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;puts &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;hello!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In fact, a monkey patch implementation could mutate the array itself, so we&amp;rsquo;re definitely required to allocate an array in the case that someone added their own &lt;code&gt;max&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;max&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:neat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p x &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; [1, 2, :neat]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So how does CRuby detect that a method has been monkey patched?&lt;/p&gt;
&lt;h2 id=&#34;method-definition-time&#34;&gt;Method Definition Time&lt;/h2&gt;
&lt;p&gt;Every time a method is defined, an entry is stored in a hash table pointed to by the current class.
We call this the &amp;ldquo;method table&amp;rdquo;, but you&amp;rsquo;ll see it referred to as &lt;code&gt;M_TBL&lt;/code&gt; or &lt;code&gt;RCLASS_M_TBL&lt;/code&gt; &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/vm_method.c#L999&#34;&gt;in the code&lt;/a&gt;.
The key to the hash is simply the method name as an &lt;code&gt;ID&lt;/code&gt; type (an integer which represents a Ruby &lt;code&gt;Symbol&lt;/code&gt;), and the value of the hash is a method entry structure.
If there was already an entry in the table, then we know it&amp;rsquo;s a &amp;ldquo;redefinition&amp;rdquo; (a.k.a. &amp;ldquo;monkey patch&amp;rdquo;), and we end up calling &lt;code&gt;rb_vm_check_redefinition_opt_method&lt;/code&gt; &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/vm_method.c#L1007&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rb_vm_check_redefinition_opt_method&lt;/code&gt; checks to see if this is a method we &amp;ldquo;care&amp;rdquo; about.
Methods we &amp;ldquo;care&amp;rdquo; about are typically ones where we&amp;rsquo;ve made some kind of optimization and we need to deoptimize if someone redefines them.&lt;/p&gt;
&lt;p&gt;If the redefined method is something we care to detect, then we set a flag in a global variable &lt;code&gt;ruby_vm_redefined_flag&lt;/code&gt;, which is an array of integers.&lt;/p&gt;
&lt;p&gt;The indexes of the &lt;code&gt;ruby_vm_redefined_flag&lt;/code&gt; array correspond to &amp;ldquo;basic operators&amp;rdquo;, or BOPs.
So for example, the 0th element is for &lt;code&gt;BOP_PLUS&lt;/code&gt;, the 1th element is &lt;code&gt;BOP_MINUS&lt;/code&gt;, etc.
You can see the full list of basic operators &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/internal/basic_operators.h#L8-L42&#34;&gt;here&lt;/a&gt;.
These basic operators correspond to &lt;em&gt;method names that we care about&lt;/em&gt;.
So if someone monkey patches the &lt;code&gt;+&lt;/code&gt; operator, we&amp;rsquo;ll set a flag in &lt;code&gt;ruby_vm_redefined_flag[BOP_PLUS]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The values of the &lt;code&gt;ruby_vm_redefined_flag&lt;/code&gt; array correspond to a bitmap that maps to &lt;em&gt;classes&lt;/em&gt; we care about.
You can see the list of classes and their corresponding bits &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/internal/basic_operators.h#L47-L60&#34;&gt;here&lt;/a&gt;, as well as a function for mapping &amp;ldquo;classes we care about&amp;rdquo; &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/vm.c#L2079-L2097&#34;&gt;to their corresponding bit flag&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, if someone monkey patches &lt;code&gt;Array#pack&lt;/code&gt;, we would set a bit in &lt;code&gt;ruby_vm_redefined_flag&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ruby_vm_redefined_flag[BOP_PACK] &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; ARRAY_REDEFINED_OP_FLAG;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, when we execute our optimized instruction (&lt;code&gt;opt_newarray_send&lt;/code&gt; which was introduced in the last post), we can check the bitmap to decide whether or not to take our fast path:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((ruby_vm_redefined_flag[BOP_PACK] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; ARRAY_REDEFINED_OP_FLAG) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// It _hasn&amp;#39;t_ been monkey patched, so take the fast path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// It _has_ been monkey patched, do the slow path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of course this bitmask checking is wrapped in a macro that looks more like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;BASIC_OP_UNREDEFINED_P&lt;/span&gt;(BOP_PACK, ARRAY_REDEFINED_OP_FLAG)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// It _hasn&amp;#39;t_ been monkey patched, so take the fast path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// It _has_ been monkey patched, do the slow path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see the actual code for &lt;code&gt;Array#pack&lt;/code&gt; redefinition checking &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/vm_insnhelper.c#L6238&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;bonus-stuff&#34;&gt;Bonus Stuff&lt;/h2&gt;
&lt;p&gt;A cool thing (at least I think it&amp;rsquo;s cool) is that the function &lt;code&gt;rb_vm_check_redefinition_opt_method&lt;/code&gt; not only sets up the &amp;ldquo;monkey patch detection&amp;rdquo; bits, it&amp;rsquo;s also a natural place to inform the JIT compiler that someone has done something catastrophic and that it should de-optimize.  In fact, you can see those calls &lt;a href=&#34;https://github.com/ruby/ruby/blob/87be381bbb7c2d4501942124e9851a2a9e1a07a2/vm.c#L2146-L2147&#34;&gt;right here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A weird thing is that since &lt;code&gt;ruby_vm_redefined_flag&lt;/code&gt; is just a list bitmaps, it&amp;rsquo;s technically possible for us to track the definition of &lt;code&gt;Integer#pack&lt;/code&gt; even though that method doesn&amp;rsquo;t exist:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ruby_vm_redefined_flag[BOP_PACK] &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; INTEGER_REDEFINED_OP_FLAG;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I guess that means there&amp;rsquo;s a lot of bit space that isn&amp;rsquo;t used, but I don&amp;rsquo;t really think it&amp;rsquo;s a big deal.&lt;/p&gt;
&lt;p&gt;Anyway, have a good day!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Eliminating Intermediate Array Allocations</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2024/09/29/eliminating-intermediate-array-allocations/"/>
    <id>https://tenderlovemaking.com/2024/09/29/eliminating-intermediate-array-allocations/</id>
    <published>2024-09-29T19:06:28-07:00</published>
    <updated>2024-09-29T19:06:28-07:00</updated>
    <content type="html">&lt;p&gt;Recently I gave a talk at &lt;a href=&#34;https://rubyonrails.org/world/2024&#34;&gt;RailsWorld&lt;/a&gt; (hopefully they&amp;rsquo;ll post the video soon), and part of my presentation was about eliminating allocations in tokenizers.
I presented a simple function for measuring allocations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;allocations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Everything in Ruby is an object, but not all objects actually make allocations.
We can use the above function to measure allocations made in a block.
Here are some examples of code that never allocate:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; }                  &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; }                 &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; }                   &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#e6db74&#34;&gt;:hello&lt;/span&gt; }                &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }                     &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; }                   &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF_FFFF_FFFF_FFFF&lt;/span&gt; } &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Literals like booleans, nil, symbols, integers, and floats are represented internally to CRuby as &amp;ldquo;tagged pointers&amp;rdquo; and they don&amp;rsquo;t allocate anything when executed.&lt;/p&gt;
&lt;p&gt;Here is an example of code that sometimes allocates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Depends on the size of the number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; }                     &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#ae81ff&#34;&gt;0x3FFF_FFFF_FFFF_FFFF&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; } &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Depends on `frozen_string_literal`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello!&amp;#34;&lt;/span&gt; }                  &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0 or 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Math on integers generally doesn&amp;rsquo;t allocate anything, but it depends on the integer.
When a number gets large enough, CRuby will allocate an object to represent that number.
On 64 bit platforms, the largest whole number we can represent without allocating is 0x3FFF_FFFF_FFFF_FFFF.&lt;/p&gt;
&lt;p&gt;String literals will sometimes allocate, but it depends on the &lt;code&gt;frozen_string_literal&lt;/code&gt; setting in your program.&lt;/p&gt;
&lt;p&gt;Here is an example of code that always allocates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; }      &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { { &lt;span style=&#34;color:#e6db74&#34;&gt;a&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;:b&lt;/span&gt; } }   &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#66d9ef&#34;&gt;Object&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new }  &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; } &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hopefully these examples are fairly straightforward.
Arrays, hashes, objects, string slices, etc will allocate an object.&lt;/p&gt;
&lt;h2 id=&#34;eliminating-intermediate-array-allocations&#34;&gt;Eliminating Intermediate Array Allocations&lt;/h2&gt;
&lt;p&gt;At the &lt;a href=&#34;https://rubyonrails.org/world/2024/day-2/closing-party&#34;&gt;Shopify after-party at RailsWorld&lt;/a&gt;, someone asked me a really great question.
Their codebase has a RuboCop rule that says that when doing min or max calculations, you should always have code like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foo&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;x, y&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;They were concerned this is wasteful as it has an Array literal, so it will be allocating an array every time!&lt;/p&gt;
&lt;p&gt;I think this is a really great question, and if you read my earlier allocation measurement examples, I think it&amp;rsquo;s a very reasonable conclusion.
However, it&amp;rsquo;s actually not the case.
This code in particular will &lt;em&gt;not&lt;/em&gt; allocate an array, and I thought we&amp;rsquo;d look in to how that works.&lt;/p&gt;
&lt;p&gt;The compiler in Ruby is able to tell a few important things about this code.
First, we&amp;rsquo;re calling a method on an array &lt;em&gt;literal&lt;/em&gt; which means that we&amp;rsquo;re guaranteed that the &lt;code&gt;max&lt;/code&gt; method will be sent to an array object.
Second, we know statically that we&amp;rsquo;re calling the &lt;code&gt;max&lt;/code&gt; method.
Third, the &lt;code&gt;max&lt;/code&gt; method that is implemented in core Ruby will not mutate its receiver, and it returns some value (an integer) that &lt;em&gt;isn&amp;rsquo;t&lt;/em&gt; the array literal.&lt;/p&gt;
&lt;p&gt;Since the compiler knows that the array literal is ephemeral, it allocates the array on the stack, does the &lt;code&gt;max&lt;/code&gt; calculation, then throws away the array, never asking the GC for a new object.&lt;/p&gt;
&lt;p&gt;To get a more concrete picture, lets look at the instruction sequences for the above code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foo&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;x, y&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;insn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;InstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;of(method(&lt;span style=&#34;color:#e6db74&#34;&gt;:foo&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;puts insn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disasm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;== disasm: #&amp;lt;ISeq:foo@x.rb:1 (1,0)-(1,30)&amp;gt;
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] x@0&amp;lt;Arg&amp;gt;   [ 1] y@1&amp;lt;Arg&amp;gt;
0000 getlocal_WC_0                          x@0                       (   1)[LiCa]
0002 getlocal_WC_0                          y@1
0004 opt_newarray_send                      2, 1
0007 leave                                  [Re]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first two instructions fetch the locals &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, and push them on the stack.
Next we have a special instruction &lt;code&gt;opt_newarray_send&lt;/code&gt;.
This instruction takes two parameters, &lt;code&gt;2, 1&lt;/code&gt;.
It&amp;rsquo;s a bit cryptic, but the &lt;code&gt;2&lt;/code&gt; means that this instruction is going to operate on two stack elements.
The &lt;code&gt;1&lt;/code&gt; is an enum and means &amp;ldquo;we want to call the &lt;code&gt;max&lt;/code&gt; method&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;opt_newarray_send&lt;/code&gt; instruction will first check to see if &lt;code&gt;Array#max&lt;/code&gt; has been monkey patched.
If it has been monkey patched, then the instruction will allocate a regular array and call the monkey patched method.
If it &lt;em&gt;hasn&amp;rsquo;t&lt;/em&gt; been monkey patched, then it calls a &amp;ldquo;max&amp;rdquo; function which uses Ruby&amp;rsquo;s stack as an array buffer.&lt;/p&gt;
&lt;p&gt;Here is what the stack looks like before executing &lt;code&gt;opt_newarray_send&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;+----+-------------+-------------+
|    | Stack Index | Stack Value |
+----+-------------+-------------+
|    | -2          | x           |
|    | -1          | y           |
| SP | 0           | Undef       |
+----+-------------+-------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;opt_newarray_send&lt;/code&gt; instruction was passed the value 2, so it knows to start the array at &lt;em&gt;negative&lt;/em&gt; 2 relative to the stack pointer (SP).
Since the stack is just an array, it calls the same function that the &lt;code&gt;max&lt;/code&gt; function would normally call, popping 2 values from the stack, then pushing the return value of the max function.&lt;/p&gt;
&lt;p&gt;In this way we can calculate the max value without allocating the intermediate array.&lt;/p&gt;
&lt;p&gt;If we use our allocations function, we can confirm that the &lt;code&gt;foo&lt;/code&gt; method indeed does not allocate anything:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foo&lt;/span&gt;(x, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;x, y&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;allocations { foo(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) } &lt;span style=&#34;color:#75715e&#34;&gt;# heat inline caches&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p allocations { foo(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) } &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;aarons-opinion-corner&#34;&gt;Aaron&amp;rsquo;s Opinion Corner&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t really know RuboCop very well, but I think that in cases like this it would be really helpful if the linter were to tell you &lt;em&gt;why&lt;/em&gt; a particular rule is a rule.
Personally, I dislike following rules unless I understand the reason behind them.
Even if the reasoning is simply &amp;ldquo;this is just how our team styles our code&amp;rdquo;.
If such a feature is already available in RuboCop, then please feel free to link to this blog post for this particular rule.&lt;/p&gt;
&lt;p&gt;I can only assume the rule that enforced this style was &amp;ldquo;performance&amp;rdquo; related.
I&amp;rsquo;m not a huge fan of linting, but I&amp;rsquo;m even less of a fan when it comes to rules around &amp;ldquo;performance&amp;rdquo;.
If idiomatic Ruby is not performant, then I think there can be a strong case to be made that the CRuby team (which I am a part of) should make that code performant.
If the CRuby team &lt;em&gt;does&lt;/em&gt; make the code performant, then there is no need for the performance rule because most people write idiomatic Ruby code (by definition).&lt;/p&gt;
&lt;p&gt;Of course there are cases where you may need to write non-idiomatic Ruby for performance reasons, but hopefully those cases are few and far between.
Should the time arrive when you need to write odd code for performance reasons, it will require knowledge, experience, and nuance that neither a linter nor an AI can provide.
Fortunately, this is a case where idiomatic Ruby is also &amp;ldquo;the fast way to do things&amp;rdquo;, so I definitely recommend people use the &lt;code&gt;[x, y].max&lt;/code&gt; pattern.&lt;/p&gt;
&lt;h2 id=&#34;more-stuff&#34;&gt;More Stuff&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Array#max&lt;/code&gt; isn&amp;rsquo;t the only method that uses this trick.
It works with &lt;code&gt;Array#min&lt;/code&gt;, &lt;code&gt;Array#pack&lt;/code&gt; and &lt;code&gt;Array#hash&lt;/code&gt;.
If you need to implement a custom &lt;code&gt;hash&lt;/code&gt; method on an object, then I highly recommend doing something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;@ivar1, @ivar2, &lt;span style=&#34;color:#f92672&#34;&gt;...].&lt;/span&gt;hash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, there are cases where CRuby &lt;em&gt;won&amp;rsquo;t&lt;/em&gt; apply this trick.
Lets look at the instructions for the following method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;max
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;insn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;InstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;of(method(&lt;span style=&#34;color:#e6db74&#34;&gt;:foo&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;puts insn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disasm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;== disasm: #&amp;lt;ISeq:foo@x.rb:1 (1,0)-(3,3)&amp;gt;
0000 duparray                               [3, 4]                    (   2)[LiCa]
0002 opt_send_without_block                 &amp;lt;calldata!mid:max, argc:0, ARGS_SIMPLE&amp;gt;
0004 leave                                                            (   3)[Re]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you read these instructions carefully, you&amp;rsquo;ll see it has a &lt;code&gt;duparray&lt;/code&gt; instruction.
This instruction &lt;strong&gt;allocates an array&lt;/strong&gt;, and then we call the &lt;code&gt;max&lt;/code&gt; method on the array.&lt;/p&gt;
&lt;p&gt;When all of the elements of the array are static, CRuby applies an optimization to allocate the array once, embed it in the instructions, and then do a &lt;code&gt;dup&lt;/code&gt; on the array.
Copying an existing array is much faster than allocating a new one.
Unfortunately, this optimization is applied &lt;em&gt;before&lt;/em&gt; the &amp;ldquo;max&amp;rdquo; method optimization, so it doesn&amp;rsquo;t apply both.&lt;/p&gt;
&lt;p&gt;For those of you at home saying &amp;ldquo;the compiler could calculate the max of &lt;code&gt;[3, 4]&lt;/code&gt; and eliminate the array all together!&amp;rdquo; just remember that someone could monkey patch &lt;code&gt;Array#max&lt;/code&gt; and we&amp;rsquo;d need to respect it.  Argh!!
Fixing this particular case is not worth the code complexity, in my opinion.
We all know that 4 is greater than 3, so we could &amp;ldquo;manually inline&amp;rdquo; this case and just write &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, all this to say is that these optimizations are context dependent.
Attempting to &amp;ldquo;prescribe&amp;rdquo; more optimal code seems like it could become a hairy situation, especially since the linter can&amp;rsquo;t know what the Ruby compiler will do.&lt;/p&gt;
&lt;p&gt;I do like the idea of language servers possible suggesting &lt;em&gt;possibly&lt;/em&gt; faster code, but only as a teaching opportunity for the developer.
The real goal should be to help build understanding so that this type of linting becomes unnecessary.&lt;/p&gt;
&lt;p&gt;Anyway, I had a really great time at RailsWorld.
I am very happy I got this question, and I hope that this post helps someone!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Using Serial Ports with Ruby</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2024/02/16/using-serial-ports-with-ruby/"/>
    <id>https://tenderlovemaking.com/2024/02/16/using-serial-ports-with-ruby/</id>
    <published>2024-02-16T11:56:28-08:00</published>
    <updated>2024-02-16T11:56:28-08:00</updated>
    <content type="html">&lt;p&gt;Lets mess around with serial ports today!
I love doing hardware hacking, and dealing with serial ports is a common thing you have to do when working with embedded systems.
Of course I want to do everything with Ruby, and I had found Ruby serial port libraries to be either lacking, or too complex, so I decided to &lt;a href=&#34;https://github.com/tenderlove/uart&#34;&gt;write my own&lt;/a&gt;.
I feel like I&amp;rsquo;ve not done a good enough job promoting the library, so today we&amp;rsquo;re going to mess with serial ports using the &lt;a href=&#34;https://github.com/tenderlove/uart&#34;&gt;UART gem&lt;/a&gt;.
Don&amp;rsquo;t let the last commit date on the repo fool you, despite being over 6 years ago, this library is actively maintained (and I use it every day!).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a &lt;a href=&#34;https://www.gqelectronicsllc.com/comersus/store/comersus_viewItem.asp?idProduct=4579&#34;&gt;GMC-320 Geiger counter&lt;/a&gt;.
Not only is the price pretty reasonable, but it also has a serial port interface!
You can log data, then download the logged data via serial port.
Today we&amp;rsquo;re just going to write a very simple program that gets the firmware version via serial port, and then gets live updates from the device.
This will allow us to start with UART basics in Ruby, and then work with streaming data and timeouts.&lt;/p&gt;
&lt;p&gt;The company that makes the Geiger counter published &lt;a href=&#34;https://www.gqelectronicsllc.com/download/GQ-RFC1201.txt&#34;&gt;a spec for the UART commands that the device supports&lt;/a&gt;, so all we need to do is send the commands and read the results.&lt;/p&gt;
&lt;p&gt;According to the spec, the default UART config for my Geiger counter is 115200 BPS, Data bit: 8, no parity, Stop bit: 1, and no control.
This is pretty easy to configure with the UART gem, all of these values are default except for the baud rate.
The UART gem defaults to 9600 for the baud rate, so that&amp;rsquo;s the only thing we&amp;rsquo;ll have to configure.&lt;/p&gt;
&lt;h2 id=&#34;getting-the-hardware-version&#34;&gt;Getting the hardware version&lt;/h2&gt;
&lt;p&gt;To get the hardware model and version, we just have to send &lt;code&gt;&amp;lt;GETVER&amp;gt;&amp;gt;&lt;/code&gt; over the serial port and then read the response.
Let&amp;rsquo;s write a small program that will fetch the hardware model and version and print them out.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UART&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;115200&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;serial&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Send the &amp;#34;get version&amp;#34; command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;GETVER&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# read and print the result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  puts serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first thing we do is require the &lt;code&gt;uart&lt;/code&gt; library (make sure to &lt;code&gt;gem install uart&lt;/code&gt; if you haven&amp;rsquo;t done so yet).
Then we open the serial interface.
We&amp;rsquo;ll pass the tty file in via the command line, so &lt;code&gt;ARGV[0]&lt;/code&gt; will have the path to the tty.
When I plug my Geiger counter in, it shows up as &lt;code&gt;/dev/tty.usbserial-111240&lt;/code&gt;.
We also configure the baud rate to 115200.&lt;/p&gt;
&lt;p&gt;Once the serial port is open, we are free to read and write to it as if it were a Ruby file object.
In fact, this is because it really is just a regular file object.&lt;/p&gt;
&lt;p&gt;First we&amp;rsquo;ll send the command &lt;code&gt;&amp;lt;GETVER&amp;gt;&amp;gt;&lt;/code&gt;, then we&amp;rsquo;ll read the response from the serial port.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what it looks like when I run it on my machine:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby rad.rb /dev/tty.usbserial-111240
GMC-320Re 4.09
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;live-updates&#34;&gt;Live updates&lt;/h2&gt;
&lt;p&gt;According to the documentation, we can get live updates from the hardware.
To do that, we just need to send the &lt;code&gt;&amp;lt;HEARTBEAT1&amp;gt;&amp;gt;&lt;/code&gt; command.
Once we send that command, the hardware will write a value to the serial port every second, and it&amp;rsquo;s our job to read the data when it becomes available.
We can use &lt;code&gt;IO#wait_readable&lt;/code&gt; to wait until there is data to be read from the serial port.&lt;/p&gt;
&lt;p&gt;According to the specification, there are two bytes (a 16 bit integer), and we need to ignore the top 2 bits.
We&amp;rsquo;ll create a mask to ignore the top two bits, and combine that with the two bytes we read to get our value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UART&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;115200&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;serial&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# turn on heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT1&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait_readable
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      p count
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# make sure to turn off heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT0&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After we&amp;rsquo;ve sent the &amp;ldquo;start heartbeat&amp;rdquo; command, we enter a loop.
Inside the loop, we block until there is data available to read by calling &lt;code&gt;serial.wait_readable&lt;/code&gt;.
Once there is data to read, we&amp;rsquo;ll read two bytes and combine them to a 16 bit integer.
Then we mask off the integer using the &lt;code&gt;MASK&lt;/code&gt; constant so that the two top bits are ignored.
Finally we just print out the count.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ensure&lt;/code&gt; section ensures that when the program exits, we&amp;rsquo;ll tell the hardware &amp;ldquo;hey, we don&amp;rsquo;t want to stream data anymore!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;When I run this on my machine, the output is like this (I hit Ctrl-C to stop the program):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby rad.rb /dev/tty.usbserial-111240
0
0
0
0
0
0
0
1
0
1
0
0
0
1
^Crad.rb:10:in &amp;#39;IO#wait_readable&amp;#39;: Interrupt
	from rad.rb:10:in &amp;#39;block (2 levels) in &amp;lt;main&amp;gt;&amp;#39;
	from &amp;lt;internal:kernel&amp;gt;:191:in &amp;#39;Kernel#loop&amp;#39;
	from rad.rb:9:in &amp;#39;block in &amp;lt;main&amp;gt;&amp;#39;
	from /Users/aaron/.rubies/arm64/ruby-trunk/lib/ruby/gems/3.4.0+0/gems/uart-1.0.0/lib/uart.rb:57:in &amp;#39;UART.open&amp;#39;
	from rad.rb:5:in &amp;#39;&amp;lt;main&amp;gt;&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lets do two improvements, and then call it a day.
First, lets specify a timeout, then lets calculate the CPM.&lt;/p&gt;
&lt;h2 id=&#34;specifying-a-timeout&#34;&gt;Specifying a timeout&lt;/h2&gt;
&lt;p&gt;Currently, &lt;code&gt;serial.wait_readable&lt;/code&gt; will block forever, but we expect an update from the hardware about every second.
If it takes longer than say 2 seconds for data to be available, then something must be wrong and we should print a message or exit the program.&lt;/p&gt;
&lt;p&gt;Specifying a timeout is quite easy, we just pass the timeout (in seconds) to the &lt;code&gt;wait_readable&lt;/code&gt; method like below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UART&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;115200&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;serial&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# turn on heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT1&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait_readable(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      p count
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      $stderr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;oh no, something went wrong!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      exit(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# make sure to turn off heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT0&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When data becomes available, &lt;code&gt;wait_readable&lt;/code&gt; will return a truthy value, and if the timeout was reached, then it will return a falsy value.
So, if it takes more than 2 seconds for data to become available &lt;code&gt;wait_readable&lt;/code&gt; will return &lt;code&gt;nil&lt;/code&gt;, and we print an error message and exit the program.&lt;/p&gt;
&lt;h2 id=&#34;calculating-cpm&#34;&gt;Calculating CPM&lt;/h2&gt;
&lt;p&gt;CPM stands for &amp;ldquo;counts per minute&amp;rdquo;, meaning the number of ionization events the hardware has detected within one minute.
However, the value that we&amp;rsquo;re reading from the serial port is actually the &amp;ldquo;counts per second&amp;rdquo; (or ionization events the hardware detected in the last second).
Most of the time that value is 0 so it&amp;rsquo;s not super fun to read.
Lets calculate the CPM and print that instead.&lt;/p&gt;
&lt;p&gt;We know the samples are arriving about every second, so I&amp;rsquo;m just going to modify this code to keep a list of the last 60 samples and just sum those:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFFFF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UART&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;115200&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;serial&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# turn on heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT1&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  samples &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait_readable(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Push the sample on the list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      samples&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;push(((serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readbyte) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MASK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Make sure we only have 60 samples in the list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; samples&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;length &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;; samples&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;shift; &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Print a sum of the samples (if we have 60)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      p &lt;span style=&#34;color:#e6db74&#34;&gt;CPM&lt;/span&gt;: samples&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sum &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; samples&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;length &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      $stderr&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;oh no, something went wrong!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      exit(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# make sure to turn off heartbeat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;HEARTBEAT0&amp;gt;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is the output on my machine:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby rad.rb /dev/tty.usbserial-111240
{:CPM=&amp;gt;9}
{:CPM=&amp;gt;8}
{:CPM=&amp;gt;8}
{:CPM=&amp;gt;8}
{:CPM=&amp;gt;8}
{:CPM=&amp;gt;9}
{:CPM=&amp;gt;9}
{:CPM=&amp;gt;9}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After about a minute or so, it starts printing the CPM.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I love playing with embedded systems as well as dealing with UART.
Next time you need to do any serial port communications in Ruby, UART to consider using &lt;a href=&#34;https://github.com/tenderlove/uart&#34;&gt;my gem&lt;/a&gt;.
Thanks for your time, and I hope you have a great weekend!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Fast Tokenizers with StringScanner</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner/"/>
    <id>https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner/</id>
    <published>2023-09-02T13:00:14-07:00</published>
    <updated>2023-09-02T13:00:14-07:00</updated>
    <content type="html">&lt;p&gt;Lately I&amp;rsquo;ve been messing around with writing a &lt;a href=&#34;https://github.com/tenderlove/tinygql&#34;&gt;GraphQL parser called TinyGQL&lt;/a&gt;.
I wanted to see how fast I could make a GraphQL parser without writing any C extensions.
I &lt;a href=&#34;https://railsatscale.com/2023-08-29-ruby-outperforms-c/&#34;&gt;think I did pretty well&lt;/a&gt;, but I&amp;rsquo;ve learned some tricks for speeding up parsers and I want to share them.&lt;/p&gt;
&lt;p&gt;Today we&amp;rsquo;re going to specifically look at the lexing part of parsing.
Lexing is just breaking down an input string in to a series of tokens.
It&amp;rsquo;s the parser&amp;rsquo;s job to interpret those tokens.
My favorite tool for tokenizing documents in Ruby is &lt;code&gt;StringScanner&lt;/code&gt;.
Today we&amp;rsquo;re going to look at a few tricks for speeding up &lt;code&gt;StringScanner&lt;/code&gt; based lexers.
We&amp;rsquo;ll start with a very simple GraphQL lexer and apply a few tricks to speed it up.&lt;/p&gt;
&lt;h2 id=&#34;a-very-basic-lexer&#34;&gt;A very basic lexer&lt;/h2&gt;
&lt;p&gt;Here is the lexer we&amp;rsquo;re going to work with today:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;strscan&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Lexer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;IDENTIFIER&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;/[_A-Za-z][_0-9A-Za-z]*\b/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;WHITESPACE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;%r{ [, \c\r\n\t]+ }x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;COMMENTS&lt;/span&gt;   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;%r{ \#.*$ }x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;INT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;           &lt;span style=&#34;color:#e6db74&#34;&gt;/[-]?(?:[0]|[1-9][0-9]*)/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_DECIMAL&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/[.][0-9]+/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_EXP&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;     &lt;span style=&#34;color:#e6db74&#34;&gt;/[eE][+-]?[0-9]+/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;INT&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_DECIMAL&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_EXP&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_DECIMAL&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT_EXP&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;KEYWORDS&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fragment&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;false&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;null&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mutation&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;subscription&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;schema&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;scalar&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;extend&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;implements&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;interface&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;union&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;enum&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;input&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;directive&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;repeatable&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;freeze
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Regexp&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;union(&lt;span style=&#34;color:#66d9ef&#34;&gt;KEYWORDS&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sort)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;\b/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Hash&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;KEYWORDS&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;kw&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;kw, kw&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;upcase&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_sym&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; }&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;module&lt;/span&gt; Literals
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;LCURLY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;RCURLY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;LPAREN&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;(&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;RPAREN&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;LBRACKET&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;RBRACKET&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;COLON&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;         &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;:&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;VAR_SIGN&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;$&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;DIR_SIGN&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;@&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;EQUALS&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;=&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;BANG&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;!&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;PIPE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;          &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;|&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;AMP&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;           &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;amp;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;ELLIPSIS&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;...&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;include&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Regexp&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;union(&lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;constants&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;const_get(name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;constants&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each_with_object({}) { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;x,o&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    o&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;const_get(x)&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt; doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @doc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @scan &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;StringScanner&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next_token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eos?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;WHITESPACE&lt;/span&gt;)  &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:WHITESPACE&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;COMMENTS&lt;/span&gt;)    &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:COMMENT&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;ELLIPSIS&lt;/span&gt;)    &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:ELLIPSIS&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt;)       &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;IDENTIFIER&lt;/span&gt;)  &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT&lt;/span&gt;)       &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:FLOAT&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;INT&lt;/span&gt;)         &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:INT&lt;/span&gt;, s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:UNKNOWN_CHAR&lt;/span&gt;, @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getch&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It only tokenizes a subset of GraphQL.  Namely, it omits string literals.
Matching string literals is kind of gross, and I wanted to keep this example small, so I removed them.
I have a large document that I&amp;rsquo;ll use to measure some performance aspects of this lexer, and if you want to try it out, you can find the document &lt;a href=&#34;https://github.com/tenderlove/tinygql/blob/main/benchmark/fixtures/negotiate.gql&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To use the lexer, just pass the document you want to tokenize, then repeatedly call &lt;code&gt;next_token&lt;/code&gt; on the lexer until it returns &lt;code&gt;nil&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lexer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Lexer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new input
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lexer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;next_token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;GraphQL documents look something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mutation {
  a: likeStory(storyID: 12345) {
    b: story {
      c: likeCount
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And with this lexer implementation, the tokens come out as tuples and they look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;likeStory&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:LPAREN&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;storyID&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our benchmarking code is going to be very simple, we&amp;rsquo;re just going to use the lexer to pull all of the tokens out of the test document:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;benchmark/ips&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;allocations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;go&lt;/span&gt; doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lexer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Lexer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lexer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;next_token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# do nothing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;doc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGF&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Benchmark&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ips { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;x&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; x&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;report { go doc } }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p &lt;span style=&#34;color:#e6db74&#34;&gt;ALLOCATIONS&lt;/span&gt;: allocations { go doc }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this implementation of the lexer, here are the benchmark results on my machine:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        21.000  i/100ms
Calculating -------------------------------------
                        211.043  (± 0.9%) i/s -      1.071k in   5.075133s
{:ALLOCATIONS=&amp;gt;20745}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can do a little over 200 iterations per second, and tokenizing the document allocates a bit over 20k objects.&lt;/p&gt;
&lt;h2 id=&#34;stringscanner-context&#34;&gt;StringScanner context&lt;/h2&gt;
&lt;p&gt;Before we get to optimizing this lexer, lets get a little background on &lt;code&gt;StringScanner&lt;/code&gt;.
&lt;code&gt;StringScanner&lt;/code&gt; is one of my favorite utilities that ships with Ruby.
You can think of this object as basically a &amp;ldquo;cursor&amp;rdquo; that points inside a string.
When you successfully scan a token from the beginning of the cursor, &lt;code&gt;StringScanner&lt;/code&gt; will move the cursor forward.
If scanning fails, the cursor doesn&amp;rsquo;t move.&lt;/p&gt;
&lt;p&gt;The inspect method on the &lt;code&gt;StringScanner&lt;/code&gt; object makes this behavior very clear, so lets just look at some code in IRB:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; scanner = StringScanner.new(&amp;#34;the quick brown fox jumped over the lazy dog&amp;#34;)
=&amp;gt; #&amp;lt;StringScanner 0/44 @ &amp;#34;the q...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.scan(/the /)
=&amp;gt; &amp;#34;the &amp;#34;
&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 4/44 &amp;#34;the &amp;#34; @ &amp;#34;quick...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.scan(/qui/)
=&amp;gt; &amp;#34;qui&amp;#34;
&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 7/44 &amp;#34;...e qui&amp;#34; @ &amp;#34;ck br...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.scan(/hello/)
=&amp;gt; nil
&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 7/44 &amp;#34;...e qui&amp;#34; @ &amp;#34;ck br...&amp;#34;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; symbol in the inspect output shows where the cursor currently points, and the ratio at the beginning gives you kind of a &amp;ldquo;progress&amp;rdquo; counter.
As I scanned through the string, the cursor moved forward.
Near the end, you can see where I tried to scan &amp;ldquo;hello&amp;rdquo;, it returned &lt;code&gt;nil&lt;/code&gt;, and the cursor stayed in place.&lt;/p&gt;
&lt;p&gt;Combining &lt;code&gt;StringScanner&lt;/code&gt; with the linear &lt;code&gt;case / when&lt;/code&gt; in Ruby is a great combination for really easily writing tokenizers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;StringScanner&lt;/code&gt; also allows us to skip particular values, as well as ask for the current cursor position:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 7/44 &amp;#34;...e qui&amp;#34; @ &amp;#34;ck br...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.skip(/ck /)
=&amp;gt; 3
&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 10/44 &amp;#34;...uick &amp;#34; @ &amp;#34;brown...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.skip(/hello/)
=&amp;gt; nil
&amp;gt;&amp;gt; scanner
=&amp;gt; #&amp;lt;StringScanner 10/44 &amp;#34;...uick &amp;#34; @ &amp;#34;brown...&amp;#34;&amp;gt;
&amp;gt;&amp;gt; scanner.pos
=&amp;gt; 10
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Calling &lt;code&gt;skip&lt;/code&gt; will try to skip a pattern.
If skipping works, it returns the length of the string it matched, and if it fails, it returns &lt;code&gt;nil&lt;/code&gt;.
You can also get and set the position of the cursor using the &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;pos=&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;Now lets try to speed up this lexer!&lt;/p&gt;
&lt;h2 id=&#34;speeding-up-this-lexer&#34;&gt;Speeding up this lexer&lt;/h2&gt;
&lt;p&gt;The name of the game for speeding up lexers (or really any code) is to reduce the number of method calls as well as the number of allocations.
So we&amp;rsquo;re going to try applying some tricks to reduce both.&lt;/p&gt;
&lt;p&gt;Whenever I&amp;rsquo;m trying to improve the performance of any code, I find it is important to think about the context of how that code is used.
For example, our lexer currently yields tokens for comments and whitespace.
However, the GraphQL grammar ignores comments and whitespace.
Since the parser doesn&amp;rsquo;t actually need to know about whitespace or comments in order to understand the document, it is fine for the lexer to just skip them.&lt;/p&gt;
&lt;p&gt;Our first optimization is to combine the whitespace and comment check, and then quit returning tokens:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;diff --git a/test.rb b/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;index 2c1e874..9130a54 100644
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;--- a/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+++ b/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;@@ -2,8 +2,12 @@ require &amp;#34;strscan&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; class Lexer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   IDENTIFIER =    /[_A-Za-z][_0-9A-Za-z]*\b/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-  WHITESPACE =  %r{ [, \c\r\n\t]+ }x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-  COMMENTS   = %r{ \#.*$ }x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+  IGNORE   =       %r{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    (?:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+      [, \c\r\n\t]+ |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+      \#.*$
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    )*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+  }x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   INT =           /[-]?(?:[0]|[1-9][0-9]*)/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   FLOAT_DECIMAL = /[.][0-9]+/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   FLOAT_EXP =     /[eE][+-]?[0-9]+/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;@@ -51,11 +55,11 @@ class Lexer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   def next_token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    @scan.skip(IGNORE)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     return if @scan.eos?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     case
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    when s = @scan.scan(WHITESPACE)  then [:WHITESPACE, s]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    when s = @scan.scan(COMMENTS)    then [:COMMENT, s]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when s = @scan.scan(ELLIPSIS)    then [:ELLIPSIS, s]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when s = @scan.scan(PUNCTUATION) then [PUNCTUATION_TABLE[s], s]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when s = @scan.scan(KW_RE)       then [KW_TABLE[s], s]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By combining the whitespace and comment regex, we could eliminate one method call.
We also changed the &lt;code&gt;scan&lt;/code&gt; to a &lt;code&gt;skip&lt;/code&gt; which eliminated string object allocations.
Lets check the benchmark after this change:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        32.000  i/100ms
Calculating -------------------------------------
                        322.100  (± 0.3%) i/s -      1.632k in   5.066846s
{:ALLOCATIONS=&amp;gt;10527}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is great!  Our iterations per second (IPS) went from 211 to 322, and our allocations went from about 20k down to around 10k.
So we cut our allocations in half and increased speed by about 50%.&lt;/p&gt;
&lt;h2 id=&#34;thinking-bigger&#34;&gt;Thinking Bigger&lt;/h2&gt;
&lt;p&gt;This lexer returns a tuple for each token.
The tuple looks like this: &lt;code&gt;[:LPAREN, &amp;quot;(&amp;quot;]&lt;/code&gt;.
But when the parser looks at the token, how often does it actually need the string value of the token?&lt;/p&gt;
&lt;p&gt;When the parser looks at the first element, it is able to understand that the lexer found a left parenthesis just by looking at the symbol &lt;code&gt;:LPAREN&lt;/code&gt;.
The parser gets no benefit from the &lt;code&gt;&amp;quot;(&amp;quot;&lt;/code&gt; string that is in the tuple.&lt;/p&gt;
&lt;p&gt;Just by looking at the token name, the parser can tell what string the lexer found.
This is true for all punctuation, as well as keywords.&lt;/p&gt;
&lt;p&gt;Identifiers and numbers are a little bit different though.
The parser doesn&amp;rsquo;t particularly care about the actual string value of any identifier or number.
It only cares that an identifier or number was found.
However, if we think one level up, it&amp;rsquo;s quite likely that consumers of the parser will care what field name or number was in the GraphQL document.&lt;/p&gt;
&lt;p&gt;Since the parser doesn&amp;rsquo;t care about the actual token value, but the user &lt;em&gt;does&lt;/em&gt; care about the token value, lets split the &lt;code&gt;next_token&lt;/code&gt; method in two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One method to get the token (&lt;code&gt;:INT&lt;/code&gt;, &lt;code&gt;:LCURLY&lt;/code&gt;, etc)&lt;/li&gt;
&lt;li&gt;One method to get the token value&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the parser encounters a token where the token value &lt;em&gt;actually matters&lt;/em&gt;, the parser can ask the lexer for the token value.
For example, something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lexer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Lexer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lexer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;next_token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    p lexer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;token_value
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;__END__
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;mutation {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  a: likeStory(storyID: 12345) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    b: story {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;      c: likeCount
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This split buys us two really big wins.
The first is that &lt;code&gt;next_token&lt;/code&gt; doesn&amp;rsquo;t need to return an array anymore.
That&amp;rsquo;s already one object per token saved.
The second win is that &lt;em&gt;we only ever allocate a string when we really need it&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here is the new &lt;code&gt;next_token&lt;/code&gt; method, and the &lt;code&gt;token_value&lt;/code&gt; helper method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next_token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;IGNORE&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eos?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;ELLIPSIS&lt;/span&gt;)        &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:ELLIPSIS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt;)       &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;IDENTIFIER&lt;/span&gt;)      &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT&lt;/span&gt;)           &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:FLOAT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;INT&lt;/span&gt;)             &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:INT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;:UNKNOWN_CHAR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;token_value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @doc&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;byteslice(@scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matched_size, @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matched_size)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve changed the method to only return a symbol that identifies the token.
We also changed most &lt;code&gt;scan&lt;/code&gt; calls to &lt;code&gt;skip&lt;/code&gt; calls.
&lt;code&gt;scan&lt;/code&gt; will return the string it matched (an allocation), but &lt;code&gt;skip&lt;/code&gt; simply returns the length of the string it matched (not an allocation).&lt;/p&gt;
&lt;p&gt;As the parser requests tokens from the lexer, if it encounters a token where it actually cares about the string value, it just calls &lt;code&gt;token_value&lt;/code&gt;.
This makes our benchmark a little bit awkward now because we&amp;rsquo;ve shifted the blame of &amp;ldquo;identifier&amp;rdquo; allocations from the lexer to the parser.
If the parser wants an allocation, it&amp;rsquo;ll have to ask the lexer for it.
But lets keep pushing forward with the same benchmark (just remembering that once we integrate the lexer with the parser, we&amp;rsquo;ll have allocations for identifiers).&lt;/p&gt;
&lt;p&gt;With this change, our benchmark results look like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        35.000  i/100ms
Calculating -------------------------------------
                        360.209  (± 0.6%) i/s -      1.820k in   5.052764s
{:ALLOCATIONS=&amp;gt;1915}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We went from 322 IPS to 360 IPS, and from 10k allocations down to about 2k allocations.&lt;/p&gt;
&lt;h2 id=&#34;punctuation-lookup-table&#34;&gt;Punctuation Lookup Table&lt;/h2&gt;
&lt;p&gt;Unfortunately we&amp;rsquo;ve still got two lines in the tokenizer that are doing allocations:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    when s = @scan.scan(PUNCTUATION) then PUNCTUATION_TABLE[s]
    when s = @scan.scan(KW_RE)       then KW_TABLE[s]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s tackle the punctuation line first.
We still extract a string from the scanner in order to do a hash lookup to find the symbol name for the token.
We need the string &lt;code&gt;&amp;quot;)&amp;quot;&lt;/code&gt; so that we can map it to the symbol &lt;code&gt;:RPAREN&lt;/code&gt;.
One interesting feature about these punctuation characters is that they are all only one byte and thus limited to values between 0 - 255.
Instead of extracting a substring, we can get the byte at the current scanner position, then use the byte as an array index.
If there is a value at that index in the array, then we know we&amp;rsquo;ve found a token.&lt;/p&gt;
&lt;p&gt;First we&amp;rsquo;ll build the lookup table like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;constants&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each_with_object(&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;) { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;n, o&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    o&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Literals&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;const_get(n)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ord&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; n
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create an array.
The array will have a symbol at the index corresponding to the byte value of our punctuation.
Any other index will return &lt;code&gt;nil&lt;/code&gt;.
And since we&amp;rsquo;re only dealing with one byte, we know the maximum value can only ever be 255.
The code below gives us a sample of how this lookup table works:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;()ab&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bytes&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;byte&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    p &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;byte&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output is like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
:LPAREN
:RPAREN
nil
nil
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use the &lt;code&gt;pos&lt;/code&gt; method on the &lt;code&gt;StringScanner&lt;/code&gt; object to get our current cursor position (no allocation), then use that information to extract a byte from the string (also no allocation).
If the byte has a value in the lookup table, we know we&amp;rsquo;ve found a token and we can push the &lt;code&gt;StringScanner&lt;/code&gt; forward one byte.&lt;/p&gt;
&lt;p&gt;After incorporating the punctuation lookup table, our &lt;code&gt;next_token&lt;/code&gt; method looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next_token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;IGNORE&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eos?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;ELLIPSIS&lt;/span&gt;)        &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:ELLIPSIS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PUNCTUATION_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;@doc&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getbyte(@scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos)&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# If the byte at the current position is inside our lookup table, push&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# the scanner position forward 1 and return the token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      tok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt;)       &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;IDENTIFIER&lt;/span&gt;)      &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:IDENTIFIER&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;FLOAT&lt;/span&gt;)           &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:FLOAT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;INT&lt;/span&gt;)             &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:INT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;:UNKNOWN_CHAR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rerunning our benchmarks gives us these results:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        46.000  i/100ms
Calculating -------------------------------------
                        459.031  (± 1.1%) i/s -      2.300k in   5.011232s
{:ALLOCATIONS=&amp;gt;346}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ve gone from 360 IPS up to 459 IPS, and from about 2k allocations down to only 350 allocations.&lt;/p&gt;
&lt;h2 id=&#34;perfect-hashes-and-graphql-keywords&#34;&gt;Perfect Hashes and GraphQL Keywords&lt;/h2&gt;
&lt;p&gt;We have one more line in our lexer that is allocating objects:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scan(&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt;)       &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This line is allocating objects because it needs to map the keyword it found in the source to a symbol:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; Lexer::KW_TABLE[&amp;#34;query&amp;#34;]
=&amp;gt; :QUERY
&amp;gt;&amp;gt; Lexer::KW_TABLE[&amp;#34;schema&amp;#34;]
=&amp;gt; :SCHEMA
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It would be great if we had a hash table that didn&amp;rsquo;t require us to extract a string from the source document.
And that&amp;rsquo;s exactly what we&amp;rsquo;re going to build.&lt;/p&gt;
&lt;p&gt;When this particular regular expression matches, we know that the lexer has found 1 of the 19 keywords listed in the &lt;code&gt;KW_TABLE&lt;/code&gt;, we just don&amp;rsquo;t know which one.
What we&amp;rsquo;d like to do is figure out &lt;em&gt;which&lt;/em&gt; keyword matched, and do it without allocating any objects.&lt;/p&gt;
&lt;p&gt;Here is the list of 19 GraphQL keywords we could possibly match:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[&amp;#34;on&amp;#34;,
 &amp;#34;true&amp;#34;,
 &amp;#34;null&amp;#34;,
 &amp;#34;enum&amp;#34;,
 &amp;#34;type&amp;#34;,
 &amp;#34;input&amp;#34;,
 &amp;#34;false&amp;#34;,
 &amp;#34;query&amp;#34;,
 &amp;#34;union&amp;#34;,
 &amp;#34;extend&amp;#34;,
 &amp;#34;scalar&amp;#34;,
 &amp;#34;schema&amp;#34;,
 &amp;#34;mutation&amp;#34;,
 &amp;#34;fragment&amp;#34;,
 &amp;#34;interface&amp;#34;,
 &amp;#34;directive&amp;#34;,
 &amp;#34;implements&amp;#34;,
 &amp;#34;repeatable&amp;#34;,
 &amp;#34;subscription&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;StringScanner#skip&lt;/code&gt; will return the length of the match, and we know that if the length is 2 we unambiguously matched &lt;code&gt;on&lt;/code&gt;, and if the length is 12 we unambiguously matched &lt;code&gt;subscription&lt;/code&gt;.
So if the matched length is 2 or 12, we can just return &lt;code&gt;:ON&lt;/code&gt; or &lt;code&gt;:SUBSCRIPTION&lt;/code&gt;.
That leaves 17 other keywords we need to disambiguate.&lt;/p&gt;
&lt;p&gt;Of the 17 remaining keywords, the 2nd and 3rd bytes uniquely identify that keyword:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; (Lexer::KW_TABLE.keys - [&amp;#34;on&amp;#34;, &amp;#34;subscription&amp;#34;]).length
=&amp;gt; 17
&amp;gt;&amp;gt; (Lexer::KW_TABLE.keys - [&amp;#34;on&amp;#34;, &amp;#34;subscription&amp;#34;]).map { |w| w[1, 2] }
=&amp;gt; [&amp;#34;ra&amp;#34;, &amp;#34;ru&amp;#34;, &amp;#34;al&amp;#34;, &amp;#34;ul&amp;#34;, &amp;#34;ue&amp;#34;, &amp;#34;ut&amp;#34;, &amp;#34;ch&amp;#34;, &amp;#34;ca&amp;#34;, &amp;#34;yp&amp;#34;, &amp;#34;xt&amp;#34;, &amp;#34;mp&amp;#34;, &amp;#34;nt&amp;#34;, &amp;#34;ni&amp;#34;, &amp;#34;nu&amp;#34;, &amp;#34;np&amp;#34;, &amp;#34;ir&amp;#34;, &amp;#34;ep&amp;#34;]
&amp;gt;&amp;gt; (Lexer::KW_TABLE.keys - [&amp;#34;on&amp;#34;, &amp;#34;subscription&amp;#34;]).map { |w| w[1, 2] }.uniq.length
=&amp;gt; 17
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use these two bytes as a key to a hash table and design a &lt;a href=&#34;https://en.wikipedia.org/wiki/Perfect_hash_function&#34;&gt;&amp;ldquo;perfect hash&amp;rdquo;&lt;/a&gt; to look up the right token.
A perfect hash is a hash table where the possible keys for the hash are &lt;em&gt;known in advance&lt;/em&gt;, and the hashing function will &lt;em&gt;never make a collision&lt;/em&gt;.
In other words, no two hash keys will result in the same bucket index.&lt;/p&gt;
&lt;p&gt;We know that the word we found is one of a limited set, so this seems like a good application for a perfect hash.&lt;/p&gt;
&lt;h3 id=&#34;building-a-perfect-hash&#34;&gt;Building a Perfect Hash&lt;/h3&gt;
&lt;p&gt;A perfect hash function uses a pre-computed &amp;ldquo;convenient&amp;rdquo; constant that let us uniquely identify each key, but also limit the hash table to a small size.
Basically we have a function like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_hash&lt;/span&gt; key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (key &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SOME_CONSTANT&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x1f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But we must figure out the right constant to use such that each entry in our perfect hash gets a unique bucket index.
We&amp;rsquo;re going to use the upper 5 bits of a &amp;ldquo;32 bit integer&amp;rdquo; (it&amp;rsquo;s not actually 32 bits, we&amp;rsquo;re just going to treat it that way) to find our hash key.
The reason we&amp;rsquo;re going to use 5 bits is because we have 17 keys, and 17 can&amp;rsquo;t fit in 4 bits.
To find the value of &lt;code&gt;SOME_CONSTANT&lt;/code&gt;, we&amp;rsquo;re just going to use a brute force method.&lt;/p&gt;
&lt;p&gt;First lets convert the two bytes from each GraphQL keyword to a 16 bit integer:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; keys = (Lexer::KW_TABLE.keys - [&amp;#34;on&amp;#34;, &amp;#34;subscription&amp;#34;]).map { |w| w[1, 2].unpack1(&amp;#34;s&amp;#34;) }
=&amp;gt; [24946, 30066, 27745, 27765, 25973, 29813, 26723, 24931, 28793, 29816, 28781, 29806, 26990, 30062, 28782, 29289, 28773]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next we&amp;rsquo;re going to use a brute force method to find a constant value such that we can convert these 16 bit numbers in to unique 5 bit numbers:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; c = 13
=&amp;gt; 13
?&amp;gt; loop do
?&amp;gt;   z = keys.map { |k| ((k * c) &amp;gt;&amp;gt; 27) &amp;amp; 0x1f }
?&amp;gt;   break if z.uniq.length == z.length
?&amp;gt;   c += 1
&amp;gt;&amp;gt; end
=&amp;gt; nil
&amp;gt;&amp;gt; c
=&amp;gt; 18592990
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We start our search at 13.
Our loop tries applying the hashing function to all keys.
If the hashing function returns unique values for all keys, then we found the right value for &lt;code&gt;c&lt;/code&gt;, otherwise we increment &lt;code&gt;c&lt;/code&gt; by one and try the next number.&lt;/p&gt;
&lt;p&gt;After this loop finishes (it takes a while), we check &lt;code&gt;c&lt;/code&gt; and that&amp;rsquo;s the value for our perfect hash!&lt;/p&gt;
&lt;p&gt;Now we can write our hashing function like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_hash&lt;/span&gt; key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (key &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;18592990&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x1f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function will return a unique value based on the 2nd and 3rd bytes of each GraphQL keyword.
Lets prove that to ourselves in IRB:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;?&amp;gt; def _hash key
?&amp;gt;   (key * 18592990) &amp;gt;&amp;gt; 27 &amp;amp; 0x1f
&amp;gt;&amp;gt; end
=&amp;gt; :_hash
&amp;gt;&amp;gt; keys = (Lexer::KW_TABLE.keys - [&amp;#34;on&amp;#34;, &amp;#34;subscription&amp;#34;]).map { |w| w[1, 2].unpack1(&amp;#34;s&amp;#34;) }
=&amp;gt; [24946, 30066, 27745, 27765, 25973, 29813, 26723, 24931, 28793, 29816, 28781, 29806, 26990, 30062, 28782, 29289, 28773]
&amp;gt;&amp;gt; keys.map { |key| _hash(key) }
=&amp;gt; [31, 5, 3, 6, 14, 1, 21, 29, 20, 2, 18, 0, 26, 4, 19, 25, 17]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ll use these integers as an index in to an array that stores the symbol name associated with that particular keyword:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; # Get a list of the array indices for each keyword
=&amp;gt; nil
&amp;gt;&amp;gt; array_indexes = keys.map { |key| _hash(key) }
=&amp;gt; [31, 5, 3, 6, 14, 1, 21, 29, 20, 2, 18, 0, 26, 4, 19, 25, 17]
&amp;gt;&amp;gt; # Insert a symbol in to an array at each index
=&amp;gt; nil
&amp;gt;&amp;gt; table = kws.zip(array_indexes).each_with_object([]) { |(kw, key),o| o[key] = kw.upcase.to_sym }
=&amp;gt; 
[:INTERFACE,
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we have a table we can use to look up the symbol for a particular keyword given the keyword&amp;rsquo;s 2nd and 3rd bytes.&lt;/p&gt;
&lt;h3 id=&#34;take-a-breather&#34;&gt;Take a breather&lt;/h3&gt;
&lt;p&gt;I think this is getting a little complicated so I want to step back and take a breather.
What we&amp;rsquo;ve done so far is write a function that, given the 2nd and 3rd bytes of a string returns an index to an array.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take the keyword &lt;code&gt;interface&lt;/code&gt; as an example.
The 2nd and 3rd bytes are &lt;code&gt;nt&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; &amp;#34;interface&amp;#34;[1, 2]
=&amp;gt; &amp;#34;nt&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use &lt;code&gt;unpack1&lt;/code&gt; to convert &lt;code&gt;nt&lt;/code&gt; in to a 16 bit integer:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; &amp;#34;interface&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;)
=&amp;gt; 29806
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we pass that integer to our hashing function (I called it &lt;code&gt;_hash&lt;/code&gt; in IRB):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; _hash(&amp;#34;interface&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;))
=&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now we have the array index where to find the &lt;code&gt;:INTERFACE&lt;/code&gt; symbol:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; table[_hash(&amp;#34;interface&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;))]
=&amp;gt; :INTERFACE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will work for any of the strings we used to build the perfect hash function.
Lets try a few:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; table[_hash(&amp;#34;union&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;))]
=&amp;gt; :UNION
&amp;gt;&amp;gt; table[_hash(&amp;#34;scalar&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;))]
=&amp;gt; :SCALAR
&amp;gt;&amp;gt; table[_hash(&amp;#34;repeatable&amp;#34;[1, 2].unpack1(&amp;#34;s&amp;#34;))]
=&amp;gt; :REPEATABLE
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;integrating-the-perfect-hash-in-to-the-lexer&#34;&gt;Integrating the Perfect Hash in to the Lexer&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve built our hash table and hash function, so the next step is to add them to the lexer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:INTERFACE&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:MUTATION&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:EXTEND&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:FALSE&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:ENUM&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:TRUE&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:QUERY&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:REPEATABLE&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;:IMPLEMENTS&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:INPUT&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:TYPE&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:SCHEMA&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:DIRECTIVE&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;:UNION&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:SCALAR&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:FRAGMENT&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_hash&lt;/span&gt; key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (key &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;18592990&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x1f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember we derived the magic constant &lt;code&gt;18592990&lt;/code&gt; earlier via brute force.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;next_token&lt;/code&gt; method, we need to extract the 2nd and 3rd bytes of the keyword, combine them to a 16 bit int, use the &lt;code&gt;_hash&lt;/code&gt; method to convert the 16 bit int to a 5 bit array index, then look up the symbol (I&amp;rsquo;ve omitted the rest of the &lt;code&gt;next_token&lt;/code&gt; method):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;skip(&lt;span style=&#34;color:#66d9ef&#34;&gt;KW_RE&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Return early if uniquely identifiable via length&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:ON&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:SUBSCRIPTION&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Get the position of the start of the keyword in the main document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @scan&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; len
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Get the 2nd and 3rd byte of the keyword and combine to a 16 bit int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (@doc&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getbyte(start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; @doc&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getbyte(start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Get the array index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      index &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; _hash(key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Return the symbol&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;KW_TABLE&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;index&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We know the length of the token because it&amp;rsquo;s the return value of &lt;code&gt;StringScanner#skip&lt;/code&gt;.
If the token is uniquely identifiable based on its length, then we&amp;rsquo;ll return early.
Otherwise, ask &lt;code&gt;StringScanner&lt;/code&gt; for the cursor position and then use the length to calculate the index of the beginning of the token (remember &lt;code&gt;StringScanner&lt;/code&gt; pushed the cursor forward when &lt;code&gt;skip&lt;/code&gt; matched).&lt;/p&gt;
&lt;p&gt;Once we have the beginning of the token, we&amp;rsquo;ll use &lt;code&gt;getbyte&lt;/code&gt; (which doesn&amp;rsquo;t allocate) to get the 2nd and 3rd bytes of the keyword.
Then we&amp;rsquo;ll combine the two bytes to a 16 bit int.
Finally we pass the int to the hash function and use the return value of the hash function to look up the token symbol in the array.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s check our benchmarks now!&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        46.000  i/100ms
Calculating -------------------------------------
                        468.978  (± 0.4%) i/s -      2.346k in   5.002449s
{:ALLOCATIONS=&amp;gt;3}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We went from 459 IPS up to 468 IPS, and from 346 allocations down to 3 allocations.
1 allocation for the Lexer object, 1 allocation for the &lt;code&gt;StringScanner&lt;/code&gt; object, and 1 allocation for ????&lt;/p&gt;
&lt;p&gt;Actually, if we run the allocation benchmark twice we&amp;rsquo;ll get different results:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;benchmark/ips&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;allocations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;GC&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stat(&lt;span style=&#34;color:#e6db74&#34;&gt;:total_allocated_objects&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;go&lt;/span&gt; doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lexer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Lexer&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new doc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; tok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lexer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;next_token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;doc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGF&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Benchmark&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ips { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;x&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; x&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;report { go doc } }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p &lt;span style=&#34;color:#e6db74&#34;&gt;ALLOCATIONS&lt;/span&gt;: allocations { go doc }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p &lt;span style=&#34;color:#e6db74&#34;&gt;ALLOCATIONS&lt;/span&gt;: allocations { go doc }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output is this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        46.000  i/100ms
Calculating -------------------------------------
                        465.071  (± 0.6%) i/s -      2.346k in   5.044626s
{:ALLOCATIONS=&amp;gt;3}
{:ALLOCATIONS=&amp;gt;2}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ruby uses GC allocated objects to store some inline caches.
Since it was the first time we called the &lt;code&gt;allocations&lt;/code&gt; method, a new inline cache was allocated, and that dinged us.
We&amp;rsquo;re actually able to tokenize this entire document with only 2 allocations: the lexer and the string scanner.&lt;/p&gt;
&lt;h2 id=&#34;one-more-hack&#34;&gt;One more hack&lt;/h2&gt;
&lt;p&gt;Lets do one more trick.
We want to reduce the number of method calls the scanner makes as much as we can.
The &lt;code&gt;case / when&lt;/code&gt; statement in &lt;code&gt;next_token&lt;/code&gt; checks each &lt;code&gt;when&lt;/code&gt; statement one at a time.
One trick I like to do is rearrange the statements so that the most popular tokens come first.&lt;/p&gt;
&lt;p&gt;If we tokenize our benchmark program and tally up all of the tokens that come out, it looks like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt; lexer = Lexer.new File.read &amp;#34;benchmark/fixtures/negotiate.gql&amp;#34;
=&amp;gt; 
#&amp;lt;Lexer:0x0000000105c33c90
...
&amp;gt;&amp;gt; list = []
=&amp;gt; []
?&amp;gt; while tok = lexer.next_token
?&amp;gt;   list &amp;lt;&amp;lt; tok
&amp;gt;&amp;gt; end
=&amp;gt; nil
&amp;gt;&amp;gt; list.tally
=&amp;gt; {:QUERY=&amp;gt;1, :IDENTIFIER=&amp;gt;2976, :LPAREN=&amp;gt;15, :VAR_SIGN=&amp;gt;6, :COLON=&amp;gt;56, :BANG=&amp;gt;1,
    :RPAREN=&amp;gt;15, :LCURLY=&amp;gt;738, :RCURLY=&amp;gt;738, :ELLIPSIS=&amp;gt;350, :ON=&amp;gt;319, :INT=&amp;gt;24,
    :TYPE=&amp;gt;4, :INPUT=&amp;gt;1, :FRAGMENT=&amp;gt;18}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From this data, it looks like &lt;code&gt;ELLIPSIS&lt;/code&gt; tokens aren&amp;rsquo;t as popular as punctuation or &lt;code&gt;IDENTIFIER&lt;/code&gt; tokens.
Yet we&amp;rsquo;re always checking for &lt;code&gt;ELLIPSIS&lt;/code&gt; tokens first.
Lets move the &lt;code&gt;ELLIPSIS&lt;/code&gt; check below the identifier check.
This makes looking for &lt;code&gt;ELLIPSIS&lt;/code&gt; more expensive, but it makes finding punctuation and identifiers cheaper.
Since punctuation and identifiers occur more frequently in our document, we should get a speedup.&lt;/p&gt;
&lt;p&gt;I applied this patch:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;diff --git a/test.rb b/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;index ac147c2..275b8ba 100644
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;--- a/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+++ b/test.rb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;@@ -59,7 +59,6 @@ class Lexer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     return if @scan.eos?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     case
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    when @scan.skip(ELLIPSIS)        then :ELLIPSIS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when tok = PUNCTUATION_TABLE[@doc.getbyte(@scan.pos)] then
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       # If the byte at the current position is inside our lookup table, push
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       # the scanner position forward 1 and return the token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;@@ -78,6 +77,7 @@ class Lexer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       KW_TABLE[_hash(key)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when @scan.skip(IDENTIFIER)      then :IDENTIFIER
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    when @scan.skip(ELLIPSIS)        then :ELLIPSIS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when @scan.skip(FLOAT)           then :FLOAT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     when @scan.skip(INT)             then :INT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     else
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when we rerun the benchmark, we get this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ruby -I lib test.rb benchmark/fixtures/negotiate.gql
Warming up --------------------------------------
                        48.000  i/100ms
Calculating -------------------------------------
                        486.798  (± 0.4%) i/s -      2.448k in   5.028884s
{:ALLOCATIONS=&amp;gt;3}
{:ALLOCATIONS=&amp;gt;2}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Great, we went from 465 IPS to 486 IPS!&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The lexer we started with tokenized the 80kb GraphQL document at 211 IPS, and where we left off it was running at 486 IPS.
More than a 2x speed improvement!&lt;/p&gt;
&lt;p&gt;Our starting lexer allocated over 20k objects, and when we finished we got it down to just 2 objects.
Of course the parser may ask the lexer to allocate something, but we know that we&amp;rsquo;re only allocating the &lt;em&gt;bare minimum&lt;/em&gt;.
In fact, if the parser only records positional offsets, it could very well never ask the lexer to allocate anything!&lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;m doing this stuff I try to use as many tricks as possible for increasing speed.
But I think the biggest and best payoffs come from trying to think about the problem from a higher level and adjust the problem space itself.
Converting &lt;code&gt;next_token&lt;/code&gt; to return only a symbol rather than a tuple cut our object allocations by more than half.
Questioning the code&amp;rsquo;s design itself is much harder, but I think reaps a greater benefit.&lt;/p&gt;
&lt;p&gt;Anyway, these are hacks I like to use!
If you want to play around with the lexer we&amp;rsquo;ve been building in this post, I&amp;rsquo;ve put the source code &lt;a href=&#34;https://gist.github.com/tenderlove/e9bb912648a3d2bce00c4f60bc632a10&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you enjoyed this, and have a good day!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Bitmap Matrix and Undirected Graphs in Ruby</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2023/03/19/bitmap-matrix-and-undirected-graphs-in-ruby/"/>
    <id>https://tenderlovemaking.com/2023/03/19/bitmap-matrix-and-undirected-graphs-in-ruby/</id>
    <published>2023-03-19T12:12:27-07:00</published>
    <updated>2023-03-19T12:12:27-07:00</updated>
    <content type="html">&lt;p&gt;I&amp;rsquo;ve been working my way through &lt;a href=&#34;https://www.elsevier.com/books/engineering-a-compiler/cooper/978-0-12-815412-0&#34;&gt;Engineering a Compiler&lt;/a&gt;.
I really enjoy the book, but one part has you build an interference graph for doing register allocation via graph coloring.
An interference graph is an &lt;a href=&#34;https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)&#34;&gt;undirected graph&lt;/a&gt;, and one way you can represent an undirected graph is with a bitmap matrix.&lt;/p&gt;
&lt;p&gt;A bitmap matrix is just a matrix but the values in the matrix can only be 1 or 0.
If every node in your graph maps to an index, you can use the bitmap matrix to represent edges in the graph.&lt;/p&gt;
&lt;p&gt;I made a bitmap matrix implementation that I like, but I think the code is too trivial to put in a Gem.
Here is the code I used:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BitMatrix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt; size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (size &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# round up to the nearest multiple of 8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @row_bytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; size &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @buffer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\0&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;b &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (@row_bytes &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; size)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize_copy&lt;/span&gt; other
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @buffer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @buffer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt; x, y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;IndexError&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; @size &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; @size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x, y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;y, x&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;sort
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    row &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; @row_bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    column_byte &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    column_bit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @buffer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setbyte(row &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; column_byte, @buffer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getbyte(row &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; column_byte) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; column_bit)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;set?&lt;/span&gt; x, y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;IndexError&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; @size &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; @size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x, y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;y, x&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;sort
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    row &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; @row_bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    column_byte &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    column_bit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (@buffer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getbyte(row &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; column_byte) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; column_bit) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;each_pair&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; enum_for(&lt;span style=&#34;color:#e6db74&#34;&gt;:each_pair&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;unless&lt;/span&gt; block_given?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @buffer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bytes&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each_with_index &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;byte, i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      row &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; @row_bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      column &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; @row_bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;j&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; j) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; byte &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;row, (column &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; j&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;to_dot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;graph g {&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; each_pair&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;x, y&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;x&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; -- &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;y&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;;&amp;#34;&lt;/span&gt; }&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I like this implementation because all bits are packed in to a binary string.
Copying the matrix is trivial because we just have to dup the string.
Memory usage is much smaller than if every node in the graph were to store an actual reference to other nodes.&lt;/p&gt;
&lt;p&gt;Anyway, this was fun to write and I hope someone finds it useful!&lt;/p&gt;
</content>
  </entry>

  
    <entry>
    <title>Vim, tmux, and Fish</title>
    <link rel="alternate" href="https://tenderlovemaking.com/2023/01/18/vim-tmux-and-fish/"/>
    <id>https://tenderlovemaking.com/2023/01/18/vim-tmux-and-fish/</id>
    <published>2023-01-18T11:23:35-08:00</published>
    <updated>2023-01-18T11:23:35-08:00</updated>
    <content type="html">&lt;p&gt;I do most of my text editing with &lt;a href=&#34;https://github.com/macvim-dev/macvim&#34;&gt;MacVim&lt;/a&gt;, but when I pair with people I like to use &lt;a href=&#34;https://tmate.io&#34;&gt;tmate&lt;/a&gt;.
tmate is just an easy way to connect tmux sessions with a remote person.
But this means that I go from coding in a GUI to coding in a terminal.
Normally this wouldn&amp;rsquo;t be a problem, but I had made a Fish alias that would open the MacVim GUI every time I typed &lt;code&gt;vim&lt;/code&gt; in the terminal.
Of course when I&amp;rsquo;m pairing via tmate, the other people cannot see the GUI, so I would have to remember a different command to open Vim.&lt;/p&gt;
&lt;p&gt;Today I did about 10min of research to fix this problem and came up with the following Fish command:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cat .config/fish/functions/vim.fish 
function vim --wraps=&amp;#39;vim&amp;#39; --description &amp;#39;open Vim&amp;#39;
  if set -q TMUX # if we&amp;#39;re in a TMUX session, open in terminal
    command vim $argv
  else
    # Otherwise open macvim
    open -a MacVim $argv; 
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All it does is open terminal Vim if I&amp;rsquo;m in a TMUX session, otherwise it opens the MacVim GUI.&lt;/p&gt;
&lt;p&gt;Instead of putting up with this frustration for such a long time, I should have taken the 10 min required to fix the situation.
This was a good reminder for me, and hopefully I&amp;rsquo;ll be better about it in the future!&lt;/p&gt;
</content>
  </entry>

  
</feed>
