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 &mdash; 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/&micro;s (higher is better);
+  <strong>allocation</strong> is <code>gc.alloc.rate.norm</code> bytes/op
+  (lower is better, log scale &mdash; 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&hellip;</div>
+
+<h2>find <span style="font-weight:300;color:#888">(short-circuit; full traversal here &mdash; no match)</span></h2>
+<div class="grid">
+  <div><h3>Throughput (ops/&micro;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/&micro;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/&micro;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 &amp; raw data:
+    <a href="throughput/indy/">throughput/indy</a> &middot;
+    <a href="throughput/classic/">throughput/classic</a> &middot;
+    <a href="alloc/indy/">alloc/indy</a> &middot;
+    <a href="alloc/classic/">alloc/classic</a>
+    (each <code>&lt;bucket&gt;/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> &middot;
+    <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 + ' &middot; 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 [...]