@@ -313,7 +313,7 @@ <h1>How fast is each idiom on each Ruby?</h1> |
313 | 313 | <div id="rout"></div> |
314 | 314 |
|
315 | 315 | <div class="rhow" id="rhow"> |
316 | | - <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> |
| 316 | + <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 on every Ruby, or wins on some and ties on the rest; = it ties on every Ruby; ~ it wins on some and another idiom wins on others; ✗ it never wins; – no Ruby could compare them. It compares the claimed idiom with the fastest of the others, and a gap within the measured error is a tie.</p> |
317 | 317 | <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> |
318 | 318 | <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> |
319 | 319 | <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> |
@@ -653,7 +653,6 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
653 | 653 | function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); } |
654 | 654 | function md(s) { return esc(s).replace(/`([^`]+)`/g, "<code>$1</code>"); } |
655 | 655 | function nice(l) { const [b, m] = l.split("+"); return b.replace(/^ruby_/, "Ruby ").replace(/^jruby_/, "JRuby ").replace(/^truffleruby_/, "TruffleRuby ") + (m ? " + " + m.toUpperCase() : ""); } |
656 | | - function short(l) { const [b, m] = l.split("+"); return b.replace(/^ruby_/, "").replace(/^jruby_/, "jruby ").replace(/^truffleruby_/, "truffle ") + (m ? " +" + m : ""); } |
657 | 656 | function fmt(v) { return v >= 1e9 ? (v / 1e9).toFixed(1) + "B" : v >= 1e6 ? (v / 1e6).toFixed(v >= 1e7 ? 1 : 2) + "M" : v >= 1e3 ? (v / 1e3).toFixed(v >= 1e5 ? 0 : 1) + "k" : v.toFixed(1); } |
658 | 657 | function pct(a, b) { const p = (a / b - 1) * 100; return (p >= 0 ? "+" : "") + p.toFixed(1) + "%"; } |
659 | 658 | function cpu(c) { return (c || "unknown CPU").replace(/^AMD /, "").replace(/ \d+-Core Processor$/, "").replace(/^Intel\(R\) Xeon\(R\) Platinum (\S+) CPU.*$/i, "Xeon $1"); } |
@@ -663,7 +662,7 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
663 | 662 | // A result that can be drawn and compared: it ran and is not suspect. |
664 | 663 | const ok = (e) => !!e && !sus(e) && e[0] > 0; |
665 | 664 | // Relative needs the reference to have the claimed report. |
666 | | - const val = (e) => ok(e) ? (showing === "rel" ? e[2] : e[0]) : null; |
| 665 | + const val = (e) => { const v = ok(e) ? (showing === "rel" ? e[2] : e[0]) : null; return v > 0 ? v : null; }; |
667 | 666 | // Two reports of one build, measured seconds apart: same-ish when the error bars overlap, as benchmark-ips says. |
668 | 667 | const same = (a, b) => a[0] + a[1] > b[0] - b[1] && a[0] - a[1] < b[0] + b[1]; |
669 | 668 | // Two Rubies, measured minutes apart: also same-ish when the gap is within how much the reference varied on that machine, which is larger than benchmark-ips' error bars. |
@@ -736,7 +735,7 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
736 | 735 | const res = cols.map((l) => [l, versus(b.runs[l])]).filter(([, v]) => v); |
737 | 736 | const n = res.length, of = (s) => res.filter(([, v]) => v.s === s); |
738 | 737 | const wins = of("ok"), ties = of("eq"), losses = of("no"); |
739 | | - const all = `all ${n} Rubies${where}`; |
| 738 | + const all = `${n === 1 ? "the one Ruby" : `all ${n} Rubies`}${where}`; |
740 | 739 | let icon = "ok", head; |
741 | 740 | if (!n) { icon = "na"; head = `Did not run on any Ruby${where}`; } |
742 | 741 | else if (!losses.length && !ties.length) head = b.names.length === 2 ? `${claim} beats ${nm(1)} on <b>${all}</b>` : `${claim} is the fastest on <b>${all}</b>`; |
@@ -752,6 +751,7 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
752 | 751 | if (rest) named.push(`others on ${rest} more`); |
753 | 752 | if (losses.length === n && top.length === 1) { icon = "no"; head = `${nm(+top[0])} beats ${claim} on <b>${all}</b>`; } |
754 | 753 | else if (losses.length === n) { icon = "no"; head = `${claim} loses on <b>${all}</b>: ${named.join("; ")}`; } |
| 754 | + else if (!wins.length) { icon = "no"; head = `${claim} never wins: it ties on <b>${ties.length} of ${n} Rubies${where}</b>; ${named.join("; ")}`; } |
755 | 755 | else { icon = "mix"; head = `${claim} wins on <b>${wins.length} of ${n} Rubies${where}</b>${ties.length ? ` (${ties.length} tie${ties.length > 1 ? "s" : ""})` : ""}; ${named.join("; ")}`; } |
756 | 756 | } |
757 | 757 |
|
@@ -787,8 +787,9 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
787 | 787 | } |
788 | 788 | // Rubies on the chart that are not in the count. |
789 | 789 | const left = cols.filter((l) => !res.some(([k]) => k === l)), notes = []; |
790 | | - const susp = left.filter((l) => b.runs[l] && b.runs[l].entries.some(sus)), gone = left.filter((l) => !susp.includes(l)); |
| 790 | + const susp = left.filter((l) => b.runs[l] && b.runs[l].entries.some(sus)), gone = left.filter((l) => !b.runs[l]), odd = left.filter((l) => b.runs[l] && !susp.includes(l)); |
791 | 791 | if (susp.length) notes.push(`Not counted: ${esc(runsOf(susp, cols))}, suspect results (over 1B i/s).`); |
| 792 | + if (odd.length) notes.push(`Not compared: ${esc(runsOf(odd, cols))}, an idiom has no result there.`); |
792 | 793 | if (gone.length) notes.push(`Did not run: ${esc(runsOf(gone, cols))}.`); |
793 | 794 | return { icon, head, tiles, foot: notes.join(" ") }; |
794 | 795 | } |
@@ -817,8 +818,10 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
817 | 818 | // Tick labels without trailing zeros: 2M, 500k, 0.05×. |
818 | 819 | function tickLabel(t) { |
819 | 820 | const n = (v) => String(+v.toPrecision(3)); |
820 | | - if (showing === "rel") return n(t) + "×"; |
821 | | - return t >= 1e9 ? n(t / 1e9) + "B" : t >= 1e6 ? n(t / 1e6) + "M" : t >= 1e3 ? n(t / 1e3) + "k" : n(t); |
| 821 | + const big = (v) => v >= 1e9 ? n(v / 1e9) + "B" : v >= 1e6 ? n(v / 1e6) + "M" : v >= 1e3 ? n(v / 1e3) + "k" : n(v); |
| 822 | + // Far below the claimed idiom, as a fraction: 1/10M× rather than 1e-7×. |
| 823 | + if (showing === "rel") return (t < 1e-3 ? "1/" + big(1 / t) : n(t)) + "×"; |
| 824 | + return big(t); |
822 | 825 | } |
823 | 826 |
|
824 | 827 | function path(pts, c, extra, w) { |
@@ -964,13 +967,12 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
964 | 967 | if (showing === "rel") hints.push(`1× is the claimed idiom on ${nice(REF)}.`); |
965 | 968 | if (mode) hints.push("Dashed: the same Rubies without the JIT, on the same machine."); |
966 | 969 | $("rhint").textContent = hints.join(" "); |
967 | | - const shown = B.map((b, n) => [b, n]); |
968 | 970 | const count = {}; |
969 | | - shown.forEach(([b]) => { count[b.section] = (count[b.section] || 0) + 1; }); |
| 971 | + B.forEach((b) => { count[b.section] = (count[b.section] || 0) + 1; }); |
970 | 972 | const secs = Object.keys(count); |
971 | 973 | $("rjump").innerHTML = `<option value="">A section</option>` + secs.map((x) => `<option value="${esc(slug(x))}">${esc(x)} (${count[x]})</option>`).join(""); |
972 | 974 | let h = "", sec = null; |
973 | | - shown.forEach(([b, n]) => { |
| 975 | + B.forEach((b, n) => { |
974 | 976 | 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>`; } |
975 | 977 | 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>' : "") + |
976 | 978 | (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>' : ""); |
@@ -988,17 +990,21 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
988 | 990 | pinned(); |
989 | 991 | } |
990 | 992 | // A title of many report names stays on one line by getting smaller (20px down to 14px); one too long even at 14px wraps at 14px. |
| 993 | + // All writes, then all reads, then all writes: one layout for every card instead of one per card. |
991 | 994 | function fitTitles() { |
992 | | - $("rout").querySelectorAll(".rcard h3").forEach((h) => { |
993 | | - h.classList.add("one"); |
994 | | - h.style.fontSize = ""; |
995 | | - const room = h.clientWidth, need = h.scrollWidth; |
| 995 | + const hs = [...$("rout").querySelectorAll(".rcard h3")]; |
| 996 | + hs.forEach((h) => { h.classList.add("one"); h.style.fontSize = ""; }); |
| 997 | + const fit = hs.map((h) => [h.clientWidth, h.scrollWidth]); |
| 998 | + hs.forEach((h, i) => { |
| 999 | + const [room, need] = fit[i]; |
996 | 1000 | if (need <= room) return; |
997 | 1001 | const size = Math.floor(20 * room / need * 10) / 10; |
998 | 1002 | if (size >= 14) h.style.fontSize = size + "px"; |
999 | 1003 | else { h.style.fontSize = "14px"; h.classList.remove("one"); } |
1000 | 1004 | }); |
1001 | 1005 | } |
| 1006 | + // A title measured before the web font arrived is measured again once it has. |
| 1007 | + if (document.fonts) document.fonts.ready.then(() => { if (!$("view-across-rubies").hidden) fitTitles(); }); |
1002 | 1008 | // A section heading reached from "Jump to" lands under the pinned picker, not behind it. |
1003 | 1009 | function pinned() { |
1004 | 1010 | const p = $("rpicker"); |
@@ -1010,7 +1016,6 @@ <h2 class="bh">FastRuby<span>.io</span> keeps fast-ruby running</h2> |
1010 | 1016 | $("rshow").addEventListener("click", (e) => { const c = e.target.closest(".chip"); if (c) { showing = c.dataset.s; window.SiteViews.setParams({ show: showing === "rel" ? "rel" : null }, true); render(); } }); |
1011 | 1017 | $("rjump").addEventListener("change", (e) => { |
1012 | 1018 | const target = e.target.value && document.getElementById(e.target.value); |
1013 | | - e.target.value = ""; |
1014 | 1019 | if (target) { pinned(); target.scrollIntoView(); } |
1015 | 1020 | }); |
1016 | 1021 | $("rout").addEventListener("toggle", (e) => { const d = e.target; if (d.matches("details")) d.open ? open.add(+d.dataset.n) : open.delete(+d.dataset.n); }, true); |
|