| new file mode 100644 |
| index 0000000..05275c6 |
| --- /dev/null |
| +++ b/report/site/index.html |
| @@ -0,0 +1,295 @@ |
| +<!doctype html> |
| +<html lang="en"> |
| +<head> |
| +<meta charset="utf-8"> |
| +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| +<title>One TodoMVC, nine languages, one platform</title> |
| +<style> |
| +:root { |
| + color-scheme: light; |
| + --plane: #f9f9f7; --surface: #fcfcfb; --ink: #0b0b0b; --ink-2: #52514e; --muted: #898781; |
| + --grid: #e1e0d9; --axis: #c3c2b7; --ring: rgba(11,11,11,0.10); |
| + --s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; |
| +} |
| +@media (prefers-color-scheme: dark) { |
| + :root { |
| + color-scheme: dark; |
| + --plane: #0d0d0d; --surface: #1a1a19; --ink: #ffffff; --ink-2: #c3c2b7; --muted: #898781; |
| + --grid: #2c2c2a; --axis: #383835; --ring: rgba(255,255,255,0.10); |
| + --s1: #3987e5; --s2: #d95926; --s3: #199e70; |
| + } |
| +} |
| +* { box-sizing: border-box; } |
| +body { margin: 0; background: var(--plane); color: var(--ink); font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; } |
| +main { max-width: 760px; margin: 0 auto; padding: 48px 20px 80px; } |
| +h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 16px; } |
| +h2 { font-size: 1.35rem; margin: 56px 0 12px; } |
| +h3 { font-size: 1.05rem; margin: 0 0 4px; } |
| +p, li { color: var(--ink-2); } |
| +a { color: inherit; } |
| +code { font-size: .9em; } |
| +.card { background: var(--surface); border: 1px solid var(--ring); border-radius: 10px; padding: 18px 20px; margin: 16px 0; } |
| +.note { color: var(--muted); font-size: .9rem; margin: 2px 0 8px; } |
| +.chart { width: 100%; height: auto; display: block; } |
| +.chart .label { fill: var(--ink-2); font-size: 13px; text-anchor: end; } |
| +.chart .value { fill: var(--ink); font-size: 13px; font-variant-numeric: tabular-nums; } |
| +.chart .missing { fill: var(--muted); font-size: 13px; font-style: italic; } |
| +.chart .axis { stroke: var(--axis); stroke-width: 1; } |
| +.chart .s1 { fill: var(--s1); } .chart .s2 { fill: var(--s2); } .chart .s3 { fill: var(--s3); } |
| +.legend { display: flex; gap: 16px; font-size: .85rem; color: var(--ink-2); margin: 4px 0 0; padding: 0; list-style: none; } |
| +.legend li::before { content: ""; display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; background: var(--k); } |
| +.scroll { overflow-x: auto; } |
| +table { border-collapse: collapse; width: 100%; font-size: .85rem; font-variant-numeric: tabular-nums; } |
| +th, td { padding: 6px 8px; border-bottom: 1px solid var(--grid); text-align: right; white-space: nowrap; } |
| +th:first-child, td:first-child { text-align: left; } |
| +th { color: var(--ink-2); font-weight: 600; } |
| +.blocked td:not(:first-child) { color: var(--muted); text-align: left; white-space: normal; } |
| +dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 8px 0 0; font-size: .92rem; } |
| +dt { color: var(--muted); } dd { margin: 0; color: var(--ink-2); } |
| +</style> |
| +</head> |
| +<body> |
| +<main> |
| +<h1>One TodoMVC, nine languages, one platform</h1> |
| +<p>We built the same small app nine times, once in each of nine language setups, and put every one on Carloku, the hosted CARLOS Platform. The app is <a href="https://todomvc.com">TodoMVC</a>: a to-do list. Here it's a page the server builds, which works with JavaScript switched off, plus a little JavaScript on top so it feels like the familiar TodoMVC.</p> |
| +<p>The point is to show that CARLOS apps, and the platform they run on, aren't tied to Go. So this page compares the nine; it doesn't rank them.</p> |
| +<p>One AI model, Opus 5.5, built every app from the same brief. A second, Astra, reviewed each one. A third, Fable, scored how readable the code is without being told which app it was reading. All nine apps are live, and their code is public at <a href="https://amadan.net/carlos/todomvc-bakeoff">amadan.net/carlos/todomvc-bakeoff</a>.</p> |
| + |
| + |
| +<h2>What stood out</h2> |
| +<ul><li><strong>All nine pass every shared test</strong> on Carloku: 67 of 67, with JavaScript on and with it off.<li><strong>Nim and Zig handled the most traffic</strong> on Carloku's default size: about 1,400 to 1,500 page loads a second, and 3,100 to 3,600 toggles. They also made the smallest programs (under 2 MB) and used the least memory at rest (2.5 to 3.5 MB).<li><strong>Go builds fastest.</strong> About 10 seconds from scratch and under a second to rebuild, with or without Rastrillo.<li><strong>Waking up costs about the same everywhere.</strong> Every app answered its first request after sleeping in 1.2 to 1.5 seconds; about one second of that is Carloku starting the program.<li><strong>Build costs ranged from $2.92 to $10.70.</strong> Rust was the cheapest. Spinel was the most expensive, mostly because getting it to compile for ARM took the builder 77 minutes.<li><strong>Two apps changed under heavy writing.</strong> OCaml served page loads about 25% more slowly afterwards, and Spinel's server process exited and was restarted 16 times during the load test.</ul> |
| + |
| + |
| +<h2>At a glance</h2> |
| +<div class="card scroll"> |
| +<table> |
| +<thead><tr><th>Setup</th><th>Tests (JS on · off)</th><th>First byte</th><th>Wake</th><th>Peak reads/s</th><th>Binary</th><th>Cold / warm build</th><th>Lines</th><th>Deps</th><th>Cost</th><th>Readability</th></tr></thead> |
| +<tbody> |
| +<tr><td>Go</td> |
| +<td>35/35 · 32/32</td> |
| +<td>95.9 ms</td><td>1267 ms</td><td>349</td> |
| +<td>12.3 MB</td><td>10.3 s / 0.2 s</td> |
| +<td>880</td><td>1</td><td>$3.69</td><td>8/10</td></tr> |
| +<tr><td>Go + Rastrillo</td> |
| +<td>35/35 · 32/32</td> |
| +<td>56.6 ms</td><td>1369 ms</td><td>336</td> |
| +<td>14.1 MB</td><td>10.4 s / 0.3 s</td> |
| +<td>802</td><td>3</td><td>$4.67</td><td>8/10</td></tr> |
| +<tr><td>Rust</td> |
| +<td>35/35 · 32/32</td> |
| +<td>93.2 ms</td><td>1245 ms</td><td>904</td> |
| +<td>3.2 MB</td><td>49.6 s / 8.5 s</td> |
| +<td>815</td><td>7</td><td>$2.92</td><td>8/10</td></tr> |
| +<tr><td>Zig</td> |
| +<td>35/35 · 32/32</td> |
| +<td>50.2 ms</td><td>1238 ms</td><td>1,382</td> |
| +<td>1.9 MB</td><td>2.5 min / 5.4 s</td> |
| +<td>1058</td><td>1</td><td>$5.71</td><td>9/10</td></tr> |
| +<tr><td>OCaml</td> |
| +<td>35/35 · 32/32</td> |
| +<td>51.7 ms</td><td>1286 ms</td><td>992</td> |
| +<td>8.2 MB</td><td>18.3 min / 10.0 s</td> |
| +<td>697</td><td>9</td><td>$7.65</td><td>8/10</td></tr> |
| +<tr><td>Haskell</td> |
| +<td>35/35 · 32/32</td> |
| +<td>99.3 ms</td><td>1364 ms</td><td>400</td> |
| +<td>28.7 MB</td><td>19.1 min / 104.4 s</td> |
| +<td>798</td><td>10</td><td>$5.06</td><td>8/10</td></tr> |
| +<tr><td>Swift</td> |
| +<td>35/35 · 32/32</td> |
| +<td>58.7 ms</td><td>1445 ms</td><td>537</td> |
| +<td>22.0 MB</td><td>81.4 s / 4.1 s</td> |
| +<td>1326</td><td>3</td><td>$4.60</td><td>8/10</td></tr> |
| +<tr><td>Nim</td> |
| +<td>35/35 · 32/32</td> |
| +<td>50.6 ms</td><td>1299 ms</td><td>1,491</td> |
| +<td>1.8 MB</td><td>33.9 s / 0.9 s</td> |
| +<td>931</td><td>2</td><td>$4.84</td><td>8/10</td></tr> |
| +<tr><td>Spinel</td> |
| +<td>35/35 · 32/32</td> |
| +<td>94.6 ms</td><td>1285 ms</td><td>722</td> |
| +<td>8.7 MB</td><td>4.8 min / 101.3 s</td> |
| +<td>566</td><td>2</td><td>$10.70</td><td>7/10</td></tr> |
| +</tbody> |
| +</table> |
| +</div> |
| + |
| + |
| +<div class="card"> |
| +<h3>Time to first byte, empty list</h3> |
| +<p class="note">Median of 30 page loads from our test machine, each on a new connection, so it includes the network.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Time to first byte, empty list"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H570.6Q574.6 15.0 574.6 19.0V27.0Q574.6 31.0 570.6 31.0H150.0Z"><title>Go: 95.9 ms</title></path><text class="value" x="580.6" y="28">95.9 ms</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H396.9Q400.9 45.0 400.9 49.0V57.0Q400.9 61.0 396.9 61.0H150.0Z"><title>Go + Rastrillo: 56.6 ms</title></path><text class="value" x="406.9" y="58">56.6 ms</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H558.9Q562.9 75.0 562.9 79.0V87.0Q562.9 91.0 558.9 91.0H150.0Z"><title>Rust: 93.2 ms</title></path><text class="value" x="568.9" y="88">93.2 ms</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H368.5Q372.5 105.0 372.5 109.0V117.0Q372.5 121.0 368.5 121.0H150.0Z"><title>Zig: 50.2 ms</title></path><text class="value" x="378.5" y="118">50.2 ms</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H374.8Q378.8 135.0 378.8 139.0V147.0Q378.8 151.0 374.8 151.0H150.0Z"><title>OCaml: 51.7 ms</title></path><text class="value" x="384.8" y="148">51.7 ms</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 99.3 ms</title></path><text class="value" x="596.0" y="178">99.3 ms</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H406.0Q410.0 195.0 410.0 199.0V207.0Q410.0 211.0 406.0 211.0H150.0Z"><title>Swift: 58.7 ms</title></path><text class="value" x="416.0" y="208">58.7 ms</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H370.2Q374.2 225.0 374.2 229.0V237.0Q374.2 241.0 370.2 241.0H150.0Z"><title>Nim: 50.6 ms</title></path><text class="value" x="380.2" y="238">50.6 ms</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H564.9Q568.9 255.0 568.9 259.0V267.0Q568.9 271.0 564.9 271.0H150.0Z"><title>Spinel: 94.6 ms</title></path><text class="value" x="574.9" y="268">94.6 ms</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Time to first byte, 50 items</h3> |
| +<p class="note">The same, with 50 todos on the list.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Time to first byte, 50 items"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H579.3Q583.3 15.0 583.3 19.0V27.0Q583.3 31.0 579.3 31.0H150.0Z"><title>Go: 97.0 ms</title></path><text class="value" x="589.3" y="28">97.0 ms</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H387.6Q391.6 45.0 391.6 49.0V57.0Q391.6 61.0 387.6 61.0H150.0Z"><title>Go + Rastrillo: 54.1 ms</title></path><text class="value" x="397.6" y="58">54.1 ms</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H573.9Q577.9 75.0 577.9 79.0V87.0Q577.9 91.0 573.9 91.0H150.0Z"><title>Rust: 95.8 ms</title></path><text class="value" x="583.9" y="88">95.8 ms</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H387.9Q391.9 105.0 391.9 109.0V117.0Q391.9 121.0 387.9 121.0H150.0Z"><title>Zig: 54.1 ms</title></path><text class="value" x="397.9" y="118">54.1 ms</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H382.4Q386.4 135.0 386.4 139.0V147.0Q386.4 151.0 382.4 151.0H150.0Z"><title>OCaml: 52.9 ms</title></path><text class="value" x="392.4" y="148">52.9 ms</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 98.5 ms</title></path><text class="value" x="596.0" y="178">98.5 ms</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H397.7Q401.7 195.0 401.7 199.0V207.0Q401.7 211.0 397.7 211.0H150.0Z"><title>Swift: 56.3 ms</title></path><text class="value" x="407.7" y="208">56.3 ms</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H373.1Q377.1 225.0 377.1 229.0V237.0Q377.1 241.0 373.1 241.0H150.0Z"><title>Nim: 50.8 ms</title></path><text class="value" x="383.1" y="238">50.8 ms</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H560.8Q564.8 255.0 564.8 259.0V267.0Q564.8 271.0 560.8 271.0H150.0Z"><title>Spinel: 92.8 ms</title></path><text class="value" x="570.8" y="268">92.8 ms</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Wake from sleep</h3> |
| +<p class="note">Time to the first byte when the app had gone to sleep after 15 idle minutes. Median of three.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Wake from sleep"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H531.7Q535.7 15.0 535.7 19.0V27.0Q535.7 31.0 531.7 31.0H150.0Z"><title>Go: 1267 ms</title></path><text class="value" x="541.7" y="28">1267 ms</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H563.0Q567.0 45.0 567.0 49.0V57.0Q567.0 61.0 563.0 61.0H150.0Z"><title>Go + Rastrillo: 1369 ms</title></path><text class="value" x="573.0" y="58">1369 ms</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H525.1Q529.1 75.0 529.1 79.0V87.0Q529.1 91.0 525.1 91.0H150.0Z"><title>Rust: 1245 ms</title></path><text class="value" x="535.1" y="88">1245 ms</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H522.8Q526.8 105.0 526.8 109.0V117.0Q526.8 121.0 522.8 121.0H150.0Z"><title>Zig: 1238 ms</title></path><text class="value" x="532.8" y="118">1238 ms</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H537.5Q541.5 135.0 541.5 139.0V147.0Q541.5 151.0 537.5 151.0H150.0Z"><title>OCaml: 1286 ms</title></path><text class="value" x="547.5" y="148">1286 ms</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H561.3Q565.3 165.0 565.3 169.0V177.0Q565.3 181.0 561.3 181.0H150.0Z"><title>Haskell: 1364 ms</title></path><text class="value" x="571.3" y="178">1364 ms</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H586.0Q590.0 195.0 590.0 199.0V207.0Q590.0 211.0 586.0 211.0H150.0Z"><title>Swift: 1445 ms</title></path><text class="value" x="596.0" y="208">1445 ms</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H541.7Q545.7 225.0 545.7 229.0V237.0Q545.7 241.0 541.7 241.0H150.0Z"><title>Nim: 1299 ms</title></path><text class="value" x="551.7" y="238">1299 ms</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H537.4Q541.4 255.0 541.4 259.0V267.0Q541.4 271.0 537.4 271.0H150.0Z"><title>Spinel: 1285 ms</title></path><text class="value" x="547.4" y="268">1285 ms</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Peak page loads per second</h3> |
| +<p class="note">Loading a 50-item list while doubling the number of simultaneous connections from 1, 10 seconds per step.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Peak page loads per second"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H248.9Q252.9 15.0 252.9 19.0V27.0Q252.9 31.0 248.9 31.0H150.0Z"><title>Go: 349</title></path><text class="value" x="258.9" y="28">349</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H245.2Q249.2 45.0 249.2 49.0V57.0Q249.2 61.0 245.2 61.0H150.0Z"><title>Go + Rastrillo: 336</title></path><text class="value" x="255.2" y="58">336</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H412.9Q416.9 75.0 416.9 79.0V87.0Q416.9 91.0 412.9 91.0H150.0Z"><title>Rust: 904</title></path><text class="value" x="422.9" y="88">904</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H553.7Q557.7 105.0 557.7 109.0V117.0Q557.7 121.0 553.7 121.0H150.0Z"><title>Zig: 1,382</title></path><text class="value" x="563.7" y="118">1,382</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H438.6Q442.6 135.0 442.6 139.0V147.0Q442.6 151.0 438.6 151.0H150.0Z"><title>OCaml: 992</title></path><text class="value" x="448.6" y="148">992</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H264.0Q268.0 165.0 268.0 169.0V177.0Q268.0 181.0 264.0 181.0H150.0Z"><title>Haskell: 400</title></path><text class="value" x="274.0" y="178">400</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H304.3Q308.3 195.0 308.3 199.0V207.0Q308.3 211.0 304.3 211.0H150.0Z"><title>Swift: 537</title></path><text class="value" x="314.3" y="208">537</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H586.0Q590.0 225.0 590.0 229.0V237.0Q590.0 241.0 586.0 241.0H150.0Z"><title>Nim: 1,491</title></path><text class="value" x="596.0" y="238">1,491</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H359.0Q363.0 255.0 363.0 259.0V267.0Q363.0 271.0 359.0 271.0H150.0Z"><title>Spinel: 722</title></path><text class="value" x="369.0" y="268">722</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Page loads: where it slows down</h3> |
| +<p class="note">The fewest simultaneous connections from which adding more never raised throughput by 10% or more.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Page loads: where it slows down"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H201.0Q205.0 15.0 205.0 19.0V27.0Q205.0 31.0 201.0 31.0H150.0Z"><title>Go: 8 connections</title></path><text class="value" x="211.0" y="28">8 connections</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H256.0Q260.0 45.0 260.0 49.0V57.0Q260.0 61.0 256.0 61.0H150.0Z"><title>Go + Rastrillo: 16 connections</title></path><text class="value" x="266.0" y="58">16 connections</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H586.0Q590.0 75.0 590.0 79.0V87.0Q590.0 91.0 586.0 91.0H150.0Z"><title>Rust: 64 connections</title></path><text class="value" x="596.0" y="88">64 connections · Carloku turned some requests away from 128</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H366.0Q370.0 105.0 370.0 109.0V117.0Q370.0 121.0 366.0 121.0H150.0Z"><title>Zig: 32 connections</title></path><text class="value" x="376.0" y="118">32 connections</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H586.0Q590.0 135.0 590.0 139.0V147.0Q590.0 151.0 586.0 151.0H150.0Z"><title>OCaml: 64 connections</title></path><text class="value" x="596.0" y="148">64 connections · Carloku turned some requests away from 256</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H366.0Q370.0 165.0 370.0 169.0V177.0Q370.0 181.0 366.0 181.0H150.0Z"><title>Haskell: 32 connections</title></path><text class="value" x="376.0" y="178">32 connections</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H586.0Q590.0 195.0 590.0 199.0V207.0Q590.0 211.0 586.0 211.0H150.0Z"><title>Swift: 64 connections</title></path><text class="value" x="596.0" y="208">64 connections · Carloku turned some requests away from 512</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H366.0Q370.0 225.0 370.0 229.0V237.0Q370.0 241.0 366.0 241.0H150.0Z"><title>Nim: 32 connections</title></path><text class="value" x="376.0" y="238">32 connections</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H586.0Q590.0 255.0 590.0 259.0V267.0Q590.0 271.0 586.0 271.0H150.0Z"><title>Spinel: 64 connections</title></path><text class="value" x="596.0" y="268">64 connections · Carloku turned some requests away from 128</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Peak toggles per second</h3> |
| +<p class="note">Ticking one todo on and off, ramping the same way.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Peak toggles per second"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H246.0Q250.0 15.0 250.0 19.0V27.0Q250.0 31.0 246.0 31.0H150.0Z"><title>Go: 810</title></path><text class="value" x="256.0" y="28">810</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H184.1Q188.1 45.0 188.1 49.0V57.0Q188.1 61.0 184.1 61.0H150.0Z"><title>Go + Rastrillo: 309</title></path><text class="value" x="194.1" y="58">309</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H272.2Q276.2 75.0 276.2 79.0V87.0Q276.2 91.0 272.2 91.0H150.0Z"><title>Rust: 1,022</title></path><text class="value" x="282.2" y="88">1,022</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H530.6Q534.6 105.0 534.6 109.0V117.0Q534.6 121.0 530.6 121.0H150.0Z"><title>Zig: 3,113</title></path><text class="value" x="540.6" y="118">3,113</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H276.7Q280.7 135.0 280.7 139.0V147.0Q280.7 151.0 276.7 151.0H150.0Z"><title>OCaml: 1,058</title></path><text class="value" x="286.7" y="148">1,058</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H252.6Q256.6 165.0 256.6 169.0V177.0Q256.6 181.0 252.6 181.0H150.0Z"><title>Haskell: 862</title></path><text class="value" x="262.6" y="178">862</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H325.6Q329.6 195.0 329.6 199.0V207.0Q329.6 211.0 325.6 211.0H150.0Z"><title>Swift: 1,453</title></path><text class="value" x="335.6" y="208">1,453</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H586.0Q590.0 225.0 590.0 229.0V237.0Q590.0 241.0 586.0 241.0H150.0Z"><title>Nim: 3,561</title></path><text class="value" x="596.0" y="238">3,561</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H448.9Q452.9 255.0 452.9 259.0V267.0Q452.9 271.0 448.9 271.0H150.0Z"><title>Spinel: 2,451</title></path><text class="value" x="458.9" y="268">2,451</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Toggles: where it slows down</h3> |
| +<p class="note">The same rule as for page loads.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Toggles: where it slows down"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H256.0Q260.0 15.0 260.0 19.0V27.0Q260.0 31.0 256.0 31.0H150.0Z"><title>Go: 32 connections</title></path><text class="value" x="266.0" y="28">32 connections · Carloku turned some requests away from 128</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H201.0Q205.0 45.0 205.0 49.0V57.0Q205.0 61.0 201.0 61.0H150.0Z"><title>Go + Rastrillo: 16 connections</title></path><text class="value" x="211.0" y="58">16 connections</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H366.0Q370.0 75.0 370.0 79.0V87.0Q370.0 91.0 366.0 91.0H150.0Z"><title>Rust: 64 connections</title></path><text class="value" x="376.0" y="88">64 connections · Carloku turned some requests away from 128</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H366.0Q370.0 105.0 370.0 109.0V117.0Q370.0 121.0 366.0 121.0H150.0Z"><title>Zig: 64 connections</title></path><text class="value" x="376.0" y="118">64 connections</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H366.0Q370.0 135.0 370.0 139.0V147.0Q370.0 151.0 366.0 151.0H150.0Z"><title>OCaml: 64 connections</title></path><text class="value" x="376.0" y="148">64 connections · Carloku turned some requests away from 128</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H256.0Q260.0 165.0 260.0 169.0V177.0Q260.0 181.0 256.0 181.0H150.0Z"><title>Haskell: 32 connections</title></path><text class="value" x="266.0" y="178">32 connections · Carloku turned some requests away from 128</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H586.0Q590.0 195.0 590.0 199.0V207.0Q590.0 211.0 586.0 211.0H150.0Z"><title>Swift: 128 connections</title></path><text class="value" x="596.0" y="208">128 connections</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H586.0Q590.0 225.0 590.0 229.0V237.0Q590.0 241.0 586.0 241.0H150.0Z"><title>Nim: 128 connections</title></path><text class="value" x="596.0" y="238">128 connections · Carloku turned some requests away from 256</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H366.0Q370.0 255.0 370.0 259.0V267.0Q370.0 271.0 366.0 271.0H150.0Z"><title>Spinel: 64 connections</title></path><text class="value" x="376.0" y="268">64 connections · Carloku turned some requests away from 256</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Peak page loads per second, after the writes</h3> |
| +<p class="note">The read ramp again, straight after the toggle ramp, on the same database.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Peak page loads per second, after the writes"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H248.2Q252.2 15.0 252.2 19.0V27.0Q252.2 31.0 248.2 31.0H150.0Z"><title>Go: 343</title></path><text class="value" x="258.2" y="28">343</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H246.1Q250.1 45.0 250.1 49.0V57.0Q250.1 61.0 246.1 61.0H150.0Z"><title>Go + Rastrillo: 336</title></path><text class="value" x="256.1" y="58">336</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H562.0Q566.0 75.0 566.0 79.0V87.0Q566.0 91.0 562.0 91.0H150.0Z"><title>Rust: 1,398</title></path><text class="value" x="572.0" y="88">1,398</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H556.3Q560.3 105.0 560.3 109.0V117.0Q560.3 121.0 556.3 121.0H150.0Z"><title>Zig: 1,379</title></path><text class="value" x="566.3" y="118">1,379</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H367.0Q371.0 135.0 371.0 139.0V147.0Q371.0 151.0 367.0 151.0H150.0Z"><title>OCaml: 743</title></path><text class="value" x="377.0" y="148">743</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H262.2Q266.2 165.0 266.2 169.0V177.0Q266.2 181.0 262.2 181.0H150.0Z"><title>Haskell: 391</title></path><text class="value" x="272.2" y="178">391</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H304.3Q308.3 195.0 308.3 199.0V207.0Q308.3 211.0 304.3 211.0H150.0Z"><title>Swift: 532</title></path><text class="value" x="314.3" y="208">532</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H586.0Q590.0 225.0 590.0 229.0V237.0Q590.0 241.0 586.0 241.0H150.0Z"><title>Nim: 1,479</title></path><text class="value" x="596.0" y="238">1,479</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H356.0Q360.0 255.0 360.0 259.0V267.0Q360.0 271.0 356.0 271.0H150.0Z"><title>Spinel: 706</title></path><text class="value" x="366.0" y="268">706</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Memory at rest</h3> |
| +<p class="note">Resident memory just after start, measured on our own machine because Carloku doesn't show members memory figures.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Memory at rest"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H461.5Q465.5 15.0 465.5 19.0V27.0Q465.5 31.0 461.5 31.0H150.0Z"><title>Go: 17.0 MB</title></path><text class="value" x="471.5" y="28">17.0 MB</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H493.6Q497.6 45.0 497.6 49.0V57.0Q497.6 61.0 493.6 61.0H150.0Z"><title>Go + Rastrillo: 18.7 MB</title></path><text class="value" x="503.6" y="58">18.7 MB</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H401.5Q405.5 75.0 405.5 79.0V87.0Q405.5 91.0 401.5 91.0H150.0Z"><title>Rust: 13.8 MB</title></path><text class="value" x="411.5" y="88">13.8 MB</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H191.6Q195.6 105.0 195.6 109.0V117.0Q195.6 121.0 191.6 121.0H150.0Z"><title>Zig: 2.5 MB</title></path><text class="value" x="201.6" y="118">2.5 MB</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H343.9Q347.9 135.0 347.9 139.0V147.0Q347.9 151.0 343.9 151.0H150.0Z"><title>OCaml: 10.7 MB</title></path><text class="value" x="353.9" y="148">10.7 MB</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H352.7Q356.7 165.0 356.7 169.0V177.0Q356.7 181.0 352.7 181.0H150.0Z"><title>Haskell: 11.1 MB</title></path><text class="value" x="362.7" y="178">11.1 MB</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H586.0Q590.0 195.0 590.0 199.0V207.0Q590.0 211.0 586.0 211.0H150.0Z"><title>Swift: 23.7 MB</title></path><text class="value" x="596.0" y="208">23.7 MB</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H210.5Q214.5 225.0 214.5 229.0V237.0Q214.5 241.0 210.5 241.0H150.0Z"><title>Nim: 3.5 MB</title></path><text class="value" x="220.5" y="238">3.5 MB</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H297.7Q301.7 255.0 301.7 259.0V267.0Q301.7 271.0 297.7 271.0H150.0Z"><title>Spinel: 8.2 MB</title></path><text class="value" x="307.7" y="268">8.2 MB</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Memory under load</h3> |
| +<p class="note">The most resident memory each app used during 10 seconds of 64 simultaneous page loads and then 10 seconds of 64 simultaneous toggles, on our own machine.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Memory under load"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H552.1Q556.1 15.0 556.1 19.0V27.0Q556.1 31.0 552.1 31.0H150.0Z"><title>Go: 70.7 MB</title></path><text class="value" x="562.1" y="28">70.7 MB</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H509.2Q513.2 45.0 513.2 49.0V57.0Q513.2 61.0 509.2 61.0H150.0Z"><title>Go + Rastrillo: 63.3 MB</title></path><text class="value" x="519.2" y="58">63.3 MB</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H448.2Q452.2 75.0 452.2 79.0V87.0Q452.2 91.0 448.2 91.0H150.0Z"><title>Rust: 52.6 MB</title></path><text class="value" x="458.2" y="88">52.6 MB</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H291.1Q295.1 105.0 295.1 109.0V117.0Q295.1 121.0 291.1 121.0H150.0Z"><title>Zig: 25.3 MB</title></path><text class="value" x="301.1" y="118">25.3 MB</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H243.5Q247.5 135.0 247.5 139.0V147.0Q247.5 151.0 243.5 151.0H150.0Z"><title>OCaml: 17.0 MB</title></path><text class="value" x="253.5" y="148">17.0 MB</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 76.6 MB</title></path><text class="value" x="596.0" y="178">76.6 MB</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H337.7Q341.7 195.0 341.7 199.0V207.0Q341.7 211.0 337.7 211.0H150.0Z"><title>Swift: 33.4 MB</title></path><text class="value" x="347.7" y="208">33.4 MB</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H211.8Q215.8 225.0 215.8 229.0V237.0Q215.8 241.0 211.8 241.0H150.0Z"><title>Nim: 11.5 MB</title></path><text class="value" x="221.8" y="238">11.5 MB</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H219.4Q223.4 255.0 223.4 259.0V267.0Q223.4 271.0 219.4 271.0H150.0Z"><title>Spinel: 12.8 MB</title></path><text class="value" x="229.4" y="268">12.8 MB</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Binary size</h3> |
| +<p class="note">The linux-arm64 file that ships.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Binary size"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H334.7Q338.7 15.0 338.7 19.0V27.0Q338.7 31.0 334.7 31.0H150.0Z"><title>Go: 12.3 MB</title></path><text class="value" x="344.7" y="28">12.3 MB</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H361.8Q365.8 45.0 365.8 49.0V57.0Q365.8 61.0 361.8 61.0H150.0Z"><title>Go + Rastrillo: 14.1 MB</title></path><text class="value" x="371.8" y="58">14.1 MB</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H194.3Q198.3 75.0 198.3 79.0V87.0Q198.3 91.0 194.3 91.0H150.0Z"><title>Rust: 3.2 MB</title></path><text class="value" x="204.3" y="88">3.2 MB</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H175.4Q179.4 105.0 179.4 109.0V117.0Q179.4 121.0 175.4 121.0H150.0Z"><title>Zig: 1.9 MB</title></path><text class="value" x="185.4" y="118">1.9 MB</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H272.1Q276.1 135.0 276.1 139.0V147.0Q276.1 151.0 272.1 151.0H150.0Z"><title>OCaml: 8.2 MB</title></path><text class="value" x="282.1" y="148">8.2 MB</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 28.7 MB</title></path><text class="value" x="596.0" y="178">28.7 MB</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H482.9Q486.9 195.0 486.9 199.0V207.0Q486.9 211.0 482.9 211.0H150.0Z"><title>Swift: 22.0 MB</title></path><text class="value" x="492.9" y="208">22.0 MB</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H173.0Q177.0 225.0 177.0 229.0V237.0Q177.0 241.0 173.0 241.0H150.0Z"><title>Nim: 1.8 MB</title></path><text class="value" x="183.0" y="238">1.8 MB</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H280.0Q284.0 255.0 284.0 259.0V267.0Q284.0 271.0 280.0 271.0H150.0Z"><title>Spinel: 8.7 MB</title></path><text class="value" x="290.0" y="268">8.7 MB</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Cold build</h3> |
| +<p class="note">Every library compiled from already-downloaded sources. One run each, because some take close to an hour under emulation.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Cold build"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H152.0Q153.9 15.0 153.9 17.0V29.0Q153.9 31.0 152.0 31.0H150.0Z"><title>Go: 10.3 s</title></path><text class="value" x="159.9" y="28">10.3 s</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H152.0Q154.0 45.0 154.0 47.0V59.0Q154.0 61.0 152.0 61.0H150.0Z"><title>Go + Rastrillo: 10.4 s</title></path><text class="value" x="160.0" y="58">10.4 s</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H165.0Q169.0 75.0 169.0 79.0V87.0Q169.0 91.0 165.0 91.0H150.0Z"><title>Rust: 49.6 s</title></path><text class="value" x="175.0" y="88">49.6 s</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H204.2Q208.2 105.0 208.2 109.0V117.0Q208.2 121.0 204.2 121.0H150.0Z"><title>Zig: 2.5 min</title></path><text class="value" x="214.2" y="118">2.5 min</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H567.3Q571.3 135.0 571.3 139.0V147.0Q571.3 151.0 567.3 151.0H150.0Z"><title>OCaml: 18.3 min</title></path><text class="value" x="577.3" y="148">18.3 min</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 19.1 min</title></path><text class="value" x="596.0" y="178">19.1 min</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H177.3Q181.3 195.0 181.3 199.0V207.0Q181.3 211.0 177.3 211.0H150.0Z"><title>Swift: 81.4 s</title></path><text class="value" x="187.3" y="208">81.4 s</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H159.0Q163.0 225.0 163.0 229.0V237.0Q163.0 241.0 159.0 241.0H150.0Z"><title>Nim: 33.9 s</title></path><text class="value" x="169.0" y="238">33.9 s</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H256.2Q260.2 255.0 260.2 259.0V267.0Q260.2 271.0 256.2 271.0H150.0Z"><title>Spinel: 4.8 min</title></path><text class="value" x="266.2" y="268">4.8 min</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Warm build</h3> |
| +<p class="note">Libraries already compiled; the app rebuilt from clean. Median of three.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Warm build"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H150.5Q150.9 15.0 150.9 15.5V30.5Q150.9 31.0 150.5 31.0H150.0Z"><title>Go: 0.2 s</title></path><text class="value" x="156.9" y="28">0.2 s</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H150.6Q151.1 45.0 151.1 45.6V60.4Q151.1 61.0 150.6 61.0H150.0Z"><title>Go + Rastrillo: 0.3 s</title></path><text class="value" x="157.1" y="58">0.3 s</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H181.7Q185.7 75.0 185.7 79.0V87.0Q185.7 91.0 181.7 91.0H150.0Z"><title>Rust: 8.5 s</title></path><text class="value" x="191.7" y="88">8.5 s</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H168.7Q172.7 105.0 172.7 109.0V117.0Q172.7 121.0 168.7 121.0H150.0Z"><title>Zig: 5.4 s</title></path><text class="value" x="178.7" y="118">5.4 s</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H188.0Q192.0 135.0 192.0 139.0V147.0Q192.0 151.0 188.0 151.0H150.0Z"><title>OCaml: 10.0 s</title></path><text class="value" x="198.0" y="148">10.0 s</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H586.0Q590.0 165.0 590.0 169.0V177.0Q590.0 181.0 586.0 181.0H150.0Z"><title>Haskell: 104.4 s</title></path><text class="value" x="596.0" y="178">104.4 s</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H163.4Q167.4 195.0 167.4 199.0V207.0Q167.4 211.0 163.4 211.0H150.0Z"><title>Swift: 4.1 s</title></path><text class="value" x="173.4" y="208">4.1 s</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H152.0Q154.0 225.0 154.0 227.0V239.0Q154.0 241.0 152.0 241.0H150.0Z"><title>Nim: 0.9 s</title></path><text class="value" x="160.0" y="238">0.9 s</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H573.0Q577.0 255.0 577.0 259.0V267.0Q577.0 271.0 573.0 271.0H150.0Z"><title>Spinel: 101.3 s</title></path><text class="value" x="583.0" y="268">101.3 s</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>One-line change</h3> |
| +<p class="note">Rebuild after changing one line in the code that serves the page. Median of three.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="One-line change"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H150.2Q150.4 15.0 150.4 15.2V30.8Q150.4 31.0 150.2 31.0H150.0Z"><title>Go: 0.1 s</title></path><text class="value" x="156.4" y="28">0.1 s</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H150.3Q150.6 45.0 150.6 45.3V60.7Q150.6 61.0 150.3 61.0H150.0Z"><title>Go + Rastrillo: 0.1 s</title></path><text class="value" x="156.6" y="58">0.1 s</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H170.3Q174.3 75.0 174.3 79.0V87.0Q174.3 91.0 170.3 91.0H150.0Z"><title>Rust: 5.8 s</title></path><text class="value" x="180.3" y="88">5.8 s</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H168.9Q172.9 105.0 172.9 109.0V117.0Q172.9 121.0 168.9 121.0H150.0Z"><title>Zig: 5.4 s</title></path><text class="value" x="178.9" y="118">5.4 s</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H160.0Q164.0 135.0 164.0 139.0V147.0Q164.0 151.0 160.0 151.0H150.0Z"><title>OCaml: 3.3 s</title></path><text class="value" x="170.0" y="148">3.3 s</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H258.7Q262.7 165.0 262.7 169.0V177.0Q262.7 181.0 258.7 181.0H150.0Z"><title>Haskell: 26.6 s</title></path><text class="value" x="268.7" y="178">26.6 s</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H162.3Q166.3 195.0 166.3 199.0V207.0Q166.3 211.0 162.3 211.0H150.0Z"><title>Swift: 3.9 s</title></path><text class="value" x="172.3" y="208">3.9 s</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H152.0Q153.9 225.0 153.9 227.0V239.0Q153.9 241.0 152.0 241.0H150.0Z"><title>Nim: 0.9 s</title></path><text class="value" x="159.9" y="238">0.9 s</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H586.0Q590.0 255.0 590.0 259.0V267.0Q590.0 271.0 586.0 271.0H150.0Z"><title>Spinel: 104.0 s</title></path><text class="value" x="596.0" y="268">104.0 s</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Lines of code</h3> |
| +<p class="note">Non-blank lines of each app's own code. Tests are not counted.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Lines of code"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H370.7Q370.7 15.0 370.7 15.0V31.0Q370.7 31.0 370.7 31.0H150.0Z"><title>Go: server 665</title></path><path class="s2" d="M372.7 15.0H398.2Q398.2 15.0 398.2 15.0V31.0Q398.2 31.0 398.2 31.0H372.7Z"><title>Go: templates 83</title></path><path class="s3" d="M400.2 15.0H438.0Q442.0 15.0 442.0 19.0V27.0Q442.0 31.0 438.0 31.0H400.2Z"><title>Go: JavaScript 132</title></path><text class="value" x="448.0" y="28">880</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H353.7Q353.7 45.0 353.7 45.0V61.0Q353.7 61.0 353.7 61.0H150.0Z"><title>Go + Rastrillo: server 614</title></path><path class="s2" d="M355.7 45.0H387.6Q387.6 45.0 387.6 45.0V61.0Q387.6 61.0 387.6 61.0H355.7Z"><title>Go + Rastrillo: templates 102</title></path><path class="s3" d="M389.6 45.0H412.1Q416.1 45.0 416.1 49.0V57.0Q416.1 61.0 412.1 61.0H389.6Z"><title>Go + Rastrillo: JavaScript 86</title></path><text class="value" x="422.1" y="58">802</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H382.9Q382.9 75.0 382.9 75.0V91.0Q382.9 91.0 382.9 91.0H150.0Z"><title>Rust: server 702</title></path><path class="s3" d="M386.9 75.0H418.4Q422.4 75.0 422.4 79.0V87.0Q422.4 91.0 418.4 91.0H386.9Z"><title>Rust: JavaScript 113</title></path><text class="value" x="426.4" y="88">815</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H469.9Q469.9 105.0 469.9 105.0V121.0Q469.9 121.0 469.9 121.0H150.0Z"><title>Zig: server 964</title></path><path class="s3" d="M473.9 105.0H499.1Q503.1 105.0 503.1 109.0V117.0Q503.1 121.0 499.1 121.0H473.9Z"><title>Zig: JavaScript 94</title></path><text class="value" x="507.1" y="118">1,058</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H348.1Q348.1 135.0 348.1 135.0V151.0Q348.1 151.0 348.1 151.0H150.0Z"><title>OCaml: server 597</title></path><path class="s3" d="M352.1 135.0H379.3Q383.3 135.0 383.3 139.0V147.0Q383.3 151.0 379.3 151.0H352.1Z"><title>OCaml: JavaScript 100</title></path><text class="value" x="387.3" y="148">697</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H377.3Q377.3 165.0 377.3 165.0V181.0Q377.3 181.0 377.3 181.0H150.0Z"><title>Haskell: server 685</title></path><path class="s3" d="M381.3 165.0H412.8Q416.8 165.0 416.8 169.0V177.0Q416.8 181.0 412.8 181.0H381.3Z"><title>Haskell: JavaScript 113</title></path><text class="value" x="420.8" y="178">798</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H565.1Q565.1 195.0 565.1 195.0V211.0Q565.1 211.0 565.1 211.0H150.0Z"><title>Swift: server 1,251</title></path><path class="s3" d="M569.1 195.0H588.0Q592.0 195.0 592.0 199.0V207.0Q592.0 211.0 588.0 211.0H569.1Z"><title>Swift: JavaScript 75</title></path><text class="value" x="596.0" y="208">1,326</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H421.8Q421.8 225.0 421.8 225.0V241.0Q421.8 241.0 421.8 241.0H150.0Z"><title>Nim: server 819</title></path><path class="s3" d="M425.8 225.0H456.9Q460.9 225.0 460.9 229.0V237.0Q460.9 241.0 456.9 241.0H425.8Z"><title>Nim: JavaScript 112</title></path><text class="value" x="464.9" y="238">931</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H314.3Q314.3 255.0 314.3 255.0V271.0Q314.3 271.0 314.3 271.0H150.0Z"><title>Spinel: server 495</title></path><path class="s3" d="M318.3 255.0H335.8Q339.8 255.0 339.8 259.0V267.0Q339.8 271.0 335.8 271.0H318.3Z"><title>Spinel: JavaScript 71</title></path><text class="value" x="343.8" y="268">566</text></svg> |
| +<ul class="legend"><li style="--k: var(--s1)">server</li><li style="--k: var(--s2)">templates</li><li style="--k: var(--s3)">JavaScript</li></ul> |
| +<div class="scroll"><table><thead><tr><th>Setup</th><th>server</th><th>templates</th><th>JavaScript</th></tr></thead><tbody><tr><td>Go</td><td>665</td><td>83</td><td>132</td></tr><tr><td>Go + Rastrillo</td><td>614</td><td>102</td><td>86</td></tr><tr><td>Rust</td><td>702</td><td>0</td><td>113</td></tr><tr><td>Zig</td><td>964</td><td>0</td><td>94</td></tr><tr><td>OCaml</td><td>597</td><td>0</td><td>100</td></tr><tr><td>Haskell</td><td>685</td><td>0</td><td>113</td></tr><tr><td>Swift</td><td>1,251</td><td>0</td><td>75</td></tr><tr><td>Nim</td><td>819</td><td>0</td><td>112</td></tr><tr><td>Spinel</td><td>495</td><td>0</td><td>71</td></tr> |
| +</tbody></table></div> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Cost to build</h3> |
| +<p class="note">What Opus 5.5 cost at list price for the build and its fix rounds.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Cost to build"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H297.6Q301.6 15.0 301.6 19.0V27.0Q301.6 31.0 297.6 31.0H150.0Z"><title>Go: $3.69</title></path><text class="value" x="307.6" y="28">$3.69</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H337.9Q341.9 45.0 341.9 49.0V57.0Q341.9 61.0 337.9 61.0H150.0Z"><title>Go + Rastrillo: $4.67</title></path><text class="value" x="347.9" y="58">$4.67</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H266.0Q270.0 75.0 270.0 79.0V87.0Q270.0 91.0 266.0 91.0H150.0Z"><title>Rust: $2.92</title></path><text class="value" x="276.0" y="88">$2.92</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H380.9Q384.9 105.0 384.9 109.0V117.0Q384.9 121.0 380.9 121.0H150.0Z"><title>Zig: $5.71</title></path><text class="value" x="390.9" y="118">$5.71</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H460.3Q464.3 135.0 464.3 139.0V147.0Q464.3 151.0 460.3 151.0H150.0Z"><title>OCaml: $7.65</title></path><text class="value" x="470.3" y="148">$7.65</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H353.9Q357.9 165.0 357.9 169.0V177.0Q357.9 181.0 353.9 181.0H150.0Z"><title>Haskell: $5.06</title></path><text class="value" x="363.9" y="178">$5.06</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H335.0Q339.0 195.0 339.0 199.0V207.0Q339.0 211.0 335.0 211.0H150.0Z"><title>Swift: $4.60</title></path><text class="value" x="345.0" y="208">$4.60</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H345.1Q349.1 225.0 349.1 229.0V237.0Q349.1 241.0 345.1 241.0H150.0Z"><title>Nim: $4.84</title></path><text class="value" x="355.1" y="238">$4.84</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H586.0Q590.0 255.0 590.0 259.0V267.0Q590.0 271.0 586.0 271.0H150.0Z"><title>Spinel: $10.70</title></path><text class="value" x="596.0" y="268">$10.70</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Tokens written to build</h3> |
| +<p class="note">Output tokens Opus 5.5 produced for the build and its fix rounds.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Tokens written to build"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H385.1Q389.1 15.0 389.1 19.0V27.0Q389.1 31.0 385.1 31.0H150.0Z"><title>Go: 65,224</title></path><text class="value" x="395.1" y="28">65,224</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H374.0Q378.0 45.0 378.0 49.0V57.0Q378.0 61.0 374.0 61.0H150.0Z"><title>Go + Rastrillo: 62,185</title></path><text class="value" x="384.0" y="58">62,185</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H340.8Q344.8 75.0 344.8 79.0V87.0Q344.8 91.0 340.8 91.0H150.0Z"><title>Rust: 53,143</title></path><text class="value" x="350.8" y="88">53,143</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H422.1Q426.1 105.0 426.1 109.0V117.0Q426.1 121.0 422.1 121.0H150.0Z"><title>Zig: 75,295</title></path><text class="value" x="432.1" y="118">75,295</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H540.7Q544.7 135.0 544.7 139.0V147.0Q544.7 151.0 540.7 151.0H150.0Z"><title>OCaml: 107,648</title></path><text class="value" x="550.7" y="148">107,648</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H460.4Q464.4 165.0 464.4 169.0V177.0Q464.4 181.0 460.4 181.0H150.0Z"><title>Haskell: 85,749</title></path><text class="value" x="470.4" y="178">85,749</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H420.4Q424.4 195.0 424.4 199.0V207.0Q424.4 211.0 420.4 211.0H150.0Z"><title>Swift: 74,844</title></path><text class="value" x="430.4" y="208">74,844</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H416.1Q420.1 225.0 420.1 229.0V237.0Q420.1 241.0 416.1 241.0H150.0Z"><title>Nim: 73,658</title></path><text class="value" x="426.1" y="238">73,658</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H586.0Q590.0 255.0 590.0 259.0V267.0Q590.0 271.0 586.0 271.0H150.0Z"><title>Spinel: 120,010</title></path><text class="value" x="596.0" y="268">120,010</text></svg> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Readability</h3> |
| +<p class="note">A blind score out of 10 from Fable, a different model from the builder. Median of three readings.</p> |
| +<svg class="chart" viewBox="0 0 680 286" role="img" aria-label="Readability"><line class="axis" x1="150" x2="150" y1="8" y2="278"/><text class="label" x="140" y="28">Go</text><path class="s1" d="M150.0 15.0H537.1Q541.1 15.0 541.1 19.0V27.0Q541.1 31.0 537.1 31.0H150.0Z"><title>Go: 8 / 10</title></path><text class="value" x="547.1" y="28">8 / 10</text><text class="label" x="140" y="58">Go + Rastrillo</text><path class="s1" d="M150.0 45.0H537.1Q541.1 45.0 541.1 49.0V57.0Q541.1 61.0 537.1 61.0H150.0Z"><title>Go + Rastrillo: 8 / 10</title></path><text class="value" x="547.1" y="58">8 / 10</text><text class="label" x="140" y="88">Rust</text><path class="s1" d="M150.0 75.0H537.1Q541.1 75.0 541.1 79.0V87.0Q541.1 91.0 537.1 91.0H150.0Z"><title>Rust: 8 / 10</title></path><text class="value" x="547.1" y="88">8 / 10</text><text class="label" x="140" y="118">Zig</text><path class="s1" d="M150.0 105.0H586.0Q590.0 105.0 590.0 109.0V117.0Q590.0 121.0 586.0 121.0H150.0Z"><title>Zig: 9 / 10</title></path><text class="value" x="596.0" y="118">9 / 10</text><text class="label" x="140" y="148">OCaml</text><path class="s1" d="M150.0 135.0H537.1Q541.1 135.0 541.1 139.0V147.0Q541.1 151.0 537.1 151.0H150.0Z"><title>OCaml: 8 / 10</title></path><text class="value" x="547.1" y="148">8 / 10</text><text class="label" x="140" y="178">Haskell</text><path class="s1" d="M150.0 165.0H537.1Q541.1 165.0 541.1 169.0V177.0Q541.1 181.0 537.1 181.0H150.0Z"><title>Haskell: 8 / 10</title></path><text class="value" x="547.1" y="178">8 / 10</text><text class="label" x="140" y="208">Swift</text><path class="s1" d="M150.0 195.0H537.1Q541.1 195.0 541.1 199.0V207.0Q541.1 211.0 537.1 211.0H150.0Z"><title>Swift: 8 / 10</title></path><text class="value" x="547.1" y="208">8 / 10</text><text class="label" x="140" y="238">Nim</text><path class="s1" d="M150.0 225.0H537.1Q541.1 225.0 541.1 229.0V237.0Q541.1 241.0 537.1 241.0H150.0Z"><title>Nim: 8 / 10</title></path><text class="value" x="547.1" y="238">8 / 10</text><text class="label" x="140" y="268">Spinel</text><path class="s1" d="M150.0 255.0H488.2Q492.2 255.0 492.2 259.0V267.0Q492.2 271.0 488.2 271.0H150.0Z"><title>Spinel: 7 / 10</title></path><text class="value" x="498.2" y="268">7 / 10</text></svg> |
| +</div> |
| + |
| + |
| +<h2>The apps</h2> |
| + |
| +<div class="card"> |
| +<h3>Go</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-go-2.bfj.oncarlos.com">https://todo-go-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/go">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/go</a></dd><dt>Stack</dt><dd>Go's standard web server and templates, with SQLite through modernc.org/sqlite (pure Go).</dd><dt>Readability</dt><dd>Strongest: comments consistently explain why (migration policy, single-writer pool, Origin check, focusout during swap), and the package docs state the security model up front. Request flow is short: route in New -> itemAction -> action -> Store method -> render "list". Error path is explicit via ErrNotFound and fail, with buffered rendering for clean 500s. Weakest: action/itemAction wrappers with inline closures and two differing callback signatures add a layer to step through; `one`, `w`/`r` fields and `bumpMarker` are terse or odd. loadStatic ignores fs.Sub and WalkDir errors, and the view struct mixes filter state, counts and Version. app.js swap/draft restoration is dense.</dd><dt>Left after review</dt><dd><ul><li>[spec] [apps/go/main.go:114](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/go/apps/go/main.go:114): `checkLoopback` accepts any loopback address, including `127.0.0.2` and `::1`. The brief explicitly requires binding only to `127.0.0.1`. These alternative `--listen` addresses pass validation and start a listener; restrict validation to the required address.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Go + Rastrillo</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-go-rastrillo-2.bfj.oncarlos.com">https://todo-go-rastrillo-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/go-rastrillo">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/go-rastrillo</a></dd><dt>Stack</dt><dd>Rastrillo 0.28, the CARLOS web framework: chi for routing and GORM over SQLite.</dd><dt>Readability</dt><dd>Strongest: comments consistently explain why (Resolve vs Run and DBPath reset, the 1 MiB form cap preventing an over-long edit reading as delete, originCheck vs csrf.Protect, toggleAll as one statement). The owned()/afterItemWrite/done seam makes a toggle easy to follow from route to SQL to fragment or 303. Weakest: renderStatus swallows template errors without logging while fail() logs with a ref, so one 500 path is silent; several discarded second returns (sessions.UserID, ParseInt) rely on reader trust in middleware; App/Router/Mux is a thin three-layer wrapper and ErrorPage/errorPage differ only by case; the "make bump edits this line" aside is noise.</dd><dt>Left after review</dt><dd><ul><li>[correctness] `apps/go-rastrillo/internal/todo/static/app.js:42` — Every action response replaces the entire app, preserving only the new-todo input. With a request pending, double-click another todo and start editing: when the earlier response arrives, the editor disappears and its unsaved text is lost. Preserve the active editor’s value and focus across replacement, or defer replacement until editing finishes.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Rust</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-rust-2.bfj.oncarlos.com">https://todo-rust-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/rust">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/rust</a></dd><dt>Stack</dt><dd>axum on tokio, Maud for HTML, and rusqlite with SQLite built in.</dd><dt>Readability</dt><dd>Strongest: the `act` helper gives every action one pipeline (resolve visitor, transaction, 404/303/fragment), so a request is easy to follow from route to db to markup; comments explain real why (body-read before 403 behind the edge, mutex over a pool, append-only migrations, editing li preservation). db.rs is one query per function with scoped list ids. Weakest: handler params are single letters (s, h, q, f) and the `found`/`Outcome` indirection adds a hop; `swap` in app.js is dense DOM surgery that needs careful reading; errors are only rusqlite wrapped in AppError and logged with eprintln, with a few `expect`s standing in for proper failure paths.</dd><dt>Left after review</dt><dd><ul><li>[correctness] [apps/rust/static/app.js:10](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/static/app.js:10): Filter navigation can discard pending changes. Requests wait in a promise queue, but filter links navigate immediately. With a toggle request pending, edit another todo and click a filter: blur queues the save, then navigation destroys the queue before that save reaches the server. Wait for pending actions before navigating.</li><li>[spec] [apps/rust/src/main.rs:45](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/rust/apps/rust/src/main.rs:45): `--listen` accepts every loopback address, including `::1` and `127.0.0.2`. The brief explicitly restricts binding to `127.0.0.1`. Validate that exact address.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Zig</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-zig-2.bfj.oncarlos.com">https://todo-zig-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/zig">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/zig</a></dd><dt>Stack</dt><dd>Zig's standard-library HTTP server, with SQLite 3.54 compiled in.</dd><dt>Readability</dt><dd>Strongest: a request is trivially traceable (app.js form → handle → post → itemAction → Store.toggle → page/303), helped by the Response/Visitor/Form structs and tight module split. Comments earn their place: why headers are copied before reading the body, why keep-alive drops after an oversized body, why a single mutexed SQLite connection. Tests double as documentation. Weakest: errors in db.zig collapse to one opaque error.Sqlite with logging as the only detail; readerExpectContinue's failure is remapped to WriteFailed, hiding the cause; post() mixes routing and handling in one long if-chain, and get() parses the query inline. Minor: static() and run() names are terse.</dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>OCaml</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-ocaml-2.bfj.oncarlos.com">https://todo-ocaml-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/ocaml">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/ocaml</a></dd><dt>Stack</dt><dd>OCaml 5 with Eio and cohttp-eio, and sqlite3-ocaml over SQLite 3.54.</dd><dt>Readability</dt><dd>Strongest: a toggle is trivially traceable (handle → action_of_path → post → perform → Store.toggle → View.fragment or 303), helped by App being server-independent and each module having one clear job. Comments are nearly all 'why' (Budget rationale, cookie age, migration rule, CSP hash, JS render invariants) with no noise. Weakest: handle's `filter p <> None ... Option.get (filter p)` double evaluation is clunky and hides intent; Store errors surface only as failwith exceptions caught by run_server's generic logger, and user_version's exec result is ignored; main.ml's Budget/connection plumbing and nested try blocks take effort to follow; a few terse names (change, shows, done_, go) need a moment.</dd><dt>Left after review</dt><dd><ul><li>[correctness] `apps/ocaml/static/app.js:14` — Filter navigation can discard queued edits. With a toggle request pending, edit another item and click “Active”: blur queues the save, but the ordinary filter link immediately unloads the page before that save runs. The changed title is lost. Wait for pending mutations before navigating between filters.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Haskell</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-haskell-2.bfj.oncarlos.com">https://todo-haskell-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/haskell">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/haskell</a></dd><dt>Stack</dt><dd>warp and WAI, Lucid for HTML, and sqlite-simple.</dd><dt>Readability</dt><dd>Strongest: tracing a request is effortless. The toggle route goes from the pathInfo case through the `post` helper to Db.toggleTodo and back to Html.fragment, with the Bool-means-found contract stated at each end. Comments consistently explain why (WAL via query_, getrandom over /dev, single connection, the `swapping` flag) without restating code. Weakest: error handling is mostly implicit. Database exceptions reach Warp's default 500 with no logging; migrate silently assumes version 0 on an odd PRAGMA result; the getrandom loop retries on any negative result, not only EINTR; app.js hides every failure behind location.reload(). Minor: `app`'s where-block is long and the multiway-if in `post` is dense; `common`, `with`, `f`, `v` are terse names.</dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Swift</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-swift-2.bfj.oncarlos.com">https://todo-swift-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/swift">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/swift</a></dd><dt>Stack</dt><dd>Hummingbird 2 on SwiftNIO, with SQLiteNIO.</dd><dt>Readability</dt><dd>Strongest: a toggle is trivially traceable (route closure → `action` → `Store.toggle` → `changed` → `Page.body`), each file has one clear job, and comments explain decisions (single SQLite connection, append-only migrations, 400-day cookie, the partial header) without restating code. Weakest: `OriginCheck` lives in Visitors.swift and `trimmingWhitespace` is split across Routes.swift and Store.swift; `action` is a generic name for the central request helper; `Store.todos` silently defaults malformed rows (`?? 0`, `?? ""`) rather than failing; a few `try?` cleanups are justified but unlogged, and the logger is otherwise unused. `bumpNote` is build tooling leaking into app code, though documented.</dd><dt>Left after review</dt><dd><ul><li>[correctness] [apps/swift/static/app.js:32](/home/paulca/amadan.net/carlos/todomvc-bakeoff/.worktrees/swift/apps/swift/static/app.js:32): Every action response removes the entire list, including any active editor. Toggle one item, then start editing another before the response arrives: the replacement discards the draft and focus without saving. Serializing requests does not protect unsent edits. Preserve active editor contents and focus across updates, or defer replacement until editing finishes.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Nim</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-nim-2.bfj.oncarlos.com">https://todo-nim-2.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/nim">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/nim</a></dd><dt>Stack</dt><dd>Nim's standard-library async HTTP server (a patched copy, to serve a Unix socket and cap request bodies), with SQLite through db_connector.</dd><dt>Readability</dt><dd>Strongest: comments explain intent (titleSpaces, maxBody sizing, the chunked-body patch, trimTitle's defect risk) and nothing else; a request is easy to follow from route through action into a single prepared statement in store.nim and back via renderMain. Clear module split with the vendored httpserver marked and its one change flagged. Weakest: action parses paths by splitting and indexing with a `found` flag and a `-1` id sentinel, which is denser than the rest; one-letter names (r, k, st, h) and the Reply-threading through `listFor` take a moment; several SQLite return codes are discarded without a word; page.nimf is included rather than imported, so view rendering is hidden from this file layout.</dd><dt>Left after review</dt><dd><ul><li>[correctness] `apps/nim/src/store.nim:181` — `$column_text(st, 1)` reads a NUL-terminated C string, although titles are stored with an explicit byte length. Posting `title=before%00after` therefore displays only `before`; saving that edit permanently loses the suffix. Read the value using `column_bytes` so accepted Unicode titles survive retrieval intact.</li></ul></dd> |
| +</dl> |
| +</div> |
| + |
| +<div class="card"> |
| +<h3>Spinel</h3> |
| +<dl><dt>Live app</dt><dd><a href="https://todo-spinel-4.bfj.oncarlos.com">https://todo-spinel-4.bfj.oncarlos.com</a></dd><dt>Source</dt><dd><a href="https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/spinel">https://amadan.net/carlos/todomvc-bakeoff?ref=main&path=apps/spinel</a></dd><dt>Stack</dt><dd>tep, a Sinatra-style layer, compiled to a native program by Spinel, with SQLite 3.50 compiled in.</dd><dt>Readability</dt><dd>Strongest: traceability. Each route reads top to bottom: origin check, visitor, path_id, todo_exists, one inline SQL statement, finish_post, render_lists. Comments explain the non-obvious (Spinel subset, single-cursor shim, token-issuance check, BUILD_NOTE). Weakest: errors. Write paths (run_sql, add_todo, rename_todo, record_token) discard DB.step's result, so a failed INSERT or UPDATE silently returns a stale fragment; the JS catch-all reload hides failures too. Lesser points: parallel arrays with a `done` field of ints, generic run_sql with a magic `id > 0` bind rule, layout/page/path_id filed under the Actions divider, and unexplained constants (18 and 19 character limits).</dd><dt>Left after review</dt><dd><ul><li>[correctness] `apps/spinel/app.rb:475` and `:527` — `strip` only removes ASCII whitespace. A title containing only U+00A0 or U+2003 creates a blank-looking todo, and editing to that value does not delete it. Apply Unicode whitespace trimming before the empty-title check.</li><li>[correctness] `apps/spinel/patches/2-tep-bakeoff.patch:330` — URL decoding turns `%00` into a NUL inside a C string, which is then truncated when copied into Ruby. Posting `title=before%00after` silently stores `before`; editing with `title=%00after` deletes the todo. Preserve decoded string lengths so input cannot silently become a different title or action.</li><li>[spec] `apps/spinel/Makefile:130` — `clean-deps` deletes only the local SQLite and tep artifacts. The arm64 build still links precompiled OpenSSL, zlib and zstd libraries from the shared toolchain root (`scripts/arm64-cc:21`), so the next build does not compile every third-party library from scratch as required. Build those dependencies into the app’s own cache and include them in `clean-deps`.</li></ul></dd><dt>Note</dt><dd>Its server process exited 16 times during the load test, and Carloku started it again each time. Requests that arrived meanwhile got an error.</dd> |
| +</dl> |
| +</div> |
| + |
| + |
| +<h2>How we measured</h2> |
| +<ul><li><strong>The same starting point.</strong> Every builder got the same brief, the same rules for routes and forms, TodoMVC's own page and styles, and the same tests. It chose its own libraries.<li><strong>The same tests.</strong> TodoMVC's 29 standard tests, run with JavaScript on and again with it off, plus checks for unsafe input, foreign characters, very long titles, one visitor reaching another's list, and a screenshot comparison with TodoMVC's own page.<li><strong>One build each.</strong> Opus 5.5 built each app in a single unattended session, with a four-hour limit.<li><strong>One round of review.</strong> Astra reviewed each app for bugs, missed requirements and security problems, and the same builder fixed what it found. A second round was allowed only for security problems; Zig and Nim needed one.<li><strong>Readability.</strong> Fable scored each app's code out of 10 three times, without knowing which app it was. We show the median.<li><strong>Speed.</strong> Measured from one test machine over the internet, one app at a time, each on a fresh copy of the app with an empty database. Time to first byte is the median of 30 page loads. The load test doubles the number of simultaneous connections from 1 upwards, 10 seconds per step, first loading a 50-item list, then ticking a to-do on and off, then loading the list again.<li><strong>Waking up.</strong> Carloku puts an app to sleep after 15 quiet minutes. We timed the first request after each app fell asleep, three times.<li><strong>Builds.</strong> Every app is built for the 64-bit ARM servers Carloku runs on. A cold build compiles every library from scratch, once per app. A warm build and a one-line change are each the median of three.<li><strong>Cost.</strong> What each build and its fixes cost at list price, from the builders' own session records.</ul> |
| + |
| +<h2>What this does and doesn't show</h2> |
| +<ul><li><strong>The network is in every speed number.</strong> Requests travelled from our test machine to Carloku over the internet, and the base delay swung between about 17 and 37 milliseconds during the runs. Small differences in first-byte times mean little.<li><strong>Carloku caps traffic at the edge.</strong> When a request reaches an edge server that isn't the one running the app, Carloku lets at most 64 such requests be in flight at once and turns the rest away with a "try again shortly" response. Above 64 connections, the load test partly measures that limit, and whether it applied depended on how our traffic was routed at the time.<li><strong>Every app ran on Carloku's default size.</strong> Throughput limits reflect that size, not what each language could do on a bigger server.<li><strong>One build per language.</strong> Another session with the same model could produce a different app, faster or slower.<li><strong>Readability scores cluster.</strong> Most apps scored 8 out of 10. Treat the score as a rough signal.<li><strong>Three apps were built under emulation.</strong> OCaml, Haskell and Spinel had no straightforward way to compile for ARM from our x86 machine, so they were built inside an emulated ARM system. Their build times are much longer for that reason, and don't compare with the others.<li><strong>Spinel restarted under heavy load.</strong> Its server process exited during the load test and Carloku started it again each time.<li><strong>Mojo was dropped.</strong> Mojo can't produce a single self-contained program file, which is what Carloku runs, so we replaced it with Nim. The platform question is filed as <a href="https://amadan.net/carlos/platform/-/discussion/360">an idea</a>.<li><strong>Fresh copies for measuring.</strong> To give every app an empty database, each was measured on a new copy of itself, which is the one the live links point to.</ul> |
| + |
| +</main> |
| +</body> |
| +</html> |