/ thirdparty / hyperfine / scripts / plot_benchmark_comparison.py
plot_benchmark_comparison.py
 1  #!/usr/bin/env python
 2  # /// script
 3  # requires-python = ">=3.10"
 4  # dependencies = [
 5  #     "matplotlib",
 6  #     "pyqt6",
 7  #     "numpy",
 8  # ]
 9  # ///
10  
11  """
12  This script shows `hyperfine` benchmark results as a bar plot grouped by command.
13  Note all the input files must contain results for all commands.
14  """
15  
16  import argparse
17  import json
18  import pathlib
19  
20  import matplotlib.pyplot as plt
21  import numpy as np
22  
23  parser = argparse.ArgumentParser(description=__doc__)
24  parser.add_argument(
25      "files", nargs="+", type=pathlib.Path, help="JSON files with benchmark results"
26  )
27  parser.add_argument("--title", help="Plot Title")
28  parser.add_argument(
29      "--benchmark-names", nargs="+", help="Names of the benchmark groups"
30  )
31  parser.add_argument("-o", "--output", help="Save image to the given filename")
32  
33  args = parser.parse_args()
34  
35  commands = None
36  data = []
37  inputs = []
38  
39  if args.benchmark_names:
40      assert len(args.files) == len(
41          args.benchmark_names
42      ), "Number of benchmark names must match the number of input files."
43  
44  for i, filename in enumerate(args.files):
45      with open(filename) as f:
46          results = json.load(f)["results"]
47      benchmark_commands = [b["command"] for b in results]
48      if commands is None:
49          commands = benchmark_commands
50      else:
51          assert (
52              commands == benchmark_commands
53          ), f"Unexpected commands in {filename}: {benchmark_commands}, expected: {commands}"
54      data.append([round(b["mean"], 2) for b in results])
55      if args.benchmark_names:
56          inputs.append(args.benchmark_names[i])
57      else:
58          inputs.append(filename.stem)
59  
60  data = np.transpose(data)
61  x = np.arange(len(inputs))  # the label locations
62  width = 0.25  # the width of the bars
63  
64  fig, ax = plt.subplots(layout="constrained")
65  fig.set_figheight(5)
66  fig.set_figwidth(10)
67  for i, command in enumerate(commands):
68      offset = width * (i + 1)
69      rects = ax.bar(x + offset, data[i], width, label=command)
70  
71  ax.set_xticks(x + 0.5, inputs)
72  ax.grid(visible=True, axis="y")
73  
74  if args.title:
75      plt.title(args.title)
76  plt.xlabel("Benchmark")
77  plt.ylabel("Time [s]")
78  plt.legend(title="Command")
79  
80  if args.output:
81      plt.savefig(args.output)
82  else:
83      plt.show()