<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>zihan jin</title>
    <subtitle>notes, essays, and writing.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://zihanjin.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://zihanjin.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-15T00:00:00+00:00</updated>
    <id>https://zihanjin.com/atom.xml</id>
    <entry xml:lang="en">
        <title>from 0.56× to 2.10×: debugging a struct-of-arrays enum bottleneck</title>
        <published>2026-09-15T00:00:00+00:00</published>
        <updated>2026-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>zihan</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zihanjin.com/writings/debugging-a-struct-of-arrays-enum-bottleneck/"/>
        <id>https://zihanjin.com/writings/debugging-a-struct-of-arrays-enum-bottleneck/</id>
        
        <content type="html" xml:base="https://zihanjin.com/writings/debugging-a-struct-of-arrays-enum-bottleneck/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/zjom/marl&quot;&gt;marl github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;iterating over enums in &lt;code&gt;marl&lt;/code&gt; ran at half the speed of a plain &lt;code&gt;Vec&lt;/code&gt;, even though the data layout was already faster. the cause was one pointer computation in the wrong place, and how llvm orders its passes around it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;enum/scale_circles&lt;/code&gt; at n = 100,000, measured against &lt;code&gt;Vec&amp;lt;Shape&amp;gt;&lt;/code&gt;: &lt;strong&gt;0.56× → 2.10×&lt;/strong&gt;. above 1× means marl is faster.&lt;/p&gt;
