|
217 | 217 | .chart { position: relative; width: 100%; min-width: 0; } |
218 | 218 | .chart svg { display: block; width: 100%; height: auto; overflow: visible; } |
219 | 219 | .chart text { font-family: var(--mono); font-size: 11px; fill: var(--muted); } |
| 220 | +.chart text.eng { font-family: var(--font); fill: var(--faint); } |
| 221 | +.rlegend .sus svg { width: 10px; height: 8px; fill: var(--bg); stroke: var(--muted); stroke-width: 1.5; overflow: visible; } |
220 | 222 | .chart .tick line { stroke: var(--grid); } |
221 | 223 | .chart .base { stroke: var(--line); } |
222 | 224 | .chart .cross { stroke: var(--faint); stroke-width: 1; opacity: 0; } |
|
227 | 229 | .tipbox .r { display: flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 12px; } |
228 | 230 | .tipbox .r i { width: 8px; height: 8px; border-radius: 50%; flex: none; box-shadow: 0 0 0 1.5px #fff; } |
229 | 231 | .tipbox .n { color: #cfcfcf; font-size: 12px; margin-top: 3px; } |
230 | | -.rfoot { display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 13px; color: var(--muted); } |
231 | | -.rfoot .noisy { color: var(--red); font-weight: 700; } |
| 232 | +.rfoot { margin: 8px 0 0; font-size: 13px; color: var(--muted); } |
| 233 | +.rfoot.noisy { margin: 0; color: var(--red); font-weight: 700; } |
232 | 234 | .rcard details summary { cursor: pointer; font-size: 13.5px; font-weight: 700; color: var(--ink); width: max-content; } |
233 | 235 | .rcard .tb { overflow-x: auto; border: 1px solid var(--line); margin-top: 8px; } |
234 | 236 | .rcard table { border-collapse: collapse; width: 100%; font-size: 13px; font-variant-numeric: tabular-nums; } |
|
290 | 292 | <h1>How fast is each idiom on each Ruby?</h1> |
291 | 293 | <p>Every Ruby ran each benchmark on the same machine, one after the other, so here you can compare i/s across Rubies. A reference Ruby ran again and again between them, to show how steady the machine was during the run.</p> |
292 | 294 | <p class="run" id="rrun"></p> |
| 295 | + <p class="run"><a href="#rhow">How to read these charts</a></p> |
293 | 296 | <p class="note" id="rnote" hidden></p> |
294 | 297 | </div> |
295 | 298 |
|
@@ -302,8 +305,10 @@ <h1>How fast is each idiom on each Ruby?</h1> |
302 | 305 | <div id="rout"></div> |
303 | 306 |
|
304 | 307 | <div class="rhow" id="rhow"> |
| 308 | + <p><b>How to read a card.</b> The heading says what happened on the build you picked: ✓ the claimed idiom (the one the benchmark file says is fastest) wins or ties on every Ruby, ~ it wins on some and another idiom wins on others, ✗ another idiom wins everywhere. It compares the claimed idiom with the fastest of the others, and a gap within the measured error is a tie.</p> |
| 309 | + <p><b>How to read a chart.</b> The y axis is logarithmic: each gridline step is the same ratio, so a 100× gap between two idioms and a 10% change between two Rubies both show. Under "Relative", every line is divided by the claimed idiom on the reference Ruby, so 1× is that idiom there and the higher line is still the faster one.</p> |
305 | 310 | <p><b>How this is measured.</b> Each shard of benchmark files ran on one GitHub runner: every build, in a random order, with benchmark-ips' default timing. The numbers are i/s as measured.</p> |
306 | | - <p><b>How steady the machine was.</b> The reference Ruby ran first, again after every few builds, and last. It is the same code every time, so how much its i/s varies is the machine, not Ruby. Under each chart is that variation (standard deviation over mean). Over 5% the benchmark is marked noisy, and when the typical benchmark of a run is over 5%, the page warns at the top. Two Rubies whose i/s differ by less than that variation are shown as same-ish.</p> |
| 311 | + <p><b>How steady the machine was.</b> The reference Ruby ran first, again after every few builds, and last. It is the same code every time, so how much its i/s varies is the machine, not Ruby. "Numbers per build" shows that variation (standard deviation over mean). Over 5% the benchmark is marked noisy under its chart, and when the typical benchmark of a run is over 5%, the page warns at the top. Two Rubies whose i/s differ by less than that variation are shown as same-ish.</p> |
307 | 312 | <p>Only Rubies of the same run are compared. Weeks are not compared with each other: each week can land on a different CPU. Results over a billion iterations per second (under 1 ns each) are marked suspect and kept off the chart: the JIT most likely removed the work.</p> |
308 | 313 | </div> |
309 | 314 | </div> |
@@ -826,10 +831,18 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
826 | 831 | tk.forEach((t) => { s += `<g class="tick"><line x1="${P.l}" x2="${W - P.r}" y1="${y(t)}" y2="${y(t)}"/><text x="${P.l - 8}" y="${y(t) + 4}" text-anchor="end">${tickLabel(t)}</text></g>`; }); |
827 | 832 | s += `<line class="base" x1="${P.l}" x2="${W - P.r}" y1="${yb}" y2="${yb}"/>`; |
828 | 833 | if (showing === "rel" && bottom < 1 && top > 1) s += `<line x1="${P.l}" x2="${W - P.r}" y1="${y(1)}" y2="${y(1)}" stroke="var(--faint)" stroke-dasharray="2 3"/>`; |
829 | | - cols.all.forEach((l, k) => { |
830 | | - const parts = short(l).split(" "); |
831 | | - s += `<text x="${x(k)}" y="${H - P.b + 16}" text-anchor="middle">${esc(parts[0])}</text>`; |
832 | | - if (parts[1]) s += `<text x="${x(k)}" y="${H - P.b + 29}" text-anchor="middle">${esc(parts.slice(1).join(" "))}</text>`; |
| 834 | + // x labels: the version on each column ("+yjit" is already in the picker), and under it the engine once per group of columns. |
| 835 | + // About 7 px per character at 11 px: on a narrow chart every other version is labelled, and a long engine name shortens. |
| 836 | + const cw = 7, every = band < 4 * cw + 2 ? 2 : 1; |
| 837 | + const groups = []; |
| 838 | + cols.all.forEach((l, k) => { const e = base(l).split("_")[0]; if (groups.length && groups[groups.length - 1].e === e) groups[groups.length - 1].to = k; else groups.push({ e, from: k, to: k }); }); |
| 839 | + groups.forEach((g) => { |
| 840 | + for (let k = g.from; k <= g.to; k += every) s += `<text x="${x(k)}" y="${H - P.b + 16}" text-anchor="middle">${esc(base(cols.all[k]).replace(/^[a-z]+_/, ""))}</text>`; |
| 841 | + }); |
| 842 | + groups.forEach((g) => { |
| 843 | + const room = band * (g.to - g.from + 1); |
| 844 | + const name = [{ ruby: "Ruby", jruby: "JRuby", truffleruby: "TruffleRuby" }[g.e] || g.e, { truffleruby: "Truffle" }[g.e], { jruby: "JR", truffleruby: "TR" }[g.e]].filter(Boolean).find((n) => n.length * cw <= room) || ""; |
| 845 | + if (name) s += `<text x="${(x(g.from) + x(g.to)) / 2}" y="${H - P.b + 30}" text-anchor="middle" class="eng">${esc(name)}</text>`; |
833 | 846 | }); |
834 | 847 | if (cols.other.length) { const gx = x(cols.line.length) - band / 2 - gap / 2; s += `<line x1="${gx}" x2="${gx}" y1="${P.t}" y2="${yb}" stroke="var(--line)"/>`; } |
835 | 848 | // Under a JIT build: the same Rubies without it, dashed. |
@@ -905,11 +918,19 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
905 | 918 | return `<div class="tb"><table>${h}</table></div>`; |
906 | 919 | } |
907 | 920 |
|
908 | | - function foot(b) { |
| 921 | + // The machine, inside "Numbers per build": the same on every card but the CPU, so it only shows on the card when the benchmark was noisy. |
| 922 | + function machine(b) { |
909 | 923 | const shard = M.shards.find((x) => x.shard === b.shard) || {}; |
910 | | - const parts = [`Ran on ${esc(cpu(shard.cpu))}${M.shards_total > 1 ? `, shard ${b.shard + 1} of ${M.shards_total}` : ""}`]; |
911 | | - if (b.variation != null) parts.push(`<span class="${b.noisy ? "noisy" : ""}">${esc(nice(REF))} ran ${b.reference_passes} times; its i/s varied by ${(b.variation * 100).toFixed(1)}%${b.noisy ? " (noisy)" : ""}</span>`); |
912 | | - return `<div class="rfoot">${parts.map((p) => `<span>${p}</span>`).join("")}</div>`; |
| 924 | + const parts = [`Ran on ${esc(cpu(shard.cpu))}${M.shards_total > 1 ? `, shard ${b.shard + 1} of ${M.shards_total}` : ""}.`]; |
| 925 | + if (b.variation != null) parts.push(`${esc(nice(REF))} ran ${b.reference_passes} times; its i/s varied by ${(b.variation * 100).toFixed(1)}%.`); |
| 926 | + return `<p class="rfoot">${parts.join(" ")}</p>`; |
| 927 | + } |
| 928 | + function noisy(b) { |
| 929 | + return b.noisy ? `<p class="rfoot noisy">Noisy: ${esc(nice(REF))}'s i/s varied by ${(b.variation * 100).toFixed(1)}% during this benchmark, so gaps that small may be the machine, not Ruby.</p>` : ""; |
| 930 | + } |
| 931 | + // Suspect results are drawn as triangles along the top of the chart. |
| 932 | + function hasSuspect(b) { |
| 933 | + return columns().all.some((l) => b.runs[l] && b.runs[l].entries.some(sus)); |
913 | 934 | } |
914 | 935 |
|
915 | 936 | const open = new Set(); |
@@ -929,12 +950,13 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
929 | 950 | let h = "", sec = null; |
930 | 951 | shown.forEach(([b, n]) => { |
931 | 952 | if (b.section !== sec) { sec = b.section; h += `<h2 class="rsec" id="${esc(slug(sec))}">${esc(sec)}<small>${count[sec]} benchmark${count[sec] > 1 ? "s" : ""}</small><span class="on">${esc(MODES.find(([m]) => m === mode)[1])}</span></h2>`; } |
932 | | - const legend = b.names.map((nm, i) => `<span><i style="background:${color(i)}"></i><code>${esc(nm)}</code>${i ? "" : " (claimed)"}</span>`).join("") + (mode ? '<span class="ghost"><i></i>without the JIT</span>' : ""); |
| 953 | + const legend = b.names.map((nm, i) => `<span><i style="background:${color(i)}"></i><code>${esc(nm)}</code>${i ? "" : " (claimed)"}</span>`).join("") + (mode ? '<span class="ghost"><i></i>without the JIT</span>' : "") + |
| 954 | + (hasSuspect(b) ? '<span class="sus"><svg viewBox="0 0 10 8" aria-hidden="true"><path d="M1,7 L5,1 L9,7Z"/></svg>suspect, over 1B i/s: kept off the chart</span>' : ""); |
933 | 955 | const v = verdict(b); |
934 | 956 | h += `<article class="rcard"><p class="what">${md(b.title)}</p><h3><span class="ico ${v.icon}" aria-hidden="true">${RICON[v.icon]}</span><span>${v.head}</span></h3>` + |
935 | 957 | (v.sub ? `<p class="say">${v.sub}</p>` : "") + `<div class="rlegend">${legend}</div>` + |
936 | | - `<div class="chart" data-n="${n}"></div>${foot(b)}` + |
937 | | - `<details data-n="${n}"${open.has(n) ? " open" : ""}><summary>Numbers per build</summary>${table(b)}</details>` + |
| 958 | + `<div class="chart" data-n="${n}"></div>${noisy(b)}` + |
| 959 | + `<details data-n="${n}"${open.has(n) ? " open" : ""}><summary>Numbers per build</summary>${machine(b)}${table(b)}</details>` + |
938 | 960 | `<div class="links"><a href="${M.repo}/blob/main/${encodeURI(b.file)}">View the code</a></div></article>`; |
939 | 961 | }); |
940 | 962 | $("rout").innerHTML = h || `<p class="empty">No benchmark matches "${esc(find)}". Clear the find box to see all ${B.length}.</p>`; |
|