Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Match dispatch measurements

Historical dispatch experiments; see Profiling the compiler for the profiling guide and the emission reference for current behavior.

Dense-match dispatch

The 2026-10-22 B19 experiment at 9c994af0 measures dense integer and enum dispatch with 4, 16, 64 and 256 arms against the then-production comparison chains. A temporary Cranelift Switch patch produces br_table; the patch is preserved as the original experiment. The production policy addresses issue #395. Reproduction, samples and generated-fixture methodology are in examples/dense_match/.

On a Ryzen 7 7840HS, x86-64 Linux, Cranelift 0.131.1, pinned to one CPU, median wall times over seven shuffled rounds for two million dispatches were:

ArmsInteger uniform: chain / switchEnum uniform: chain / switchInteger 90%-last: chain / switch
413.1 / 15.7 ms23.0 / 26.9 ms8.5 / 9.9 ms
1616.7 / 18.4 ms27.2 / 29.2 ms11.3 / 10.5 ms
6423.1 / 18.7 ms33.9 / 30.8 ms22.6 / 10.0 ms
25648.1 / 19.4 ms84.0 / 31.2 ms68.0 / 11.0 ms

The same-session baseline and prototype runs interleave. Times include process startup, all-arm correctness checks, the loop and output; enums also include borrowed list indexing. They are not pure dispatch latency or integer-versus-enum ABI comparisons. A Python oracle checks every sequence’s checksum; integer fallbacks include the first out-of-range value and u64.MAX.

The 256-arm enum 90%-last case fell from 111.4 to 14.6 ms. First-arm-heavy cases show why distribution matters: switches regressed at 4/16/64 arms for both integers and enums; at 256 they improved from 11.7 to 9.2 ms and 18.2 to 14.6 ms. A balanced Plenty integer decision tree was slower than chains for uniform 4/16/64-arm inputs (14.7/22.9/30.2 ms), and slower than the switch at 256 (37.9 ms). Neither balanced trees nor switches should replace every chain.

At 256 arms, isolated dispatch size fell from 5,515 to 3,656 bytes for integers and 7,763 to 3,688 for enums, including table data/padding. At four arms it grew from 91 to 128 and 131 to 152 bytes. Optimized CLIF instruction counts at 256 fell from 1,284 to 521 and 1,549 to 534. Cold compile/link medians for the full uniform fixtures were 69/65 ms (integer chain/switch) and 95/99 ms (enum); these include validation/setup code and linking, with only three samples in separate batches. They establish no reliable compile-time gain.

Production policy

The production lowerer now selects a jump table for at least 256 distinct contiguous integer/tag values, in any source order (issue #395). Smaller and sparse sets retain chains. The threshold deliberately leaves the mixed 64-arm results alone; it is not claimed to be optimal for every CPU or distribution. Regression checks cover all eight integer widths, negative/offset ranges, signed extrema, unsigned maxima, distant 64-bit fallbacks, reversed cases, alternatives, nested payload ordering, owned/borrowed enum payloads and tail exits. Exhaustive enum matching retains the invalid-tag trap. The emission contract describes index normalization and bounded table storage.

The harness also offers --kinds work: each arm computes a different wrapping multiply/add over a runtime state argument. This avoids drawing the entire conclusion from constant-return arms, while remaining a synthetic workload. It does not establish a benefit for any existing application. No source syntax or tutorial idiom changes follow from this optimization.

A follow-up paired run against the chain compiler at 70aa0a3e validated the production policy on the same host and CPU affinity, using two million dispatches and seven shuffled rounds (672 checked timed executions):

256-case workloadUniform chain / table90%-first chain / table90%-last chain / table
Integer55.6 / 21.0 ms12.2 / 10.5 ms79.7 / 12.1 ms
Enum100.1 / 35.1 ms19.7 / 15.6 ms127.7 / 17.8 ms
Runtime multiply/add57.6 / 22.1 ms13.5 / 10.5 ms82.1 / 11.8 ms

Isolated dispatch bytes, including table data/padding, fell from 5,515 to 3,646 for integers, 7,763 to 3,674 for enums and 7,623 to 5,754 for runtime arithmetic. All 26 optimized-IR/assembly dump files for the smaller matches and unchanged tree controls are byte-identical between compilers. Full samples, compiler/source hashes and dump indexes are preserved in production-results.json. Cold build samples were collected in separate batches with other builds active during the baseline batch; they establish no compile-time gain. The machine was not reserved or frequency-locked, so small timing differences in unchanged controls are noise rather than evidence for tuning smaller matches. This run does not measure allocation or peak memory, nor justify a broader sparse-table policy.

String match dispatch

A match with at least 16 distinct str literals before its fallback calls plenty_str_match once and branches through a br_table (issue #565). The runtime binary-searches a read-only table of the literals, ordered by byte length and then bytes; each entry maps to the first source arm with that literal. Smaller matches keep the plenty_str_eq chain. The table stores 32-bit offsets followed by the literal bytes, so it needs no load-time relocations in a PIE.

The 2026-10-30 measurements at 1756617d used a Ryzen 7 7840HS, x86-64 Linux and Cranelift 0.137, pinned to the eight physical cores. Each program matches the first n sorted IANA zone names (a seeded sample below 597) and four misses: a longer string, a shorter prefix, the empty string and an unknown name. Inputs are split from one literal at run time, so no lookup compares literal addresses. Times are medians and ranges of five shuffled rounds of two million lookups, including process start:

ArmsUniform: chain / tableFirst arm only: chain / tablefind bytes: chain / table
532.5 / 32.9 ms (chain kept)18.3 / 17.1 ms283 / 283
1558.5 / 59.6 ms (chain kept)17.8 / 17.7 ms693 / 693
1659.7 [59.4–60.5] / 49.2 [48.6–52.1] ms18.2 / 28.0 ms734 / 357
50123.8 [123.0–125.0] / 65.1 [63.5–66.2] ms17.7 / 30.9 ms2,128 / 831
5971,078.8 [1,077.8–1,087.6] / 110.6 [109.4–112.3] ms18.7 / 46.3 ms24,555 / 8,491

At 597 arms a uniform lookup falls from about 530 to 46 ns. The table data adds 4(n + 1) bytes plus the literal bytes. Optimized CLIF instruction counts fall from 115 to 55 at 16 arms and from 3,601 to 1,217 at 597. Cold compile times were within noise of each other (66–71 ms at 597 arms).

A run that forced the table for every size found the chain faster for uniform inputs below about 12 arms (5 arms: 33.8 / 38.1 ms), so 16 is where the table clearly pays. When the first arm matches every input, the chain always wins, by about 5 ns per lookup at 16 arms and 14 ns at 597: the chain stops after one comparison, while the search always takes about log2(n) steps. Order arms by frequency only in a small match; a large match no longer depends on order. A same-length comparison by 8-byte big-endian words was slower than the byte-slice comparison, which calls memcmp, and was not kept.