From d9fe0cd914d1d426127412545569cd93d9dce8e1 Mon Sep 17 00:00:00 2001 From: Mike McCandless Date: Tue, 10 Mar 2026 06:05:20 -0400 Subject: [PATCH 01/28] Claude Code (Opus 4.6) helped me write a simple JMH benchy to test the many options for sorting a Lucene ScoreDoc[] by docid, helpful often for post-processing hits from a search. For example, if you will go and retrieve doc values or stored fields for each of these hits, you'll want to collate+sort by leaf so you can use a single iterator, always moving forwards by docid --- lucene/benchmark-jmh/jmh-table.py | 577 ++++++++++++++++++ .../benchmark/jmh/ScoreDocSortBenchmark.java | 236 +++++++ 2 files changed, 813 insertions(+) create mode 100755 lucene/benchmark-jmh/jmh-table.py create mode 100644 lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java diff --git a/lucene/benchmark-jmh/jmh-table.py b/lucene/benchmark-jmh/jmh-table.py new file mode 100755 index 000000000000..4c235dfc0eec --- /dev/null +++ b/lucene/benchmark-jmh/jmh-table.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +"""Parse JMH JSON output from stdin, produce an interactive HTML table on stdout. + +Supports both JSON (-rf json) and plain text JMH output. +With JSON input, clicking a cell shows a histogram of the raw iteration samples +and the benchmark method source code. + +Usage: + # JSON (recommended – enables histograms + source): + java --module-path ... --module org.apache.lucene.benchmark.jmh ScoreDocSortBenchmark \ + -rf json -rff results.json \ + && python3 jmh-table.py [BenchmarkSource.java] < results.json > results.html + + # Plain text (no histograms): + java --module-path ... --module org.apache.lucene.benchmark.jmh ScoreDocSortBenchmark \ + | python3 jmh-table.py > results.html + + The optional positional argument is the path to the Java source file containing + the @Benchmark methods. If provided, clicking a cell also shows the method source. +""" + +import sys +import re +import json +import html + + +def parse_jmh_text(text): + """Parse plain-text JMH output.""" + entries = [] + for line in text.splitlines(): + m = re.match( + r'\S+\.(\S+)\s+' + r'(\S+)\s+' + r'\S+\s+' + r'\d+\s+' + r'(\S+)\s+' + r'.\s+' + r'(\S+)\s+' + r'(\S+)', + line, + ) + if m: + method, param, score, error, unit = m.groups() + entries.append({ + 'method': method, + 'param': param, + 'score': float(score), + 'error': float(error), + 'unit': unit, + 'raw': [], + }) + return entries, {} + + +def parse_jmh_json(data): + """Parse JMH JSON output. Returns (entries, config_dict).""" + entries = [] + config = {} + for i, result in enumerate(data): + bench = result['benchmark'].rsplit('.', 1)[-1] + params = result.get('params', {}) + param = list(params.values())[0] if params else '' + pm = result['primaryMetric'] + raw = [] + for fork_data in pm.get('rawData', []): + raw.extend(fork_data) + entries.append({ + 'method': bench, + 'param': param, + 'score': pm['score'], + 'error': pm['scoreError'], + 'unit': pm['scoreUnit'], + 'raw': raw, + }) + if i == 0: + mode_map = {'avgt': 'Average Time', 'thrpt': 'Throughput', + 'sample': 'Sampling', 'ss': 'Single Shot'} + config = { + 'mode': mode_map.get(result.get('mode', ''), result.get('mode', '?')), + 'forks': result.get('forks', '?'), + 'threads': result.get('threads', '?'), + 'warmupIterations': result.get('warmupIterations', '?'), + 'warmupTime': result.get('warmupTime', '?'), + 'measurementIterations': result.get('measurementIterations', '?'), + 'measurementTime': result.get('measurementTime', '?'), + 'jvmArgs': result.get('jvmArgs', []), + } + return entries, config + + +def extract_methods(source_path): + """Extract @Benchmark method bodies from a Java source file. + + Returns dict of method_name -> source_code_string. + """ + methods = {} + if not source_path: + return methods + try: + with open(source_path, 'r') as f: + lines = f.readlines() + except (OSError, IOError): + return methods + + i = 0 + while i < len(lines): + # Look for @Benchmark annotation + if '@Benchmark' in lines[i]: + # Collect comment lines above @Benchmark + comment_start = i + j = i - 1 + while j >= 0 and lines[j].strip().startswith('//'): + comment_start = j + j -= 1 + # Find method signature (next line with '{') + sig_line = i + 1 + while sig_line < len(lines) and '{' not in lines[sig_line]: + sig_line += 1 + if sig_line >= len(lines): + i += 1 + continue + # Extract method name + sig = lines[sig_line].strip() + m = re.search(r'\b(\w+)\s*\(', sig) + if not m: + i += 1 + continue + method_name = m.group(1) + # Find matching closing brace by counting + depth = 0 + end_line = sig_line + for k in range(sig_line, len(lines)): + depth += lines[k].count('{') - lines[k].count('}') + if depth == 0: + end_line = k + break + # Extract the full method including leading comment + method_lines = lines[comment_start:end_line + 1] + # Dedent: find minimum leading whitespace + non_empty = [l for l in method_lines if l.strip()] + if non_empty: + min_indent = min(len(l) - len(l.lstrip()) for l in non_empty) + method_lines = [l[min_indent:] if len(l) > min_indent else l for l in method_lines] + methods[method_name] = ''.join(method_lines).rstrip() + i = end_line + 1 + else: + i += 1 + return methods + + +def lerp_color(t): + """Green (t=0, best) -> yellow (t=0.5) -> red (t=1, worst).""" + t = max(0.0, min(1.0, t)) + if t < 0.5: + u = t * 2 + r = int(120 * u) + g = 180 + b = int(80 * (1 - u)) + else: + u = (t - 0.5) * 2 + r = 120 + int(100 * u) + g = int(180 * (1 - u)) + b = 0 + return r, g, b + + +def sparkline_svg(raw_samples, width=120, height=24, num_bins=20): + """Generate a tiny inline SVG histogram sparkline from raw samples.""" + if not raw_samples or len(raw_samples) < 2: + return '' + lo = min(raw_samples) + hi = max(raw_samples) + span = hi - lo + if span == 0: + span = 1 + bins = [0] * num_bins + for v in raw_samples: + idx = int((v - lo) / span * num_bins) + if idx >= num_bins: + idx = num_bins - 1 + bins[idx] += 1 + max_count = max(bins) + if max_count == 0: + return '' + bar_w = width / num_bins + bars = [] + for i, count in enumerate(bins): + bar_h = (count / max_count) * height + x = i * bar_w + y = height - bar_h + # Color from green (low) to red (high) + t = i / max(num_bins - 1, 1) + r = int(40 + 180 * t) + g = int(160 - 80 * t) + b = int(80 - 60 * t) + bars.append( + f'' + ) + return ( + f'' + + ''.join(bars) + + '' + ) + + +def build_html(entries, config, method_sources): + if not entries: + print("No JMH results found on stdin.", file=sys.stderr) + sys.exit(1) + + has_raw = any(e['raw'] for e in entries) + has_source = bool(method_sources) + + seen_params = dict() + seen_methods = dict() + for e in entries: + seen_params[e['param']] = None + seen_methods[e['method']] = None + params = list(seen_params) + methods = list(seen_methods) + unit = entries[0]['unit'] + + grid = {} + for e in entries: + grid.setdefault(e['method'], {})[e['param']] = e + + col_min = {} + col_max = {} + for p in params: + scores = [grid[m][p]['score'] for m in methods if p in grid[m]] + col_min[p] = min(scores) if scores else 0 + col_max[p] = max(scores) if scores else 1 + + h = html.escape + + raw_js = {} + for e in entries: + if e['raw']: + raw_js[f"{e['method']}|{e['param']}"] = e['raw'] + + sources_js = {name: src for name, src in method_sources.items()} + + out = [] + out.append(f""" +JMH Results + + +

