You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by gi...@apache.org on 2026/06/02 02:51:13 UTC
(groovy) branch gh-pages updated: Update JMH adhoc summary page
This is an automated email from the ASF dual-hosted git repository.
github-actions[bot] pushed a commit to branch gh-pages
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/gh-pages by this push:
new 283cfb86b3 Update JMH adhoc summary page
283cfb86b3 is described below
commit 283cfb86b3cb189b674312352ab16b2f74baa0c6
Author: github-actions[bot] <41...@users.noreply.github.com>
AuthorDate: Tue Jun 2 02:51:08 2026 +0000
Update JMH adhoc summary page
---
dev/bench/jmh-adhoc/index.html | 243 +++++++++++++++++++++
dev/bench/jmh-adhoc/vendor/README.md | 73 +++++++
dev/bench/jmh-adhoc/vendor/chart.umd.min.js | 20 ++
.../vendor/chartjs-adapter-date-fns.bundle.min.js | 7 +
4 files changed, 343 insertions(+)
diff --git a/dev/bench/jmh-adhoc/index.html b/dev/bench/jmh-adhoc/index.html
new file mode 100644
index 0000000000..912b7920eb
--- /dev/null
+++ b/dev/bench/jmh-adhoc/index.html
@@ -0,0 +1,243 @@
+<!DOCTYPE html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Groovy JMH ad-hoc benchmarks</title>
+<style>
+ :root { color-scheme: light; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ margin: 2em auto;
+ max-width: 1200px;
+ padding: 0 1em;
+ color: #222;
+ background: #fff;
+ }
+ h1 { font-weight: 500; margin-bottom: 0.25em; }
+ h2 { font-weight: 500; margin: 1.75em 0 0.25em; border-bottom: 1px solid #eee; padding-bottom: 0.2em; }
+ h3 { font-weight: 500; margin: 1.25em 0 0.25em; color: #333; }
+ .intro { color: #444; line-height: 1.45; margin-bottom: 1.25em; max-width: 80ch; }
+ .controls { margin: 0.5em 0 1.5em; font-size: 0.95em; }
+ .controls label { margin-right: 1em; cursor: pointer; }
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5em; }
+ .chart-wrap { position: relative; height: 320px; }
+ .meta { color: #666; font-size: 0.9em; margin-top: 2em; line-height: 1.5; }
+ .meta a { color: #4A8CC2; text-decoration: none; }
+ .meta a:hover { text-decoration: underline; }
+ code { background: #f5f5f7; padding: 0.05em 0.3em; border-radius: 3px; font-size: 0.92em; }
+ .empty { color: #888; font-style: italic; }
+ @media (max-width: 820px) { .grid { grid-template-columns: 1fr; } }
+</style>
+</head>
+<body>
+<h1>Groovy JMH ad-hoc benchmarks</h1>
+<p class="intro">
+ Exploratory, idiom-vs-idiom micro-benchmarks from the
+ <code>org.apache.groovy.adhoc</code> package — kept out of the core
+ regression suites on purpose. Each chart shows the <strong>latest</strong>
+ run only (these compare idioms <em>against each other</em>, not Groovy over
+ time). <strong>Throughput</strong> is ops/µs (higher is better);
+ <strong>allocation</strong> is <code>gc.alloc.rate.norm</code> bytes/op
+ (lower is better, log scale — bars at the <code>0</code> floor mean
+ effectively zero allocation). Bars are grouped by workload size. Use the
+ toggle to switch between <code>indy</code> and <code>classic</code> call
+ sites. Per-run history (with regression alerting) lives under the
+ per-bucket dashboards linked below.
+</p>
+
+<div class="controls">
+ <strong>Call site:</strong>
+ <label><input type="radio" name="mode" value="indy" checked> invokedynamic (<code>indy</code>)</label>
+ <label><input type="radio" name="mode" value="classic"> classic</label>
+</div>
+
+<div id="status" class="empty">Loading…</div>
+
+<h2>find <span style="font-weight:300;color:#888">(short-circuit; full traversal here — no match)</span></h2>
+<div class="grid">
+ <div><h3>Throughput (ops/µs, higher = better)</h3><div class="chart-wrap"><canvas id="find-thrpt"></canvas></div></div>
+ <div><h3>Allocation (bytes/op, lower = better)</h3><div class="chart-wrap"><canvas id="find-alloc"></canvas></div></div>
+</div>
+
+<h2>findAll <span style="font-weight:300;color:#888">(full traversal; empty result list)</span></h2>
+<div class="grid">
+ <div><h3>Throughput (ops/µs, higher = better)</h3><div class="chart-wrap"><canvas id="findAll-thrpt"></canvas></div></div>
+ <div><h3>Allocation (bytes/op, lower = better)</h3><div class="chart-wrap"><canvas id="findAll-alloc"></canvas></div></div>
+</div>
+
+<h2>count <span style="font-weight:300;color:#888">(full-traversal reducer; builds no collection)</span></h2>
+<div class="grid">
+ <div><h3>Throughput (ops/µs, higher = better)</h3><div class="chart-wrap"><canvas id="count-thrpt"></canvas></div></div>
+ <div><h3>Allocation (bytes/op, lower = better)</h3><div class="chart-wrap"><canvas id="count-alloc"></canvas></div></div>
+</div>
+
+<div class="meta">
+ <div id="lastUpdate"></div>
+ <div style="margin-top:0.5em">
+ Per-bucket history & raw data:
+ <a href="throughput/indy/">throughput/indy</a> ·
+ <a href="throughput/classic/">throughput/classic</a> ·
+ <a href="alloc/indy/">alloc/indy</a> ·
+ <a href="alloc/classic/">alloc/classic</a>
+ (each <code><bucket>/data.js</code>)
+ </div>
+ <div style="margin-top:0.5em">
+ Generated by <a href="https://github.com/benchmark-action/github-action-benchmark">github-action-benchmark</a> ·
+ <a href="../../../">Apache Groovy benchmark home</a>
+ </div>
+</div>
+
+<!-- Vendored locally (see vendor/README) instead of a CDN. -->
+<script src="vendor/chart.umd.min.js"></script>
+<script src="vendor/chartjs-adapter-date-fns.bundle.min.js"></script>
+<script>
+(function () {
+ const ALLOC_FLOOR = 0.5; // log axis can't plot 0; plot zeros here
+ const SIZE_COLORS = { '100': '#9ECAE1', '1000': '#4292C6', '10000': '#08519C' };
+ const charts = {};
+
+ function parseDataJs(text) {
+ const json = text.replace(/^\s*window\.BENCHMARK_DATA\s*=\s*/, '').replace(/;\s*$/, '');
+ return JSON.parse(json);
+ }
+
+ // Latest run's bench list for a bucket, or null if unavailable.
+ async function loadLatest(bucket) {
+ try {
+ const resp = await fetch(bucket + '/data.js', { cache: 'no-cache' });
+ if (!resp.ok) return null;
+ const data = parseDataJs(await resp.text());
+ const runs = (data.entries && data.entries.Benchmark) || [];
+ if (!runs.length) return null;
+ const last = runs[runs.length - 1];
+ return { date: last.date, commit: last.commit, benches: last.benches || [] };
+ } catch (e) {
+ return null;
+ }
+ }
+
+ // "findAllG_lambdasCurryWith (size=10000)" -> {family, variant, size}
+ // NB: findAll must precede find in the alternation so it wins the match.
+ function parseName(name) {
+ const m = name.match(/^(findAll|find|count)([^ ]+?)\s*\(size=(\d+)\)\s*$/);
+ if (!m) return null;
+ return { family: m[1], variant: m[2].replace(/^_/, ''), size: m[3] };
+ }
+
+ // Shape a bench list into { variants:[...], bySize:{size:{variant:value}} } for one family.
+ function shape(benches, family) {
+ const variants = [];
+ const seen = new Set();
+ const bySize = {};
+ for (const b of benches) {
+ const p = parseName(b.name);
+ if (!p || p.family !== family) continue;
+ if (!seen.has(p.variant)) { seen.add(p.variant); variants.push(p.variant); }
+ (bySize[p.size] = bySize[p.size] || {})[p.variant] = b.value;
+ }
+ variants.sort(); // A_… B_… … H_…
+ return { variants, bySize };
+ }
+
+ function datasets(shaped, isAlloc) {
+ const sizes = Object.keys(shaped.bySize).sort((a, b) => (+a) - (+b));
+ return sizes.map(size => ({
+ label: 'size ' + size,
+ backgroundColor: SIZE_COLORS[size] || '#888',
+ data: shaped.variants.map(v => {
+ const raw = shaped.bySize[size][v];
+ if (raw == null) return null;
+ return isAlloc ? Math.max(raw, ALLOC_FLOOR) : raw;
+ }),
+ rawData: shaped.variants.map(v => shaped.bySize[size][v]),
+ }));
+ }
+
+ function render(canvasId, shaped, isAlloc) {
+ const ctx = document.getElementById(canvasId);
+ if (charts[canvasId]) charts[canvasId].destroy();
+ charts[canvasId] = new Chart(ctx, {
+ type: 'bar',
+ data: { labels: shaped.variants, datasets: datasets(shaped, isAlloc) },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: { ticks: { autoSkip: false, maxRotation: 60, minRotation: 45, font: { size: 10 } } },
+ y: isAlloc
+ ? { type: 'logarithmic', title: { display: true, text: 'bytes/op (log)' } }
+ : { beginAtZero: true, title: { display: true, text: 'ops/µs' } },
+ },
+ plugins: {
+ legend: { position: 'top' },
+ tooltip: {
+ callbacks: {
+ label: (item) => {
+ const ds = item.dataset;
+ const raw = ds.rawData ? ds.rawData[item.dataIndex] : item.parsed.y;
+ return ds.label + ': ' + (isAlloc
+ ? (raw == null ? 'n/a' : raw.toLocaleString() + ' B/op')
+ : (raw == null ? 'n/a' : raw.toFixed(3) + ' ops/µs'));
+ },
+ },
+ },
+ },
+ },
+ });
+ }
+
+ async function draw(mode) {
+ const status = document.getElementById('status');
+ const [thrpt, alloc] = await Promise.all([
+ loadLatest('throughput/' + mode),
+ loadLatest('alloc/' + mode),
+ ]);
+ if (!thrpt && !alloc) {
+ status.textContent = 'No data published yet for "' + mode + '". '
+ + 'The jmh-adhoc workflow populates this on its first run.';
+ return;
+ }
+ status.style.display = 'none';
+ if (thrpt) {
+ render('find-thrpt', shape(thrpt.benches, 'find'), false);
+ render('findAll-thrpt', shape(thrpt.benches, 'findAll'), false);
+ render('count-thrpt', shape(thrpt.benches, 'count'), false);
+ }
+ if (alloc) {
+ render('find-alloc', shape(alloc.benches, 'find'), true);
+ render('findAll-alloc', shape(alloc.benches, 'findAll'), true);
+ render('count-alloc', shape(alloc.benches, 'count'), true);
+ }
+ const ref = thrpt || alloc;
+ if (ref && ref.commit) {
+ const when = ref.date ? new Date(ref.date).toISOString().slice(0, 10) : '';
+ const sha = (ref.commit.id || '').slice(0, 8);
+ const url = ref.commit.url || '#';
+ document.getElementById('lastUpdate').innerHTML =
+ 'Latest run: ' + when + ' · commit <a href="' + url + '">' + sha + '</a>';
+ }
+ }
+
+ document.querySelectorAll('input[name="mode"]').forEach(el =>
+ el.addEventListener('change', e => draw(e.target.value)));
+ draw('indy');
+})();
+</script>
+</body>
+</html>
diff --git a/dev/bench/jmh-adhoc/vendor/README.md b/dev/bench/jmh-adhoc/vendor/README.md
new file mode 100644
index 0000000000..71e6dd5835
--- /dev/null
+++ b/dev/bench/jmh-adhoc/vendor/README.md
@@ -0,0 +1,73 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+# Vendored dashboard dependencies
+
+These JavaScript files are vendored verbatim (no modification) so the
+performance dashboard published to the `gh-pages` branch has no
+third-party CDN runtime dependency. This removes a supply-chain /
+availability surface from the published page.
+
+When bumping a version: re-download from the exact pinned URL below,
+replace the file, update the version and SHA-256 here, and verify the
+dashboard still renders.
+
+| File | Project | Version | Upstream | License |
+|------|---------|---------|----------|---------|
+| `chart.umd.min.js` | Chart.js | 4.4.4 | https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js | MIT |
+| `chartjs-adapter-date-fns.bundle.min.js` | chartjs-adapter-date-fns (bundles date-fns) | 3.0.0 | https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js | MIT |
+
+SHA-256 (as vendored):
+
+```
+b38076762f7363bc9e912b68b8e034826798db5df26bb61f000ec2e7a3137bc7 chart.umd.min.js
+ea7ab30d26c38dcf1f2d26bb43e73a94537b58f1906f55e1a546dd09321b5615 chartjs-adapter-date-fns.bundle.min.js
+```
+
+Both projects (and the `date-fns` library bundled inside the adapter)
+are distributed under the MIT License. Bundling MIT-licensed works in
+an Apache-licensed project is permitted; the MIT license text is
+reproduced below as required by its terms.
+
+## The MIT License
+
+```
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+Upstream license sources:
+- Chart.js: https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
+- chartjs-adapter-date-fns: https://github.com/chartjs/chartjs-adapter-date-fns/blob/master/LICENSE.md
+- date-fns: https://github.com/date-fns/date-fns/blob/main/LICENSE.md
diff --git a/dev/bench/jmh-adhoc/vendor/chart.umd.min.js b/dev/bench/jmh-adhoc/vendor/chart.umd.min.js
new file mode 100644
index 0000000000..06d52d4ec3
--- /dev/null
+++ b/dev/bench/jmh-adhoc/vendor/chart.umd.min.js
@@ -0,0 +1,20 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/chart.js@4.4.4/dist/chart.umd.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+/*!
+ * Chart.js v4.4.4
+ * https://www.chartjs.org
+ * (c) 2024 Chart.js Contributors
+ * Released under the MIT License
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++} [...]
+/*!
+ * @kurkle/color v0.3.2
+ * https://github.com/kurkle/color#readme
+ * (c) 2023 Jukka Kurkela
+ * Released under the MIT License
+ */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot [...]
+//# sourceMappingURL=chart.umd.js.map
diff --git a/dev/bench/jmh-adhoc/vendor/chartjs-adapter-date-fns.bundle.min.js b/dev/bench/jmh-adhoc/vendor/chartjs-adapter-date-fns.bundle.min.js
new file mode 100644
index 0000000000..37bffe67e8
--- /dev/null
+++ b/dev/bench/jmh-adhoc/vendor/chartjs-adapter-date-fns.bundle.min.js
@@ -0,0 +1,7 @@
+/*!
+ * chartjs-adapter-date-fns v3.0.0
+ * https://www.chartjs.org
+ * (c) 2022 chartjs-adapter-date-fns Contributors
+ * Released under the MIT license
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart)}(this,(function(t){"use strict";function e(t){if(null===t||!0===t||!1===t)return NaN;var e=Number(t);return isNaN(e)?e:e<0?Math.ceil(e):Math.floor(e)}function r(t,e){if(e.length<t)throw new TypeError(t+" argument"+(t>1?"s":"")+" required, but only "+e.length+" present")}functi [...]