| """Reads JSON files produced by the benchmarking framework and renders them. | |
| Installation: | |
| > apt-get install python3-pip | |
| > pip3 install matplotlib pandas seaborn | |
| Run: | |
| > python3 libc/benchmarks/libc-benchmark-analysis.py3 <files> | |
| """ | |
| import argparse | |
| import json | |
| import pandas as pd | |
| import seaborn as sns | |
| import matplotlib.pyplot as plt | |
| from matplotlib.ticker import EngFormatter | |
| def formatUnit(value, unit): | |
| return EngFormatter(unit, sep="").format_data(value) | |
| def formatCache(cache): | |
| letter = cache["Type"][0].lower() | |
| level = cache["Level"] | |
| size = formatUnit(cache["Size"], "B") | |
| ways = cache["NumSharing"] | |
| return F'{letter}L{level}:{size}/{ways}' | |
| def getCpuFrequency(study): | |
| return study["Runtime"]["Host"]["CpuFrequency"] | |
| def getId(study): | |
| CpuName = study["Runtime"]["Host"]["CpuName"] | |
| CpuFrequency = formatUnit(getCpuFrequency(study), "Hz") | |
| Mode = " (Sweep)" if study["Configuration"]["IsSweepMode"] else "" | |
| CpuCaches = ", ".join(formatCache(c) for c in study["Runtime"]["Host"]["Caches"]) | |
| return F'{CpuName} {CpuFrequency}{Mode}\n{CpuCaches}' | |
| def getFunction(study): | |
| return study["Configuration"]["Function"] | |
| def getLabel(study): | |
| return F'{getFunction(study)} {study["StudyName"]}' | |
| def displaySweepData(id, studies, mode): | |
| df = None | |
| for study in studies: | |
| Measurements = study["Measurements"] | |
| SweepModeMaxSize = study["Configuration"]["SweepModeMaxSize"] | |
| NumSizes = SweepModeMaxSize + 1 | |
| NumTrials = study["Configuration"]["NumTrials"] | |
| assert NumTrials * NumSizes == len(Measurements), 'not a multiple of NumSizes' | |
| Index = pd.MultiIndex.from_product([range(NumSizes), range(NumTrials)], names=['size', 'trial']) | |
| if df is None: | |
| df = pd.DataFrame(Measurements, index=Index, columns=[getLabel(study)]) | |
| else: | |
| df[getLabel(study)] = pd.Series(Measurements, index=Index) | |
| df = df.reset_index(level='trial', drop=True) | |
| if mode == "cycles": | |
| df *= getCpuFrequency(study) | |
| if mode == "bytespercycle": | |
| df *= getCpuFrequency(study) | |
| for col in df.columns: | |
| df[col] = pd.Series(data=df.index, index=df.index).divide(df[col]) | |
| FormatterUnit = {"time":"s","cycles":"","bytespercycle":"B/cycle"}[mode] | |
| Label = {"time":"Time","cycles":"Cycles","bytespercycle":"Byte/cycle"}[mode] | |
| graph = sns.lineplot(data=df, palette="muted", ci=95) | |
| graph.set_title(id) | |
| graph.yaxis.set_major_formatter(EngFormatter(unit=FormatterUnit)) | |
| graph.yaxis.set_label_text(Label) | |
| graph.xaxis.set_major_formatter(EngFormatter(unit="B")) | |
| graph.xaxis.set_label_text("Copy Size") | |
| _ = plt.xticks(rotation=90) | |
| plt.show() | |
| def displayDistributionData(id, studies, mode): | |
| distributions = set() | |
| df = None | |
| for study in studies: | |
| distribution = study["Configuration"]["SizeDistributionName"] | |
| distributions.add(distribution) | |
| local = pd.DataFrame(study["Measurements"], columns=["time"]) | |
| local["distribution"] = distribution | |
| local["label"] = getLabel(study) | |
| local["cycles"] = local["time"] * getCpuFrequency(study) | |
| if df is None: | |
| df = local | |
| else: | |
| df = df.append(local) | |
| if mode == "bytespercycle": | |
| mode = "time" | |
| print("`--mode=bytespercycle` is ignored for distribution mode reports") | |
| FormatterUnit = {"time":"s","cycles":""}[mode] | |
| Label = {"time":"Time","cycles":"Cycles"}[mode] | |
| graph = sns.violinplot(data=df, x="distribution", y=mode, palette="muted", hue="label", order=sorted(distributions)) | |
| graph.set_title(id) | |
| graph.yaxis.set_major_formatter(EngFormatter(unit=FormatterUnit)) | |
| graph.yaxis.set_label_text(Label) | |
| _ = plt.xticks(rotation=90) | |
| plt.show() | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Process benchmark json files.") | |
| parser.add_argument("--mode", choices=["time", "cycles", "bytespercycle"], default="time", help="Use to display either 'time', 'cycles' or 'bytes/cycle'.") | |
| parser.add_argument("files", nargs="+", help="The json files to read from.") | |
| args = parser.parse_args() | |
| study_groups = dict() | |
| for file in args.files: | |
| with open(file) as json_file: | |
| json_obj = json.load(json_file) | |
| Id = getId(json_obj) | |
| if Id in study_groups: | |
| study_groups[Id].append(json_obj) | |
| else: | |
| study_groups[Id] = [json_obj] | |
| plt.tight_layout() | |
| sns.set_theme(style="ticks") | |
| for id, study_collection in study_groups.items(): | |
| if "(Sweep)" in id: | |
| displaySweepData(id, study_collection, args.mode) | |
| else: | |
| displayDistributionData(id, study_collection, args.mode) | |
| if __name__ == "__main__": | |
| main() |