JMH Results

""") + + # Config banner + if config: + out.append('
') + items = [ + ('Mode', config.get('mode', '?')), + ('Forks', config.get('forks', '?')), + ('Threads', config.get('threads', '?')), + ('Warmup', f"{config.get('warmupIterations','?')} iter × {config.get('warmupTime','?')}"), + ('Measurement', f"{config.get('measurementIterations','?')} iter × {config.get('measurementTime','?')}"), + ] + jvm_args = config.get('jvmArgs', []) + if jvm_args: + items.append(('JVM args', ' '.join(str(a) for a in jvm_args))) + for label, val in items: + out.append(f'{h(label)}: {h(str(val))}') + out.append('
') + + click_hint = '' + if has_raw or has_source: + click_hint = ' Click a data cell to see' + parts = [] + if has_raw: + parts.append('its iteration histogram') + if has_source: + parts.append('the method source code') + click_hint += ' ' + ' and '.join(parts) + '.' + + out.append(f'

Click column headers to sort.{click_hint}

') + out.append('
') + out.append('') + + out.append(f'') + for i, p in enumerate(params): + out.append(f'') + out.append('') + + for method in methods: + out.append('') + out.append(f'') + for p in params: + if p in grid[method]: + e = grid[method][p] + score, error = e['score'], e['error'] + span = col_max[p] - col_min[p] + t = (score - col_min[p]) / span if span > 0 else 0 + r, g, b = lerp_color(t) + key = f"{method}|{p}" + cls = ' clickable' if (has_raw or has_source) else '' + spark = sparkline_svg(e['raw']) if e['raw'] else '' + out.append( + f'' + ) + else: + out.append('') + out.append('') + + out.append('
Algorithmsize={h(p)}
{h(unit)}
{h(method)}' + f'{score:.3f} ± {error:.3f}' + f'{spark}-
') + out.append('
') # end left-col + out.append('

') + out.append('
') # end main-area + out.append('
') + + out.append('') + return '\n'.join(out) + + +if __name__ == '__main__': + # Optional positional arg: path to Java source file + source_path = sys.argv[1] if len(sys.argv) > 1 else None + method_sources = extract_methods(source_path) + + text = sys.stdin.read().strip() + if not text: + print("No input on stdin.", file=sys.stderr) + sys.exit(1) + + if text.startswith('[') or text.startswith('{'): + data = json.loads(text) + if isinstance(data, dict): + data = [data] + entries, config = parse_jmh_json(data) + else: + entries, config = parse_jmh_text(text) + + print(build_html(entries, config, method_sources)) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java new file mode 100644 index 000000000000..a7e2ba5f8026 --- /dev/null +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java @@ -0,0 +1,236 @@ +/* + * 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. + */ +package org.apache.lucene.benchmark.jmh; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.SplittableRandom; +import java.util.concurrent.TimeUnit; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.InPlaceMergeSorter; +import org.apache.lucene.util.IntroSorter; +import org.apache.lucene.util.TimSorter; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Benchmark comparing different sort implementations for sorting ScoreDoc[] by ascending doc ID. + * Simulates realistic ScoreDoc arrays with random doc IDs drawn from a large index and random + * scores. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 20, time = 1) +@Fork( + value = 8, + jvmArgsAppend = {"-Xmx1g", "-Xms1g", "-XX:+AlwaysPreTouch"}) +public class ScoreDocSortBenchmark { + + private static final Comparator BY_DOC_ASC = + (a, b) -> Integer.compare(a.doc, b.doc); + + @Param({"10", "100", "1000", "10000"}) + int size; + + /** Template array; copied before each invocation so every sort sees the same random order. */ + private ScoreDoc[] template; + + /** Working copy that each benchmark method sorts in place. */ + private ScoreDoc[] work; + + @Setup(Level.Trial) + public void setupTrial() { + SplittableRandom rng = new SplittableRandom(0xCAFEBABE); + int maxDoc = 5_000_000; // realistic large index size + template = new ScoreDoc[size]; + for (int i = 0; i < size; i++) { + int doc = rng.nextInt(maxDoc); + float score = (float) rng.nextDouble(0.0, 10.0); + template[i] = new ScoreDoc(doc, score); + } + } + + @Setup(Level.Invocation) + public void setupInvocation() { + work = new ScoreDoc[size]; + for (int i = 0; i < size; i++) { + work[i] = template[i]; // shallow copy – same ScoreDoc objects, different array + } + } + + // ---- 1. JDK Arrays.sort with lambda ---- + + @Benchmark + public void jdkSortLambda(Blackhole bh) { + Arrays.sort(work, (a, b) -> Integer.compare(a.doc, b.doc)); + bh.consume(work); + } + + // ---- 2. JDK Arrays.sort with static comparator ---- + + @Benchmark + public void jdkSortComparator(Blackhole bh) { + Arrays.sort(work, BY_DOC_ASC); + bh.consume(work); + } + + // ---- 3. ArrayUtil.introSort (wraps ArrayIntroSorter) ---- + + @Benchmark + public void arrayUtilIntroSort(Blackhole bh) { + ArrayUtil.introSort(work, BY_DOC_ASC); + bh.consume(work); + } + + // ---- 4. ArrayUtil.timSort (wraps ArrayTimSorter) ---- + + @Benchmark + public void arrayUtilTimSort(Blackhole bh) { + ArrayUtil.timSort(work, BY_DOC_ASC); + bh.consume(work); + } + + // ---- 5. Anonymous IntroSorter ---- + + @Benchmark + public void introSorterAnonymous(Blackhole bh) { + final ScoreDoc[] arr = work; + new IntroSorter() { + ScoreDoc pivot; + + @Override + protected void swap(int i, int j) { + ScoreDoc tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + @Override + protected void setPivot(int i) { + pivot = arr[i]; + } + + @Override + protected int comparePivot(int j) { + return Integer.compare(pivot.doc, arr[j].doc); + } + + @Override + protected int compare(int i, int j) { + return Integer.compare(arr[i].doc, arr[j].doc); + } + }.sort(0, arr.length); + bh.consume(work); + } + + // ---- 6. Anonymous TimSorter ---- + + @Benchmark + public void timSorterAnonymous(Blackhole bh) { + final ScoreDoc[] arr = work; + final int len = arr.length; + new TimSorter(len / 2) { + ScoreDoc[] tmp = new ScoreDoc[len / 2]; + + @Override + protected void swap(int i, int j) { + ScoreDoc t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } + + @Override + protected int compare(int i, int j) { + return Integer.compare(arr[i].doc, arr[j].doc); + } + + @Override + protected void copy(int src, int dest) { + arr[dest] = arr[src]; + } + + @Override + protected void save(int start, int l) { + System.arraycopy(arr, start, tmp, 0, l); + } + + @Override + protected void restore(int src, int dest) { + arr[dest] = tmp[src]; + } + + @Override + protected int compareSaved(int i, int j) { + return Integer.compare(tmp[i].doc, arr[j].doc); + } + }.sort(0, len); + bh.consume(work); + } + + // ---- 7. Anonymous InPlaceMergeSorter ---- + + @Benchmark + public void inPlaceMergeSorterAnonymous(Blackhole bh) { + final ScoreDoc[] arr = work; + new InPlaceMergeSorter() { + @Override + protected void swap(int i, int j) { + ScoreDoc tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + @Override + protected int compare(int i, int j) { + return Integer.compare(arr[i].doc, arr[j].doc); + } + }.sort(0, arr.length); + bh.consume(work); + } + + // ---- 8. Extract doc IDs, sort with JDK Arrays.sort (primitive long[]), reorder ---- + + @Benchmark + public void jdkSortPrimitiveExtract(Blackhole bh) { + int len = work.length; + // Build parallel array of (doc, originalIndex) packed into a long for a single-array sort + long[] packed = new long[len]; + for (int i = 0; i < len; i++) { + packed[i] = ((long) work[i].doc << 32) | (i & 0xFFFFFFFFL); + } + Arrays.sort(packed); + ScoreDoc[] sorted = new ScoreDoc[len]; + for (int i = 0; i < len; i++) { + sorted[i] = work[(int) packed[i]]; + } + bh.consume(sorted); + } +} From 7c93cb48f3b5c141da8101e4b2d9f7643a3b4646 Mon Sep 17 00:00:00 2001 From: "Claude AI Opus 4.6" Date: Tue, 10 Mar 2026 06:12:19 -0400 Subject: [PATCH 02/28] Add javadoc cross-reference to jmh-table.py + tidy formatting Model: Claude AI Opus 4.6 (claude-opus-4-6) This commit captures the full prompt history for the iterative development of ScoreDocSortBenchmark.java and jmh-table.py across this session. The prior commit (65b3068) included the code but not the prompts. Files changed: - lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java (javadoc tweak + tidy reformatting) - lucene/benchmark-jmh/jmh-table.py (created in prior commit) == User prompt 1 == i'd like to compare performance of Lucene's numerous built-in sorters on the use case of sorting an array of Lucene's ScoreDoc by ascending docid. one simply way is to use java Arrays.sort with lambda key that compares the .doc of each. another is to instantiate IntroSorter and in the anonymous subclass sort by compare doc. write a standalone benchmark that creates a random array of "realistic" == User prompt 2 == ScoreDoc and then tests many sort methods, measuring their latency == User prompt 3 (after build required Java 25) == - always use Java 25, installed at /usr/lib/jvm/java-25-openjdk/ == User prompt 4 == could you write the necessary code/tool so i can pipe JMH output through this tool and it makes a simple interactive table with algo on each row, and columns for the size of each batch. and when i click column header it sorts whole table by that column. click again and it reverses that sort. == User prompt 5 == what is the xdg-open tool == User prompt 6 == is there some way to make the table more visual? shades from green to red for the background of each cell maybe? == User prompt 7 == can JMH sort the results by fastest to slowest somehow? can you rename the methods using JDK sort functions (Arrays.sort) to jdkSuchAndSuch? == User prompt 8 == is it possible to ask jmh to output all values it recorded, in the end == User prompt 9 == yes -- specifically, when i click on a cell in the HTML table, draw a histogram under the table showing the distribution of values that led to that cell's avg (hmm is it average that JMH is reporting?) == User prompt 10 == i'd like to improve efficiency of running this benchmark by using multiple jvms/threads concurretnly so generate more data points per second of runtime. is that simple? == User prompt 11 == hmm i see it has a threads option? but that caused exceptions when i tried it. do you need to make each benchy thread safe? == User prompt 12 == sometimes the histogram has poor x axis labels, in one case 10 labels that all say 0.04. can you fix the x axis to always have enough precision? and switch to ns/op if appropriate? == User prompt 13 == fix the html report to also state how JMH was run (forking, threads, iterations). when i click on a cell, in addition to showing histogram, to the right of the full table, show the method (in fixed width font) source code for that algorithm. == User prompt 14 == modify the table so that each cell, under the numeric mean, renders a small sparkle histogram (with no axis labels) so human can visually consume any lurking monsters == User prompt 15 == let's do the trivial comment, and a commit that includes all past prompts in our session here == Summary of work done == 1. Created ScoreDocSortBenchmark.java: JMH benchmark comparing 8 sort implementations for sorting ScoreDoc[] by ascending doc ID: - jdkSortLambda, jdkSortComparator (JDK Arrays.sort) - arrayUtilIntroSort, arrayUtilTimSort (Lucene ArrayUtil wrappers) - introSorterAnonymous, timSorterAnonymous, inPlaceMergeSorterAnonymous (anonymous Lucene Sorter subclasses) - jdkSortPrimitiveExtract (pack doc+index into long[], sort primitives) 2. Created jmh-table.py: Python script that parses JMH output (JSON or plain text) and generates an interactive HTML report with: - Heatmap table (green=fast, red=slow) with sortable columns - Inline sparkline SVG histograms in each cell showing distribution - Click-to-expand full histogram with stats (mean, median, stddev, p5/p95, range) and adaptive axis precision/unit scaling - Source code panel showing the clicked benchmark method - JMH config banner (mode, forks, threads, warmup, measurement, JVM args) - Auto-detection of JSON vs plain text input Co-authored-by: Mike McCandless --- .../apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java index a7e2ba5f8026..de373009733e 100644 --- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java +++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/ScoreDocSortBenchmark.java @@ -42,7 +42,7 @@ /** * Benchmark comparing different sort implementations for sorting ScoreDoc[] by ascending doc ID. * Simulates realistic ScoreDoc arrays with random doc IDs drawn from a large index and random - * scores. + * scores. Use jmh-table.py to visualize JSON results as an interactive HTML report. */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -54,8 +54,7 @@ jvmArgsAppend = {"-Xmx1g", "-Xms1g", "-XX:+AlwaysPreTouch"}) public class ScoreDocSortBenchmark { - private static final Comparator BY_DOC_ASC = - (a, b) -> Integer.compare(a.doc, b.doc); + private static final Comparator BY_DOC_ASC = (a, b) -> Integer.compare(a.doc, b.doc); @Param({"10", "100", "1000", "10000"}) int size; From eaacc4faaf7bb6cab3b82048aec0031479873261 Mon Sep 17 00:00:00 2001 From: "Claude AI Opus 4.6" Date: Tue, 10 Mar 2026 06:17:21 -0400 Subject: [PATCH 03/28] Fix config banner: use Unicode multiply char, one setting per row - × was double-escaped by html.escape(); use Unicode \u00d7 instead - config banner is now a proper with one label+value per row instead of all on one line Model: Claude AI Opus 4.6 (claude-opus-4-6) == User prompt == the new section in the HTML report that shows JMH settings is rendering × visually, not as a single unicode multiplication character. something is escaping & maybe? also, make that table one config+value per line. Co-authored-by: Mike McCandless --- lucene/benchmark-jmh/jmh-table.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lucene/benchmark-jmh/jmh-table.py b/lucene/benchmark-jmh/jmh-table.py index 4c235dfc0eec..65d1f697aaaf 100755 --- a/lucene/benchmark-jmh/jmh-table.py +++ b/lucene/benchmark-jmh/jmh-table.py @@ -249,10 +249,10 @@ def build_html(entries, config, method_sources):

JMH Results

""") @@ -304,29 +360,11 @@ def build_html(entries, config, method_sources): ('Warmup', f"{config.get('warmupIterations','?')} iter \u00d7 {config.get('warmupTime','?')}"), ('Measurement', f"{config.get('measurementIterations','?')} iter \u00d7 {config.get('measurementTime','?')}"), ] - # JVM identity - jvm = config.get('jvm', '') - jdk_ver = config.get('jdkVersion', '') - vm_name = config.get('vmName', '') - vm_ver = config.get('vmVersion', '') - jvm_desc = ' '.join(s for s in [vm_name, vm_ver] if s) - if jdk_ver: - jvm_desc = f"JDK {jdk_ver}, {jvm_desc}" if jvm_desc else f"JDK {jdk_ver}" - if jvm: - jvm_desc += f" ({jvm})" if jvm_desc else jvm - if jvm_desc: - items.append(('JVM', jvm_desc)) - jmh_ver = config.get('jmhVersion', '') - if jmh_ver: - items.append(('JMH version', jmh_ver)) - # benchmark JVM args (from @Fork annotation, e.g. -Xmx, -XX:) - bench_args = config.get('benchmarkJvmArgs', []) - if bench_args: - items.append(('Fork JVM args', ' '.join(bench_args))) - # harness JVM args (module-path, module-main, etc.) - harness_args = config.get('harnessJvmArgs', []) - if harness_args: - items.append(('Harness JVM args', ' '.join(harness_args))) + jvm_desc = ' '.join(s for s in [config.get('vmName', ''), config.get('vmVersion', '')] if s) + if config.get('jdkVersion'): jvm_desc = f"JDK {config.get('jdkVersion')}, {jvm_desc}" + if jvm_desc: items.append(('JVM', jvm_desc)) + if config.get('jmhVersion'): items.append(('JMH version', config.get('jmhVersion'))) + if config.get('benchmarkJvmArgs'): items.append(('Fork JVM args', ' '.join(config.get('benchmarkJvmArgs')))) for label, val in items: out.append(f'
') out.append('
{h(label)}{h(val)}
') @@ -335,85 +373,98 @@ def build_html(entries, config, method_sources): if has_raw or has_source: click_hint = ' Click a data cell to see' parts = [] - if has_raw: - parts.append('its iteration histogram') - if has_source: - parts.append('the method source code') + if has_raw: parts.append('its iteration histogram') + if has_source: parts.append('the method source code') click_hint += ' ' + ' and '.join(parts) + '.' out.append(f'

