hyperfine 2.0.0

A command-line benchmarking tool
#!/usr/bin/env python
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "pyqt6",
#     "matplotlib",
#     "numpy",
# ]
# ///

"""This program shows `hyperfine` benchmark results in a sequential way
in order to debug possible background interference, caching effects,
thermal throttling and similar effects.
"""

import argparse

import matplotlib.pyplot as plt
import numpy as np

from plot_utils import METRICS, add_metric_argument, load_results


def moving_average(values, num_runs):
    values_padded = np.pad(
        values, (num_runs // 2, num_runs - 1 - num_runs // 2), mode="edge"
    )
    kernel = np.ones(num_runs) / num_runs
    return np.convolve(values_padded, kernel, mode="valid")


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    add_metric_argument(parser)
    parser.add_argument("file", help="JSON file with benchmark results")
    parser.add_argument("--title", help="Plot Title")
    parser.add_argument("-o", "--output", help="Save image to the given filename.")
    parser.add_argument(
        "-w",
        "--moving-average-width",
        type=int,
        metavar="num_runs",
        help="Width of the moving-average window (default: N/5)",
    )
    parser.add_argument(
        "--no-moving-average",
        action="store_true",
        help="Do not show moving average curve",
    )

    args = parser.parse_args()
    metric, [results] = load_results(parser, [args.file], args.metric)
    metric_info = METRICS[metric]

    for result in results:
        values = [
            m[metric]["value"] / metric_info.scale for m in result["measurements"]
        ]
        num = len(values)
        nums = range(num)

        plt.scatter(x=nums, y=values, marker=".")
        plt.ylim([0, None])
        plt.xlim([-1, num])

        if not args.no_moving_average:
            moving_average_width = (
                max(1, num // 5)
                if args.moving_average_width is None
                else args.moving_average_width
            )

            average = moving_average(values, moving_average_width)
            plt.plot(nums, average, "-")

    if args.title:
        plt.title(args.title)

    legend = []
    for result in results:
        legend.append(result.get("name", result["command"]))
        if not args.no_moving_average:
            legend.append("moving average")
    plt.legend(legend)

    plt.ylabel(metric_info.axis_label)

    if args.output:
        plt.savefig(args.output)
    else:
        plt.show()


if __name__ == "__main__":
    main()