|
5 | 5 | import plotly.io as io |
6 | 6 |
|
7 | 7 | # This script parses JMH benchmarking results into charts developed using plot.ly (https://plotly.com/) |
8 | | -# It currently develops one boxplot PER class, with each JMH benchmark method represented as a separate boxplot. |
9 | 8 | # It expects JMH benchmark results be dumped to a file "scijava-ops-benchmark_results.json", within its directory. |
10 | 9 |
|
11 | | -# If you'd like to add a title to the plotly charts, add an entry to the following dict. |
12 | | -# |
13 | | -# The key should be the simple name of the class containing the JMH benchmark |
14 | | -# and the value should be the title of the chart |
15 | | -figure_titles = { |
16 | | - "BenchmarkFrameworks" : "Algorithm Execution Performance by Framework", |
17 | | - "BenchmarkCaching" : "Caching Effects on Op Matching Performance", |
18 | | - "BenchmarkConversion": "Parameter Conversion Performance", |
19 | | - "BenchmarkMatching": "Basic Op Matching Performance", |
20 | | - "BenchmarkCombined": "Combined Performance Metrics", |
21 | | -} |
22 | | - |
23 | | -# If you'd like to alias a particular test in the chart categories, add an entry to the following dict. |
24 | | -# |
25 | | -# The key should be the JMH benchmark method name, and the value should be the alias |
26 | | -benchmark_categories = { |
27 | | - "imageJOps" : "ImageJ Ops", |
28 | | - "sciJavaOps": "SciJava Ops", |
29 | | - "runStatic" : "Static Method", |
30 | | - "runOp" : "Op Execution", |
31 | | - "runOpCached": "Op Execution (cached)", |
32 | | - "runOpConverted": "Op Execution (converted)", |
33 | | - "runOpConvertedAdapted": "Op Execution (converted + adapted)", |
34 | | - "runOpAdapted": "Op Execution (adapted)", |
35 | | -} |
36 | | - |
37 | | -# Read in the benchmark results |
| 10 | +# If you'd like to add a plotly chart, add an entry to the following list. |
| 11 | + |
| 12 | +A = "<b style=\"color:black\">[<b style=\"color:#009E73\">A</b>]</b>" |
| 13 | +C = "<b style=\"color:black\">[<b style=\"color:#E69F00\">C</b>]</b>" |
| 14 | +AC = "<b style=\"color:black\">[<b style=\"color:#CC79A7\">AC</b>]</b>" |
| 15 | +figures = [ |
| 16 | + { |
| 17 | + "name": "BenchmarkMatching", |
| 18 | + "title": "Basic Op Matching Performance", |
| 19 | + "bars": { |
| 20 | + "noOps": "Static Method", |
| 21 | + "noOpsAdapted": f"Static Method {A}", |
| 22 | + "sjOps": "SciJava Ops", |
| 23 | + "sjOpsAdapted": f"SciJava Ops {A}" |
| 24 | + } |
| 25 | + }, |
| 26 | + { |
| 27 | + "name": "BenchmarkCaching", |
| 28 | + "title": "Caching Effects on Op Matching Performance", |
| 29 | + "bars": { |
| 30 | + "noOps": "Static Method", |
| 31 | + "sjOps": "SciJava Ops", |
| 32 | + "sjOpsWithCache": "SciJava Ops (cached)" |
| 33 | + } |
| 34 | + }, |
| 35 | + { |
| 36 | + "name": "BenchmarkConversion", |
| 37 | + "title": "Parameter Conversion Performance", |
| 38 | + "bars": { |
| 39 | + "noOpsConverted": f"Static Method {C}", |
| 40 | + "noOpsAdaptedAndConverted": f"Static Method {AC}", |
| 41 | + "sjOpsConverted": f"SciJava Ops {C}", |
| 42 | + "sjOpsConvertedAndAdapted": f"SciJava Ops {AC}" |
| 43 | + } |
| 44 | + }, |
| 45 | + { |
| 46 | + "name": "BenchmarkFrameworks", |
| 47 | + "title": "Algorithm Execution Performance by Framework", |
| 48 | + "bars": { |
| 49 | + "noOps": "Static Method", |
| 50 | + "sjOps": "SciJava Ops", |
| 51 | + "ijOps": "ImageJ Ops" |
| 52 | + } |
| 53 | + }, |
| 54 | + { |
| 55 | + "name": "BenchmarkCombined", |
| 56 | + "title": "Combined Performance Metrics", |
| 57 | + "bars": { |
| 58 | + "noOps": "Static Method", |
| 59 | + "noOpsAdapted": f"Static Method {A}", |
| 60 | + "noOpsConverted": f"Static Method {C}", |
| 61 | + "noOpsAdaptedAndConverted": f"Static Method {AC}", |
| 62 | + "sjOpsWithCache": "SciJava Ops (cached)", |
| 63 | + "sjOps": "SciJava Ops", |
| 64 | + "sjOpsAdapted": f"SciJava Ops {A}", |
| 65 | + "sjOpsConverted": f"SciJava Ops {C}", |
| 66 | + "sjOpsConvertedAndAdapted": f"SciJava Ops {AC}", |
| 67 | + "ijOps": "ImageJ Ops", |
| 68 | + } |
| 69 | + } |
| 70 | +] |
| 71 | + |
| 72 | +# Read in the benchmark results. |
38 | 73 | with open("scijava-ops-benchmarks_results.json") as f: |
39 | 74 | data = json.load(f) |
40 | 75 |
|
41 | | -# Build a map of results by benchmark class |
42 | | -benchmark_classes = {} |
43 | | -# And another map of results by benchmark test |
44 | | -benchmark_tests = {} |
| 76 | +# Construct a mapping from test method to scores. |
| 77 | +results = {} |
45 | 78 | for row in data: |
46 | | - fqdn_tokens = row["benchmark"].split(".") |
47 | | - cls, test = fqdn_tokens[-2], fqdn_tokens[-1] |
48 | | - |
49 | | - # NB: Convert seconds to milliseconds |
50 | | - score = 1000 * row["primaryMetric"]["score"] |
51 | | - error = 1000 * row["primaryMetric"]["scoreError"] |
52 | | - stats = {"score": score, "error": error} |
53 | | - |
54 | | - if cls not in benchmark_classes: |
55 | | - benchmark_classes[cls] = {} |
56 | | - |
57 | | - benchmark_classes[cls][test] = stats |
58 | | - |
59 | | - if test == "sciJavaOps": |
60 | | - # NB: sciJavaOps == runOp |
61 | | - test = "runOp" |
62 | | - if test not in benchmark_tests: |
63 | | - benchmark_tests[test] = [] |
64 | | - |
65 | | - benchmark_tests[test].append(stats) |
| 79 | + test = row["benchmark"].split(".")[-1] |
| 80 | + score = row["primaryMetric"]["score"] |
| 81 | + percentiles = row["primaryMetric"]["scorePercentiles"] |
| 82 | + minmax = [percentiles["0.0"], percentiles["100.0"]] |
| 83 | + results[test] = {"score": score, "minmax": minmax} |
66 | 84 |
|
67 | | -# Aggregate results into combined performance metrics |
68 | | -benchmark_classes["BenchmarkCombined"] = {} |
69 | | -for test, stats_list in benchmark_tests.items(): |
70 | | - # Take the average of all scores for this test |
71 | | - score = statistics.fmean(stats["score"] for stats in stats_list) |
72 | | - # Take the *worst* of all errors for this test |
73 | | - error = max(stats["error"] for stats in stats_list) |
74 | | - benchmark_classes["BenchmarkCombined"][test] = {"score": score, "error": error} |
| 85 | +# Build charts and dump them to JSON. |
| 86 | +for figure in figures: |
| 87 | + name = figure["name"] |
| 88 | + print(f"Generating figure for {name}", end="") |
75 | 89 |
|
76 | | -# For each class, build a chart and dump it to JSON |
77 | | -for cls, test in benchmark_classes.items(): |
78 | | - print(f"Generating figure for {cls}", end="") |
79 | 90 | x = [] |
80 | 91 | y = [] |
81 | 92 | error_y = [] |
| 93 | + error_y_minus = [] |
82 | 94 |
|
83 | 95 | # Add each benchmark in the class |
84 | | - for method, stats in sorted(test.items(), key=lambda item: item[1]["score"]): |
85 | | - print(".", end="") |
86 | | - method = benchmark_categories.get(method, method) |
87 | | - x.append(method) |
88 | | - y.append(stats["score"]) |
89 | | - error_y.append(stats["error"]) |
| 96 | + for test, label in figure["bars"].items(): |
| 97 | + print(f".", end="") |
| 98 | + result = results[test] |
| 99 | + x.append(label) |
| 100 | + y.append(result["score"]) |
| 101 | + error_y.append(result["minmax"][1] - result["score"]) |
| 102 | + error_y_minus.append(result["score"] - result["minmax"][0]) |
90 | 103 |
|
91 | 104 | # Create a bar chart |
92 | 105 | fig = go.Figure() |
93 | 106 | fig.add_bar( |
94 | 107 | x=x, |
95 | 108 | y=y, |
96 | | - error_y=dict(type='data', array=error_y), |
| 109 | + error_y=dict(type='data', array=error_y, arrayminus=error_y_minus), |
97 | 110 | ) |
98 | 111 | fig.update_layout( |
99 | | - title_text=figure_titles.get(cls, "TODO: Add title"), |
100 | | - yaxis_title="Performance (ms/op)" |
| 112 | + title_text=figure["title"] + f"<br><sup style=\"color: gray\">{A}=Adaptation, {C}=Conversion, {AC}=Adaptation & Conversion</sup>", |
| 113 | + yaxis_title="<b>Performance (μs/execution)</b>" |
101 | 114 | ) |
102 | 115 |
|
103 | 116 | # Convert to JSON and dump |
104 | | - with open(f"images/{cls}.json", "w") as f: |
| 117 | + with open(f"images/{name}.json", "w") as f: |
105 | 118 | f.write(io.to_json(fig)) |
106 | 119 |
|
107 | 120 | print() |
|
0 commit comments