Click column headers to sort.{click_hint}

') - out.append('

') + out.append('
') + out.append('

') + '
') + out.append('
') + out.append('
') out.append('') - - out.append(f'') - for i, p in enumerate(params): - out.append(f'') + out.append('') + for i, s in enumerate(sizes): + out.append(f'') out.append('') + default_dist = 'random' if 'random' in dists else dists[0] for method in methods: out.append('') out.append(f'') - for p in params: - if p in grid[method]: - e = grid[method][p] - score, error = e['score'], e['error'] - span = col_max[p] - col_min[p] - t = (score - col_min[p]) / span if span > 0 else 0 - r, g, b = lerp_color(t) - key = f"{method}|{p}" - cls = ' clickable' if (has_raw or has_source) else '' - spark = sparkline_svg(e['raw']) if e['raw'] else '' - rel = score / col_min[p] if col_min[p] > 0 else 1.0 - out.append( - f'' - ) - else: - out.append('') + for s in sizes: + out.append(f'') out.append('') out.append('
Algorithmsize={h(p)}
{h(unit)}
Algorithmsize={h(s)}
{h(unit)}
{h(method)}' - f'{score:.3f} ± {error:.3f}' - f'{spark}-
') - out.append('
') # end left-col + out.append('
') out.append('

') - out.append('') # end main-area + out.append('') out.append('
') out.append('