&lt;p&gt;apple m1 pro · rustc 1.98.1 · llvm 22.1.8 · criterion 0.8 · release profile&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-marl-stores-an-enum&quot;&gt;how marl stores an enum&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;marl&lt;/code&gt; is a rust implementation of zig&#39;s &lt;code&gt;std.MultiArrayList&lt;/code&gt;: a growable &lt;em&gt;struct-of-arrays&lt;/em&gt; list. each field of &lt;code&gt;T&lt;/code&gt; gets its own column, and all columns share a single allocation. a loop that touches one field reads only that field&#39;s bytes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#[derive(MultiArrayList)]&lt;/code&gt; generates everything needed to access the columns type-safely. enums are supported the way zig supports a &lt;code&gt;union(enum)&lt;/code&gt;: a list of enums is stored as two columns, &lt;code&gt;tags&lt;/code&gt; with the active variant and &lt;code&gt;data&lt;/code&gt; with the payload minus its tag. iterating yields matchable reference enums such as &lt;code&gt;ShapeRefMut::Circle { r: &amp;amp;mut f32 }&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;here is the enum used by &lt;code&gt;benches/enum.rs&lt;/code&gt;. variants are scrambled so the branch predictor can&#39;t learn their order.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;enum&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Shape&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    Rect&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    Triangle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;f32&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    Empty&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;stored two ways, with capacity 4 (&lt;code&gt;T&lt;/code&gt; is a tag, &lt;code&gt;D&lt;/code&gt; is a payload):&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Vec&amp;lt;Shape&amp;gt;                28 bytes per element, tag inline&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[T|DDDDDD] [T|DDDDDD] [T|DDDDDD] [T|DDDDDD]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MultiArrayList&amp;lt;Shape&amp;gt;     one allocation, columns ordered by alignment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[DDDDDD][DDDDDD][DDDDDD][DDDDDD] [T][T][T][T]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;^ ptr + cap × 0                  ^ ptr + cap × 24&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;each column starts at &lt;code&gt;ptr + cap × PREFIX[i]&lt;/code&gt;, where &lt;code&gt;PREFIX&lt;/code&gt; is a per-type constant. keep an eye on that multiply.&lt;/p&gt;
&lt;h2 id=&quot;one-benchmark-ran-backwards&quot;&gt;one benchmark ran backwards&lt;/h2&gt;
&lt;p&gt;the criterion suite compares every operation against the same data in a &lt;code&gt;Vec&amp;lt;Shape&amp;gt;&lt;/code&gt;. most enum results looked as expected: &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;count_variant&lt;/code&gt; were comfortably faster, since counting a variant only has to scan the 1-byte tag column. one benchmark was not. &lt;code&gt;enum/scale_circles&lt;/code&gt; mutates the payload of one variant in place, and the two loops are the same code apart from the type names.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;// Vec&amp;lt;Shape&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;for&lt;/span&gt;&lt;span&gt; shape&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;iter_mut&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if let&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Shape&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; shape&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;        *&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *=&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0001&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;// MultiArrayList&amp;lt;Shape&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;for&lt;/span&gt;&lt;span&gt; shape&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;iter_mut&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if let&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; shape&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;        *&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *=&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0001&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;before the fix (speedup is &lt;code&gt;Vec&lt;/code&gt; time divided by marl time):&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;n = 1,000&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;n = 100,000&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;n = 1,000,000&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/push&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.35×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.36×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.06×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/count_variant&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.74×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.80×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.03×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/total_area&lt;/code&gt; (iter)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.99×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.99×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.01×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/scale_circles&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.52×&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.56×&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.60×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;the struct-of-arrays layout makes the tag scan in &lt;code&gt;scale_circles&lt;/code&gt; cheaper, yet marl took almost twice as long.&lt;/p&gt;
&lt;h2 id=&quot;finding-it&quot;&gt;finding it&lt;/h2&gt;
&lt;p&gt;criterion reports how long a whole benchmark takes, not why. to see what the compiler was doing, the loops were copied into a small standalone crate, each in its own &lt;code&gt;#[inline(never)]&lt;/code&gt; function, so they could be timed per element and disassembled one at a time.&lt;/p&gt;
&lt;h3 id=&quot;1-separate-the-layout-from-the-abstraction&quot;&gt;1. separate the layout from the abstraction&lt;/h3&gt;
&lt;p&gt;alongside the two benchmark loops, a third variant skipped the iterator entirely and walked the raw &lt;code&gt;tags&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; columns by hand. if the layout itself were the problem, it would be slow too.&lt;/p&gt;
&lt;p&gt;time per element, n = 1,000. sorting the input makes every branch predictable.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;loop&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;scrambled variants&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;sorted by variant&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Vec&amp;lt;Shape&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.66 ns&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.36 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;marl &lt;code&gt;iter_mut()&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.16 ns&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.69 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;marl raw columns&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.25 ns&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.19 ns&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;[METHOD NEEDED: how many runs behind each probe figure, and what the spread was. step 4 leans on a 1.17 vs 0.26 ns split, which is wide enough to survive almost any noise — but say so rather than making the reader assume it.]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the layout was fine:&lt;/strong&gt; the raw columns ran 2.6× faster than &lt;code&gt;Vec&lt;/code&gt;. the time went into the iterator path. and since that path stayed slow on sorted input, branch misprediction couldn&#39;t be the main cost.&lt;/p&gt;
&lt;h3 id=&quot;2-read-the-machine-code&quot;&gt;2. read the machine code&lt;/h3&gt;
&lt;p&gt;the &lt;code&gt;Vec&lt;/code&gt; loop compiles to what you would write by hand: load the tag, skip unless it&#39;s &lt;code&gt;Circle&lt;/code&gt;, scale.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;asm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; Vec&amp;lt;Shape&amp;gt;, per element&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ldur  w10, [x9, #-&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x4&lt;/span&gt;&lt;span&gt;]   &lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; load tag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cbnz  w10, next          &lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; not Circle: skip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ldr   s1, [x9]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;fmul&lt;/span&gt;&lt;span&gt;  s1, s1, s0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;str&lt;/span&gt;&lt;span&gt;   s1, [x9]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x9, x9, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x1c&lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      ; +28 bytes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the marl loop compares the tag against every variant, and for &lt;code&gt;Rect&lt;/code&gt; and &lt;code&gt;Triangle&lt;/code&gt; it computes payload pointers and writes them to the stack. nothing ever reads those stack slots again.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;asm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; marl iter_mut(), per element&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ldrb  w14, [x12], #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;   ; load tag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cmp&lt;/span&gt;&lt;span&gt;   w14, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b.gt  tri_or_empty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cbz   w14, circle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rect&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x14, &lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sp&lt;/span&gt;&lt;span&gt;, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x15, &lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sp&lt;/span&gt;&lt;span&gt;, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x18&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mov&lt;/span&gt;&lt;span&gt;   w17, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mov&lt;/span&gt;&lt;span&gt;   x16, x13&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b     store&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;tri_or_empty&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cmp&lt;/span&gt;&lt;span&gt;   w14, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b.ne  next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;madd  x14, x9, x11, x10  &lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; idx * 24 + base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x16, x14, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x14, &lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sp&lt;/span&gt;&lt;span&gt;, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x15, &lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sp&lt;/span&gt;&lt;span&gt;, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mov&lt;/span&gt;&lt;span&gt;   w17, #&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0x10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;add&lt;/span&gt;&lt;span&gt;   x17, x13, x17&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;str&lt;/span&gt;&lt;span&gt;   x16, [x15]         &lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; dead write to stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;str&lt;/span&gt;&lt;span&gt;   x17, [x14]         &lt;/span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;; dead write to stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b     next&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;aarch64 disassembly, trimmed to the loop body. &lt;code&gt;circle&lt;/code&gt; does the same load/multiply/store as the &lt;code&gt;Vec&lt;/code&gt; version.&lt;/p&gt;
&lt;h3 id=&quot;3-ask-llvm-why-the-writes-survive&quot;&gt;3. ask llvm why the writes survive&lt;/h3&gt;
&lt;p&gt;the generated &lt;code&gt;get_mut&lt;/code&gt; matches on the tag and builds a &lt;code&gt;ShapeRefMut&lt;/code&gt;; the caller&#39;s &lt;code&gt;if let&lt;/code&gt; then matches on that. after inlining, llvm should fold the two matches into one tag test and delete the fields nobody reads. the ir showed why it didn&#39;t:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;llvm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%.sink6.i.sroa.phi = &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;phi&lt;/span&gt;&lt;span&gt; ptr [ %_13.sroa.10, %bb3.i ], [ %_13.sroa.7, %bb5 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%.sink4.ph.i.sroa.phi = &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;phi&lt;/span&gt;&lt;span&gt; ptr [ %_13.sroa.12, %bb3.i ], [ %_13.sroa.10, %bb5 ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%__marl_field_1.i = &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;getelementptr inbounds nuw i8&lt;/span&gt;&lt;span&gt;, ptr %data.i, &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;i64&lt;/span&gt;&lt;span&gt; %.sink&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;store&lt;/span&gt;&lt;span&gt; ptr %data.sink.i, ptr %.sink6.i.sroa.phi, &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;align&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;store&lt;/span&gt;&lt;span&gt; ptr %__marl_field_1.i, ptr %.sink4.ph.i.sroa.phi, &lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;align&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;each &lt;code&gt;%_13.sroa.*&lt;/code&gt; is a stack slot holding one field of the &lt;code&gt;ShapeRefMut&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;the &lt;code&gt;Rect&lt;/code&gt; and &lt;code&gt;Triangle&lt;/code&gt; arms end in similar code, so simplifycfg merged their tails into one block, which is what the &lt;code&gt;.sink&lt;/code&gt; names record. after the merge, the &lt;em&gt;destination&lt;/em&gt; of each store is a &lt;code&gt;phi&lt;/code&gt; that picks between stack slots. mem2reg and dead-store elimination only handle stores to a known slot. so these stay. because the slots are still written, the &lt;code&gt;ShapeRefMut&lt;/code&gt; is never fully eliminated, and the four-way &lt;code&gt;switch&lt;/code&gt; survives into the loop.&lt;/p&gt;
&lt;h3 id=&quot;4-bisect-the-generated-code&quot;&gt;4. bisect the generated code&lt;/h3&gt;
&lt;p&gt;hand-written copies of the iterator narrowed it down. a replica that took &lt;em&gt;ready-made&lt;/em&gt; column pointers and ran the identical &lt;code&gt;match&lt;/code&gt; was as fast as the raw loop. plain &lt;code&gt;list.get_mut(i)&lt;/code&gt; in an index loop, with no iterator at all, was as slow as &lt;code&gt;iter_mut()&lt;/code&gt;. the common factor was &lt;code&gt;Soa::get_mut&lt;/code&gt; itself, and the one real difference was where it computed the column pointers:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;// columns computed per element: 1.17 ns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;..&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    let&lt;/span&gt;&lt;span&gt; tags&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; ptr&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;cap&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; PREFIX&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    let&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; ptr&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;cap&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; PREFIX&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if let&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; get_mut&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;),&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;        *&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *=&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0001&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;// columns computed once: 0.26 ns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;/span&gt;&lt;span&gt; tags&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; ptr&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;cap&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; PREFIX&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span&gt; ptr&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;cap&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; PREFIX&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;..&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if let&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; get_mut&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;),&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;        *&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *=&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0001&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;same arithmetic, same &lt;code&gt;get_mut&lt;/code&gt;, same match. only the position of two lines differs (shown inlined here; in the experiment they sat inside a separate &lt;code&gt;#[inline]&lt;/code&gt; function).&lt;/p&gt;
&lt;p&gt;time per element, n = 1,000, scrambled variants:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;variant&lt;/th&gt;&lt;th&gt;column pointers&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;time&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Vec&amp;lt;Shape&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;baseline&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.66 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;marl &lt;code&gt;iter_mut()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;per element&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.17 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;marl &lt;code&gt;get_mut(i)&lt;/code&gt; loop&lt;/td&gt;&lt;td&gt;per element&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.18 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;replica, columns inside call&lt;/td&gt;&lt;td&gt;per element&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.17 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;replica, columns hoisted&lt;/td&gt;&lt;td&gt;once, before the loop&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.26 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;replica iterator&lt;/td&gt;&lt;td&gt;stored in the iterator&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.26 ns&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;raw columns&lt;/td&gt;&lt;td&gt;no reference enum&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.26 ns&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;two clusters, no middle ground. every variant that computes &lt;code&gt;ptr + cap × PREFIX[i]&lt;/code&gt; inside the per-element call lands at about 1.17 ns; every variant that doesn&#39;t lands at about 0.26 ns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the trigger:&lt;/strong&gt; llvm does eventually hoist that multiply out of the loop, but only in a later pass. by then simplifycfg has already merged the match arms around it and left stores llvm can no longer delete. the multiply is hoisted one pass too late.&lt;/p&gt;
&lt;h2 id=&quot;the-fix-pass-columns-not-the-raw-buffer&quot;&gt;the fix: pass columns, not the raw buffer&lt;/h2&gt;
&lt;p&gt;don&#39;t make element access rely on the optimiser to hoist the column computation: do it at the source level. &lt;code&gt;Soa&lt;/code&gt; gained a hidden associated type holding a pointer to the start of every column, and &lt;code&gt;get&lt;/code&gt;/&lt;code&gt;get_mut&lt;/code&gt; now take those pointers instead of the raw buffer.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; // src/soa.rs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pub unsafe trait Soa: Sized {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    /// Pointers to the start of every column, e.g. `(*mut u32, *mut bool)`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    type Columns: Copy;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    unsafe fn columns(raw: Raw) -&amp;gt; Self::Columns;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    unsafe fn get_mut&amp;lt;&amp;#39;a&amp;gt;(raw: Raw, idx: usize) -&amp;gt; Self::RefMut&amp;lt;&amp;#39;a&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    unsafe fn get_mut&amp;lt;&amp;#39;a&amp;gt;(cols: Self::Columns, idx: usize) -&amp;gt; Self::RefMut&amp;lt;&amp;#39;a&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;get&lt;/code&gt; changes the same way. for a struct, &lt;code&gt;Columns&lt;/code&gt; is a tuple with one pointer per field; for an enum it is &lt;code&gt;(*mut ShapeTag, *mut ShapeData)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;the iterators compute the columns once, in &lt;code&gt;new()&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; // src/iter.rs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; pub struct IterMut&amp;lt;&amp;#39;a, T: Soa&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    raw: Raw,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    cols: T::Columns,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     front: usize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     back: usize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; fn next(&amp;amp;mut self) -&amp;gt; Option&amp;lt;T::RefMut&amp;lt;&amp;#39;a&amp;gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    T::get_mut(self.raw, self.front)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    T::get_mut(self.cols, self.front)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sort_by&lt;/code&gt; and &lt;code&gt;sort_unstable_by&lt;/code&gt; do the same before sorting. &lt;code&gt;list.get(i)&lt;/code&gt; calls &lt;code&gt;T::columns&lt;/code&gt; on each lookup, so single-element access behaves as before.&lt;/p&gt;
&lt;p&gt;what the derive now generates for &lt;code&gt;Shape&lt;/code&gt; (simplified). the match is unchanged; there is no longer any multiply inside it.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unsafe fn&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; get_mut&lt;/span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #EBA0AC;&quot;&gt;cols&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; Self&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Columns&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #EBA0AC;&quot;&gt; idx&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; usize&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot; style=&quot;background-color: #CDD6F412;&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;/span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;/span&gt;&lt;span&gt;cols&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;idx&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;),&lt;/span&gt;&lt;span&gt; cols&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;idx&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    match&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;        ShapeTag&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; = &amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Circle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;        ShapeTag&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rect&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; = &amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rect&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rect&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;        ShapeTag&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Triangle&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; let&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; = &amp;amp;&lt;/span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Triangle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;Triangle&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;        ShapeTag&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ShapeRefMut&lt;/span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;why-this-works&quot;&gt;why this works&lt;/h3&gt;
&lt;p&gt;the pointers now live in the iterator struct. once &lt;code&gt;next()&lt;/code&gt; is inlined into the loop, they are plain local values fixed before the first iteration, so no later pass has to hoist anything. each match arm is then just an offset from &lt;code&gt;data&lt;/code&gt;, and the arms no longer share a computation for simplifycfg to merge around.&lt;/p&gt;
&lt;p&gt;that is the expected mechanism. what was measured: the replica built this way compiles with no stack writes in the loop, and it runs at the same speed as the raw column loop, which needs only one tag test per element.&lt;/p&gt;
&lt;p&gt;the same inlining, with and without a multiply inside the match:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;before:&lt;/strong&gt; inline &lt;code&gt;get_mut&lt;/code&gt; → rect/triangle tails merged around the unhoisted multiply → store target is a &lt;code&gt;phi&lt;/code&gt; → writes kept, 4-way switch kept&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;after:&lt;/strong&gt; inline &lt;code&gt;get_mut&lt;/code&gt; → pointers already invariant → &lt;code&gt;ShapeRefMut&lt;/code&gt; split and removed → one tag test&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;the fix doesn&#39;t rely on llvm&#39;s pass ordering happening to line up. it removes the computation that the ordering depended on, so it should hold for other enum shapes and future compiler versions, rather than being tuned to this benchmark.&lt;/p&gt;
&lt;h2 id=&quot;results&quot;&gt;results&lt;/h2&gt;
&lt;p&gt;the full criterion suite was rerun with the fix applied. the existing tests, clippy and &lt;code&gt;cargo fmt --check&lt;/code&gt; all pass.&lt;/p&gt;
&lt;p&gt;speedup over &lt;code&gt;Vec&lt;/code&gt; (the two runs were hours apart):&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;n&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;before&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;after&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/scale_circles&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.52×&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;2.37×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;100,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.56×&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;2.10×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.60×&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1.55×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/count_variant&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.03×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.35×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/push&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.06×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.13×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;enum/total_area&lt;/code&gt; (iter)&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.01×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.99×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;struct/iter_all_fields&lt;/code&gt;&lt;/td&gt;&lt;td&gt;100,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.42×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.45×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;struct/sort_by_key&lt;/code&gt;&lt;/td&gt;&lt;td&gt;100,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.67×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.79×&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;struct/random_get&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.92×&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.71×¹&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;from twice as slow to more than twice as fast. the gain is smallest at a million elements; that size wasn&#39;t analysed further. everything outside &lt;code&gt;scale_circles&lt;/code&gt; is within run-to-run noise.&lt;/p&gt;
&lt;p&gt;¹ &lt;code&gt;random_get&lt;/code&gt; looked like a regression, so it was rerun back to back against a clean checkout, twice. at 1M, the version before the fix measured 351 and 355 µs and the fixed version 349 and 377 µs: no measurable difference.&lt;/p&gt;
&lt;h2 id=&quot;loose-ends&quot;&gt;loose ends&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;total_area&lt;/code&gt; is still at parity, and that&#39;s not a marl problem.&lt;/strong&gt; that benchmark matches every element and reads its whole payload. the &lt;code&gt;Vec&lt;/code&gt; loop and marl&#39;s loop compile to essentially the same code, and the stored element only shrinks from 28 to 25 bytes, so splitting tags from payloads has nothing to save. an early guess that the running floating-point sum set the speed limit didn&#39;t hold up: splitting the sum across four accumulators made the loop slower, not faster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;index access still computes columns per call.&lt;/strong&gt; &lt;code&gt;list.get(i)&lt;/code&gt; can&#39;t cache anything between calls, so a hot loop over &lt;code&gt;get_mut(i)&lt;/code&gt; can still hit the slow pattern. iterators, sorting and the column slices avoid it. if index loops over enums become common, a public way to hold on to &lt;code&gt;Columns&lt;/code&gt; would be the next step.&lt;/p&gt;
&lt;p&gt;for derive-generated accessors generally: anything derived from the container rather than the element, such as base pointers, offsets or capacity-scaled products, belongs outside the per-element function. it will usually get hoisted anyway. &lt;em&gt;usually&lt;/em&gt; is exactly the gap this bug fell into.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;measured on an apple m1 pro with rustc 1.98.1 (llvm 22.1.8) and criterion 0.8, default release profile. per-element timings come from a standalone probe crate built from the same code; speedups come from &lt;code&gt;scripts/bench.py&lt;/code&gt;. the change is commit 4057689, &quot;optimise: use columns instead of raw&quot;.&lt;/em&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>about this site</title>
        <published>2026-05-08T00:00:00+00:00</published>
        <updated>2026-05-08T00:00:00+00:00</updated>
        
        <author>
          <name>zihan</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zihanjin.com/writings/about-this-site/"/>
        <id>https://zihanjin.com/writings/about-this-site/</id>
        
        <content type="html" xml:base="https://zihanjin.com/writings/about-this-site/">&lt;p&gt;this site has undergone many an iteration.&lt;/p&gt;
