| """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() |