Over the hackathon's 72-hour window — on top of a preparation period the rules explicitly allowed beforehand — I optimised four programs for Speed Demon 2026. The overall score went from 28.468 to 78.149 — a 175% increase. Every point came from the live board, and I have the measurements to show for it.
But this post is not about the wins. It is about the eight changes I built, verified, measured, and then deleted — because those are the ones that taught me what actually moves a score, and because a submission that only reports its wins is telling you half the story.
Here is the single most useful thing I learned, and it took me most of the window to accept it:
Adding mechanism almost never paid. Deleting code almost always did.
I'll show you the numbers.
Scope of the window. The rules allowed design, specification and environment preparation before the clock started, and all four tracks were planned to module level in that period. A correctness-first version of each program existed from before the window; the version described here was rewritten, measured and frozen inside the 72 hours, and that is where the score went from 28.468 to 78.149. The plan is in the repository, including the places measurement proved it wrong.
What the thing is
Four independent tasks, four standalone Rust programs, one source file each, no crates, no dependencies, no network in the build. The build is rustc on a single file inside a digest-pinned container.
| Track | What it does | Input | |
|---|---|---|---|
| 🅰 | Ingest | hourly percentile aggregation | 239 MB access log |
| 🅱 | Index | 50,000 boolean + phrase queries | 254 MB corpus |
| 🅲 | Join | hostile CSV → country aggregates | 202 MB |
| 🅳 | Traverse | 2,000 exact shortest paths | 5 M edges |
The score is a product of four independent axes:
K = (T_ref / T)^0.50 · (M_ref / M)^0.20 · (S_ref / S)^0.15 · (D_ref / D)^0.15 points = 50 · log10(K) overall = geometric mean of the best three tracks And the cost of one point on each axis is wildly different:
| Axis | What one point costs | |
|---|---|---|
| Time | 8.8% faster | the ordinary target |
| Memory | 21% less | expensive |
| Source | 26% fewer lines | the most expensive — and the most reliable |
That table is the whole strategy. Source is the most expensive axis to buy and the easiest to improve, because it rewards deletion. Once I understood that, the project changed.
flowchart LR I["an idea"] --> B["build it"] B --> V{"exact?<br/>full corpus × 5<br/>+ tiny oracle<br/>+ official sample"} V -->|"no"| F["find the bug"] F --> V V -->|"yes"| M["measure it<br/>interleaved, smallest of N"] M --> P{"does it pay<br/>on the board?"} P -->|"yes"| K["keep it"] P -->|"no"| R["<b>delete it</b><br/>and write down why"] K --> L["PERF.md"] R --> L L --> I The delete branch is the point. A losing change gets removed from the source, not merely superseded — because final scoring measures the frozen commit, and the board's "keep the best" rule does not transfer to it.
Where it finished
┌──────────────────────────────────────────────────────────────────────────┐ │ TRACK TIME MEMORY SOURCE BOARD POINTS │ │ 🅰 Ingest 26.0 ms 19 MB 626 lines 80.046 │ │ 🅱 Index 140.0 ms 47 MB 708 lines 75.124 │ │ 🅲 Join 24.0 ms 9 MB 721 lines 79.370 │ │ 🅳 Traverse 170.0 ms 71 MB 536 lines 68.589 │ ├──────────────────────────────────────────────────────────────────────────┤ │ OVERALL (best three, geometric mean) ............... 78.149 │ │ Starting point ..................................... 28.468 │ │ Wrong answers ever published ....................... zero │ └──────────────────────────────────────────────────────────────────────────┘ Now the failures.
I believed build flags were the difference, so I tried target-cpu=native
Every track was ~1.2× slower than the fastest entry. The gap was 61–102% time on every track, across four different algorithms in two languages. That looks systemic, so I tried the systemic fix:
rustc -C target-cpu=native ... Result: slower on all four tracks.
| Track | x86-64-v3 | native | Change |
|---|---|---|---|
| 🅰 | 0.02653 | 0.02710 | +2.1% |
| 🅱 | 0.17049 | 0.17524 | +2.8% |
| 🅲 | 0.03541 | 0.03561 | +0.6% |
| 🅳 | 0.16986 | 0.18123 | +6.7% |
I reverted it and moved on, but I didn't understand why, and an unexplained measurement is a hypothesis, not a conclusion.
The answer came much later, and it is the best single fact I learned about this competition:
The machine that compiles is not the machine that runs.
The board's build host has AVX-512. The scoring CPU is a Core i5-13500, Alder Lake, whose AVX-512 is fused off. So native resolved against the builder and emitted instructions the scoring machine did not want.
I later switched to naming the target explicitly — and the board measured that worse too, so I reverted that as well:
A 80.046 → 79.716 C 79.370 → 79.142 B 75.124 → 72.908 D 68.589 → 67.822 Twice on the same axis, theory and measurement disagreed, and both times measurement won. x86-64-v3 is what every banked best was measured with, so that is what got frozen.
I believed lto = fat and a smarter probe would pay. Both lost.
Two more experiments that looked obviously right:
| Experiment | Expected | Measured | |
|---|---|---|---|
lto = fat + embed-bitcode | cross-unit inlining | +12.5% slower | at codegen-units=1 there is no cross-unit LTO left to do |
| Grouped four-slot term probe | a big scan win | scan +20%, total +14% | our table grows at 50% load, so a probe ends at the first or second slot — and a scalar compare beats a 32-byte load plus three AVX2 ops |
The second one already told me something important: if skipping the hash and probe entirely for most tokens doesn't help, the term lookup is not the bottleneck. I wrote that down and it saved me from two later dead ends.
I believed faster labels meant fewer bytes. They meant more lines.
Our Track D's own PERF.md named the open question: "the attack is the label and heap data structures, not the search size." So I built 16-bit saturating distance labels with a 64-bit fallback, verified 99.7% of our answers fit in 16 bits before writing a line, and implemented it.
It was exact on every net — full corpus, tiny hostile oracle, official sample, 120 random fuzz graphs, 48,000 logic queries.
And the board said:
D before: T 0.16986 M 70.57 MB S 536 -> 68.589 D after: T 0.19356 M 73.31 MB S 658 -> 66.338 +13.9% +3.9% +122 lines Slower, heavier, and 122 more source lines. Here is why, and it is the most transferable lesson in this post:
The exactness rule requires a second search body for the queries the narrow store cannot hold — a duplicate of the entire relaxation loop. That fixed cost is charged to every query, while the narrower labels only pay off on queries long enough for label cache lines to matter. On a corpus whose median answer is 1207, the cheap half of the trade never arrives.
And on this track source is the expensive axis (26.4% of lines per point against 8.8% of time), so the duplicate loop cost more than the label width saved.
The bug I made while writing it is worth showing, because the hash gate caught it:
// WRONG -- an unvisited rival label is infinity, so a plain add wraps // to nd - 1 in release mode: a spurious meeting that stops the search early. best = best.min(nd + other[0].get(v)); // RIGHT best = best.min(nd.saturating_add(other[0].get(v))); One character. The published hash caught it on the tiny hostile oracle, the official sample and the full corpus.
I believed I should measure locally and move on
I ran three experiments that "obviously" help an I/O-bound parser, and the machine disagreed with all three:
| Experiment | Why I expected it | Measured |
|---|---|---|
MADV_POPULATE_WRITE on the output map | fewer page faults while formatting | 0.039 → 0.069 s |
MADV_SEQUENTIAL on the 239 MB log | better readahead | parse 0.0289 vs 0.0233 s |
| Dropping the corpus pages after the scan | less pressure | inside the noise floor, and 16 certain source lines |
Then I learned the reason local measurement kept misleading me. Run-to-run spread on time reaches 16% on identical code. My rules became:
- Interleave the variants. Alternating A/B/A/B, not one block then another.
- Trust the smallest, not the mean. A loaded machine can only make a run slower.
- Measure the phase, not the total. A total tells you a change helped. It never tells you why.
That third rule produced the single most valuable measurement in the project.
I believed Track A was a memory problem. Measurement said parser.
Every idea I had for Track A had been about memory: hold fewer records, release chunks sooner, drop pages after use. Then I added phase laps and got this:
🅰 parse 50.8% ← the parser is over half the track hours 24.4% text 22.0% rank 2.8% size+map 0.6% Nobody had measured that. I had been optimising the wrong half of the program. The same instrumentation then named the other bottlenecks:
The measured bottlenecks, one per track
flowchart LR A["🅰 parse 50.8%<br>24.4% hours, 22.0% text"] --> B["🅱 corpus scan 70%"] B --> C["🅲 every record,<br>three random reads each"] C --> D["🅳 construction 66%<br>search only 33%"] And the plan had missed all of them. Three of the four tracks had to be re-attributed by measurement before they could be made faster — which is why the repository keeps its pre-build plan, including the places it was wrong.
What actually paid: four deletions
Every large gain was a removal. Not one was an added mechanism.
| Track | Change | Result |
|---|---|---|
| 🅰 | cut the parser and formatter from 704 → 440 lines | first big jump |
| 🅰 | drop(parts) before the output map, not after | +1.20 points, memory 31.4 → 23.94 MB |
| 🅱 | bucketed u16 build, then the class-renumber rewrite | 61.65 → 72.62, memory 219 → 49.7 MB |
| 🅲 | delete the pre-pass; Phase claim primitive; 1 MB name table | 66.81 → 74.02, lines 1105 → 652 |
| 🅳 | restore PR_SET_THP_DISABLE | +25 MB recovered |
And the two rewrites that moved the board furthest both got faster and smaller:
🅱 72.829 → 75.124 (+2.295) time −17% 683 → 644 lines 🅲 75.345 → 79.370 (+4.025) time −32% 610 → 582 lines Track C is the clearest example. Instead of walking each CSV record field by field, the input is classified: each 64-byte block becomes quote, comma and LF masks at once, and the "inside quotes" state falls out of one carry-less multiply computing the prefix XOR of the quote mask:
let q = mask64(lo, hi, b'"'); let inq = _mm_cvtsi128_si64( _mm_clmulepi64_si128::<0>(_mm_cvtsi64_si128(q as i64), _mm_set1_epi8(-1)) ) as u64 ^ inside; inside = ((inq as i64) >> 63) as u64; That one idea replaces the entire quoted-field scanner — and it deletes the code that used to do it. A "" pair turns quoting off and on again with no special case at all.
Track B's rewrite is the same story on a different axis: the term table went from a 32-byte lowercased vector with a byte comparison per probe, to a 64-bit hash built from bzhi-masked 8-byte loads. The keys array is gone. The byte compare is gone. Smaller and faster at the same time.
I believed one corpus was enough
It isn't, and the way you find out is a bug that passes one and fails another. The gate became:
1. FULL CORPUS 5 runs, each sha256-hashed every run must equal the pinned expected value 2. TINY HOSTILE ORACLE hand-built boundary cases 3. OFFICIAL SAMPLE the organizers' published sample Five runs, not one — a schedule-dependent defect can pass a single run. And the tiny oracle covers: zero weights, parallel and duplicate edges, self-loops, isolated vertices, unreachable targets, 64-bit sums, "" escapes, bare newlines inside quoted notes, mixed CRLF/LF endings, a byte order mark before a record, s == t, and empty inputs.
Two bugs it caught that I would not have found otherwise:
// Track B: an out-of-bounds read. insert() called key() unconditionally, // and key() computes `32 - len` on a usize -> underflow for a term over 32 bytes. // Track C: my Windows fuzzer wrote corpora in text mode, so CRLF crept in. // D's number() consumes ONE byte past a number, so it ate the CR and read // the LF as the next number's first byte -> 0/40 Windows trials vs 120/120 Linux. // Fix: newline="\n" on every corpus writer. A check whose expected value you invented is not a check. Early on I compared a track against a hash I had typed by hand rather than produced from the reference. It was wrong, and removing it was the right call.
The failure that was not a failure
Late in the window a terminal showed me this in four files:
# PERF.md — Track A (Ingest) Classic UTF-8 mis-decoded as Latin-1. I reported it as broken and started writing a repair.
Then I checked instead of assuming. The files were fine. Zero damaged sequences — all four decoded as valid UTF-8 without error. The — was my terminal's codepage failing to render a 3-byte em-dash, not a defect in the files.
Rewriting correct bytes to chase a console artifact would have corrupted four files whose entire purpose is precision — and I would have reported a repair that repaired nothing.
Where the failures actually were
The formulas were the easiest part to get right, because a simulation has no stake in my being right. Every serious failure was somewhere else:
- a one-character arithmetic bug that wrapped instead of saturating
- an out-of-bounds read from a subtraction on an unsigned type
- two dependent cache misses per vertex where one would do
- consistency measured on the wrong axis — a genuine time win that cost more in source lines than it earned
- 16% run-to-run noise that made three "obvious" wins look like losses until I started interleaving
- a design assumption (that Track A's cost was memory) that the whole plan rested on and one measurement destroyed
Not one of them was an algorithm problem. They were a missing saturation, an underflow, a cache-line boundary, a scored axis I wasn't optimising, a measurement method, and an assumption nobody had tested.
Honest limits
The repository documents its own weaknesses rather than hiding them, and so does this post:
- 🅳 Track D's query queue retains its pool capacity between queries. It is emptied but not shrunk, so it holds every pushed entry including stale ones, where the heap it replaced freed on pop. That is the memory keeping it at 68.589 while its time is the best that track has ever recorded.
- 🅲 Track C identifies a name by 32 key bits plus a slot group, not full bytes. That is what makes the 4-byte slot — and the compact table and eight-wide probe — possible, and it is exact on every corpus published. The residual collision probability at this scale is documented in the source.
- 🅱 Track B identifies a term by the top 46 bits of a 64-bit hash, not a byte comparison — far below any practical collision rate at this vocabulary, but no longer exact by construction.
- 🅰 Track A's parser is unoptimised relative to what is possible. It is 50.8% of the track and it has never had the multi-stream treatment.
And the route to 80 is written down with its arithmetic, not claimed as done: with B held at 75.124, the product A × C must rise 7.27% — about 250 deleted lines across those two files, since source is the axis that has paid every time.
What I would tell you before you start
- Measure where the time is before you touch anything. One afternoon of phase laps changed this project more than a month of guessing.
- Interleave your A/B runs and trust the minimum. A loaded machine only ever makes things slower.
- Delete before you add. It is the cheapest axis and the most reliable one.
- Assume your build host is not your run host. Name the CPU explicitly.
- Verify on more than one corpus. One corpus is a guess.
- Write down what you reverted, and why. It stops you retrying the same idea later, and it is the most credible thing you can publish.
Try it
git clone https://github.com/pal-123456789/speed-demon cd speed-demon # build a track exactly as the scored path does -- rustc only, no cargo, no network sh track-a/build.sh # check correctness on the tiny hostile oracle and the official sample tooling/check-corpora.sh a summary.txt "$(cat track-a/verification/expected/summary.txt.sha256)" Each track is a complete, independent build context: its own Dockerfile, build.sh, source, README.md and PERF.md.
Repository: https://github.com/pal-123456789/speed-demon
Frozen release: https://github.com/pal-123456789/speed-demon/releases/tag/v1.0-freeze
Inside, if you want the long version:
| Read | For |
|---|---|
docs/JOURNEY.md | the full narrative, including every reversal and the two measurements I got wrong |
docs/TECHNIQUES.md | every optimisation, by track and by the axis it attacks |
docs/MEASUREMENT.md | how every number was obtained, and what is deliberately not claimed |
plan/README.md | the pre-build plan — including where measurement proved it wrong |
track-*/PERF.md | each track's own ledger, with a KNOWN / EXPECTED / REVERTED honesty legend |
Written for the Speed Demon 2026 write-up quest. Every number in this post is a live board measurement or a locally reproducible one; the eight experiments described as losses were all built, verified exact, and deleted.
