From ee4e4d04b2dc24bcbd2de0eeb71cfd23d4ecad6e Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Wed, 5 Mar 2025 07:58:12 +0100 Subject: [PATCH] Add plot_benchmarks scripts The script is intended for a collection of hyperfine JSON files where multiple benchmarks were run for a selected group of commands. --- scripts/plot_benchmarks.py | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 scripts/plot_benchmarks.py diff --git a/scripts/plot_benchmarks.py b/scripts/plot_benchmarks.py new file mode 100755 index 0000000..3c81ba8 --- /dev/null +++ b/scripts/plot_benchmarks.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "matplotlib", +# "pyqt6", +# "numpy", +# ] +# /// + +# This program shows `hyperfine` benchmark results as a bar plot grouped by command. + +import argparse +import json +import pathlib + +import matplotlib.pyplot as plt +import numpy as np + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument( + "files", nargs="+", type=pathlib.Path, help="JSON files with benchmark results" +) +parser.add_argument("--title", help="Plot Title") +parser.add_argument( + "--benchmark-names", nargs="+", help="Names of the benchmark groups" +) +parser.add_argument("-o", "--output", help="Save image to the given filename") + +args = parser.parse_args() + +commands = None +data = [] +inputs = [] + +if args.benchmark_names: + assert len(args.files) == len( + args.benchmark_names + ), "Number of benchmark names must match the number of input files." + +for i, filename in enumerate(args.files): + with open(filename) as f: + results = json.load(f)["results"] + benchmark_commands = [b["command"] for b in results] + if commands is None: + commands = benchmark_commands + else: + assert ( + commands == benchmark_commands + ), f"Unexpected commands in {filename}: {benchmark_commands}, expected: {commands}" + data.append([round(b["mean"], 2) for b in results]) + if args.benchmark_names: + inputs.append(args.benchmark_names[i]) + else: + inputs.append(filename.stem) + +data = np.transpose(data) +x = np.arange(len(inputs)) # the label locations +width = 0.25 # the width of the bars + +fig, ax = plt.subplots(layout="constrained") +fig.set_figheight(5) +fig.set_figwidth(10) +for i, command in enumerate(commands): + offset = width * (i + 1) + rects = ax.bar(x + offset, data[i], width, label=command) + +ax.set_xticks(x + 0.5, inputs) +ax.grid(visible=True, axis="y") + +if args.title: + plt.title(args.title) +plt.xlabel("Benchmark") +plt.ylabel("Time [s]") +plt.legend(title="Command") + +if args.output: + plt.savefig(args.output) +else: + plt.show()