Runtime performance
How fast the programs Rue compiles actually run. Every point is a whole process — spawn to exit — of a release-quality build, measured on the pinned reference regime and judged against a committed correctness oracle before it is allowed to be a measurement at all. The companion series for the compiler itself is on the compiler performance page.
Programs
One card per measured program. Select one to plot it. The figure is the median of independent fresh processes at the newest measured commit, with the median absolute deviation of those samples beside it.
Wall time —
Whole-process wall time across compiler commits, oldest first. The axis starts at zero, so the height of a point is the time it took rather than its distance from a chosen floor. The runtime workloads consume data, and ADR-0072 records that data's identity with every observation instead of pinning it. So a movement in the series has two possible causes — the compiler changed, or the input did — and only the first is a result. Every point carries the digest of the input it consumed and of the program's own source. A segment is a run of consecutive points that agree on both. Raw medians are comparable inside one and are not comparable across one, so the chart draws each segment as its own stretch, the deltas below are taken within a segment, and the trailing window a flag is measured against restarts at every boundary. A single line drawn across a corpus change would be a picture of a trend that did not happen. The cross-segment signal ADR-0072 defines is the ratio of Rue's time to a pinned peer tool's time on the same corpus, measured in the same run — see the comparison below, and the caveats that travel with it.Why the line breaks
Segments
The same series as a table, one row per identity-matched stretch. This is where the figures live for a reader who cannot use the chart, and it is the honest place to compare two medians: within a row, never between them.
| Segment | Commits | Input | Program | Median wall time | Change within segment |
|---|
Against other tools
ADR-0072's headline question is order-of-magnitude placement against production software: gazette, a static site generator written in Rue, building the live rue-lang.dev corpus beside pinned Zola and Hugo building the identical corpus under a feature-parity configuration. All three tools have these features disabled: syntax highlighting, search-index generation, and HTML minification, along with Sass and CSS building, live reload, image processing, taxonomies, and pagination. The parity configurations are versioned in the repository beside the template ports. The corpus is modified before any tool sees it, identically for all three. Two pages are removed — the performance and runtime dashboards, whose templates read derived benchmark data, which would make this benchmark an input to itself. The blog's This compares a corpus-specialized Rue program against general-purpose tools. Gazette implements the subset of Markdown, shortcodes, and template features this corpus actually uses; Zola and Hugo implement all of theirs. Gazette renders through its general engine paths — corpus-specific fast paths are out of bounds — but a subset engine is structurally leaner than a general one, and no amount of validation removes that asymmetry. The corpus-scale column is a page-count axis, not a site-size one. The 10x rows build ten copies of the same corpus under path prefixes, so per-page work scales while site shape does not: internal links and section lookups still resolve into the original tree, and cross-reference resolution stays constant. A duplicated section also still redirects to the original target. And the specification sidebar collapses on a duplicated page, because a copy's permalink prefixes nothing in the original tree that the section lookup returns — at 10x, 630 of 700 specification pages render one 2,534-byte navigation where the 70 originals average 4,446. A 10x row therefore understates per-page templating work relative to 1x, and cannot be read as "the 1x figure, times ten".Tool Corpus scale Threads Median wall time Versus gazette Version paginate_by key is stripped, because pagination is outside the subset and only one tool would otherwise build a paginated view. The specification's internal links are rewritten as it is copied in, which is what the real site build does too. What is no longer done is renaming: the three specification appendices with upper-case file names were lower-cased here until gazette learned to slugify a content path, and all three tools now route them where the live site does, from the names the repository actually carries. The static passthrough is the committed static tree: the site build derives two files into it — the home page's status board and the performance dashboard's data — and the preparer excludes everything git ignores there, because the input must not vary with whether the website was ever built, and the benchmark's own published output must never ride back in as its input.
No peer measurements are publishable for this platform's newest run. The peer leg runs when the recorded comparison configuration changes — the corpus, a template port, a pinned peer's version, or the runner epoch — and the per-run canary rides every observation after that; this table fills in with the first such run whose measurement is complete. A run that lost its canary, or whose own observation was truncated, publishes nothing here rather than an older table. Nothing is estimated in the meantime.
Field notes
Disclosures
What a flag means here
A program is flagged when its median differs from the median of the trailing window of prior in-segment measurements by more than k times the two dispersions combined. It is measured against noise rather than against a percentage, it has no direction — a large speedup flags exactly as a regression does — and the window it compares against restarts at every corpus or program discontinuity.
Runtime flags carry no CI gate in v1. ADR-0072 puts the runtime series deliberately outside the required-CI gates: a stalled runtime series has a remedy measured in parser work, so it surfaces as a staleness note and a maintainer triage item, never a repository-wide block.
Memory and binary size
Measurement regime
Collection health
Download the derived data — rebuilt at site build time from the raw runtime records on the performance-data-v1 branch, which stores observations and nothing derived. Medians, dispersions, segments, and every comparison on this page are recomputed from those samples; none of them is stored.
The ports, side by side
The ratio above divides one tool's time by another's; this is what the two sides are made of. All three excerpts render the specification sidebar, which is where this corpus's work is — 71 of the roughly 96 pages the tools build at 1x are specification pages, and both the active class and the recursion are gated on a prefix test against the current page's permalink, so at 1x the sidebar is a different document on every page. That is a claim about the original corpus only: as the caption above records, duplication collapses it, and at 10x most specification pages share one navigation.
The three say it differently, which is the other reason to put them side by side. Zola runs the production template unchanged — that is what the port is for. Gazette needs four substitutions to say the same thing in a smaller dialect: it has no arithmetic, so the depth parameter goes, and the prefix test is spelled as a filter rather than a test. Only Hugo restates the recursion outright — Go templates have no macros, so what Tera calls on itself as self::render_nav becomes a partial invoking itself with a dict.
Each excerpt is cut from the checked-in file when this page is built, never pasted, and the line numbers below are wherever the block was found in that build. Rename it, split it, or precompute the prefix test out of it, and the extraction fails and this page does not build.
{% macro render_nav(section, current_path) %}
<ul class="spec-nav-list">
{# Render direct pages in this section (already sorted by weight) #}
{% for pg in section.pages %}
<li class="spec-nav-item">
<a href="{{ pg.permalink }}" class="spec-nav-link {% if pg.permalink == current_path %}active{% endif %}">
{{ pg.title }}
</a>
</li>
{% endfor %}
{# Subsections paths are already sorted alphabetically by Zola, which works for
our numbered chapters (02-lexical-structure, 03-types, etc.) #}
{% for sub_path in section.subsections | sort %}
{% set sub = get_section(path=sub_path) %}
<li class="spec-nav-section">
<a href="{{ sub.permalink }}" class="spec-nav-section-title {% if current_path | starts_with(prefix=sub.permalink) %}active{% endif %}">
{{ sub.title }}
</a>
{% if current_path | starts_with(prefix=sub.permalink) %}
{{ self::render_nav(section=sub, current_path=current_path) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}{% macro render_nav(section, current_path, depth) %}
<ul class="spec-nav-list">
{# Render direct pages in this section (already sorted by weight) #}
{% for pg in section.pages %}
<li class="spec-nav-item">
<a href="{{ pg.permalink }}" class="spec-nav-link {% if pg.permalink == current_path %}active{% endif %}">
{{ pg.title }}
</a>
</li>
{% endfor %}
{# Subsections paths are already sorted alphabetically by Zola, which works for
our numbered chapters (02-lexical-structure, 03-types, etc.) #}
{% for sub_path in section.subsections | sort %}
{% set sub = get_section(path=sub_path) %}
<li class="spec-nav-section">
<a href="{{ sub.permalink }}" class="spec-nav-section-title {% if current_path is starting_with(sub.permalink) %}active{% endif %}">
{{ sub.title }}
</a>
{% if current_path is starting_with(sub.permalink) %}
{{ self::render_nav(section=sub, current_path=current_path, depth=depth + 1) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}{{- $current := .current -}}
<ul class="spec-nav-list">
{{ range .section.RegularPages }}
<li class="spec-nav-item">
<a href="{{ .Permalink }}" class="spec-nav-link {{ if eq .Permalink $current }}active{{ end }}">
{{ .Title }}
</a>
</li>
{{ end }}
{{/* Sorted by path, as the Tera macro sorts `section.subsections`. The
corpus's numbered chapter directories make that the reading order, and
it stays total on the duplicated corpus of the scale variants, where
every copy ties with its original on weight and on title. */}}
{{ range sort .section.Sections "Path" }}
<li class="spec-nav-section">
<a href="{{ .Permalink }}" class="spec-nav-section-title {{ if strings.HasPrefix $current .Permalink }}active{{ end }}">
{{ .Title }}
</a>
{{ if strings.HasPrefix $current .Permalink }}
{{ partial "spec-nav.html" (dict "section" . "current" $current) }}
{{ end }}
</li>
{{ end }}
</ul>And what reads them
Zola and Hugo each arrive with a general-purpose template engine. Gazette arrives with this. The prefix test the sidebar gates on is spelled as a filter because gazette's grammar has no is tests, and it runs where Tera's and Go's do — inside the program being measured. Computing the open branch outside the template would move measured work out of the benchmark, and no output check would catch it: a flattened sidebar emits the same links and the same visible text on every page.
The last line is what corpus-specialized means, stated in source rather than in a caption. The filter chain ends in a diagnostic: gazette implements the filters this corpus uses and refuses every other one, where Zola and Hugo implement all of theirs. That is the structural asymmetry the comparison table above discloses, and this is the line it refers to.// Tera spells this as the test `x is starting_with(y)`. A filter needs no
// new grammar and reads the same way, and it must exist in the ENGINE: the
// production sidebar gates both the `active` class and its own recursion on
// it, and computing it host-side would move measured work out of the
// program the benchmark exists to measure.
if eng.arena.strings.equals_literal(name, "starts_with") {
let prefix = arg_of(inout eng, args, "prefix", line);
if prefix == NONE() {
fail(inout eng, line, "starts_with requires a `prefix` argument", name);
return eng.arena.boolean(false);
}
let subject = eng.arena.scalar(input);
let wanted = eng.arena.scalar(prefix);
return eng.arena.boolean(subject.starts_with(borrow wanted));
}
fail(inout eng, line, "unknown filter", name);