&lt;p&gt;i can never seem to settle on one thing. partly because i am indecisive and like shiny things, partly because nothing i&#39;ve tried was the right fit.&lt;/p&gt;
&lt;p&gt;the first iteration of this site was, like most web projects nowadays, a &lt;a rel=&quot;external&quot; href=&quot;https://nextjs.org&quot;&gt;nextjs&lt;/a&gt; site.
it was mostly fine...
i think vercel has done some very technologically impressive things with nextjs but it introduced way too much complexity for what i was doing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;why is my &lt;em&gt;static&lt;/em&gt; website 400mb?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;another thing that irked me with nextjs is the vendor lock in. i know there is &lt;a rel=&quot;external&quot; href=&quot;https://opennext.js.org&quot;&gt;opennext&lt;/a&gt; but the fact that there has to be an opennext just felt wrong.&lt;/p&gt;
&lt;p&gt;from a complete overkill solution, i tried to reject the last 20 year of web development tooling, and for the better part of a year, this site was just raw, handwritten html in a folder being served by &lt;a rel=&quot;external&quot; href=&quot;https://caddyserver.com&quot;&gt;caddy&lt;/a&gt; on my vps.&lt;/p&gt;
&lt;p&gt;this worked great. it was easy to update, easy to manage, easy to understand. there was no hidden complexities of what was going on where. i had full control. when i wanted to update a page or create a new page, i just &lt;code&gt;scp&lt;/code&gt; it to my vps and its done.
there was a giant issue though...&lt;/p&gt;
&lt;p&gt;that of course was that i had to write every single page by hand.
it was incredibly annoying and i had effectively 0 code reuse (templating).
leading me to look for a minimal static site generator.&lt;/p&gt;
&lt;p&gt;this is when i found &lt;a rel=&quot;external&quot; href=&quot;https://github.com/getzola/zola&quot;&gt;zola&lt;/a&gt;. zola is a static site builder written in rust with &lt;a rel=&quot;external&quot; href=&quot;https://keats.github.io/tera/&quot;&gt;tera&lt;/a&gt; templating.
having used an exorbitant amount of go templating tera was a breath of fresh air.&lt;/p&gt;
&lt;p&gt;this theme is a fork of &lt;a rel=&quot;external&quot; href=&quot;https://github.com/wjianbo/zola-theme-still&quot;&gt;still&lt;/a&gt;, a minimal theme focused on long form writing.
idk who the author is but he did a great job. this theme reminds me the unix philosphy whereby each tool only does one thing but does that one thing very very well.&lt;/p&gt;
&lt;p&gt;this site is built using a personal &lt;a href=&quot;https://zihanjin.com/writings/about-this-site/github.com/zjom/zola&quot;&gt;fork of zola&lt;/a&gt; with extra tera functions and filters.&lt;/p&gt;
&lt;p&gt;you can find the source of this site &lt;a href=&quot;https://zihanjin.com/writings/about-this-site/github.com/zjom/www&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
</feed>
