hls-performance-thesis/code/fpga/plot_fpga.py
2021-07-03 17:59:32 +02:00

170 lines
6.6 KiB
Python

import argparse
import matplotlib as mpl
mpl.use('TkAgg')
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import numpy as np
import json
import glob
from scipy.integrate import simps
kernel_name_map = {
"unopt": "reference FPGA kernel",
"memory": "memory optimized kernel",
"ndrange": "NDRange optimized kernel",
"final": "memory and NDRAnge optimized kernel"
}
cmap = plt.get_cmap('viridis')
colors = [cmap(i) for i in np.linspace(0, 1, 6)]
def main(corpora, sizes, lengths, dir, count, save, kernel):
plt.style.use('seaborn')
results = parse_results(corpora, sizes, lengths, dir)
plot_throughput(results, corpora, sizes, lengths, count, save, kernel)
plot_energy(results, corpora, sizes, lengths, save, kernel)
print_memory_transfers(results, corpora, sizes, lengths)
def parse_results(corpora, sizes, lengths, dir):
results = dict()
for corpus in corpora:
results[corpus] = dict()
for size in sizes:
results[corpus][size] = dict()
for length in lengths:
results[corpus][size][length] = parse_result(corpus, size, length, dir)
return results
def parse_result(corpus, size, length, dir):
resultdir = f"{dir}/{corpus}.{size}MB.len{length}"
result = []
for filepath in glob.iglob(f"{resultdir}/*"):
with open(filepath, "r") as f:
data = json.load(f)
time = data['Kernel Execution'][0]['time']
transfer_time = sum(map(lambda x: x['time'], data['Data Transfer: Host to Global Memory']))
energy = get_energy_usage(data)
result.append((time/1000, energy, transfer_time))
return result
def get_energy_usage(data):
xs = list(map(lambda x: x["timestamp"], data["power"]))
ys = list(map(lambda y: y["power"], data["power"]))
# Get start and end timestamp of kernel execution.
start = float(data["timeline"]["START"])
end = float(data["timeline"]["END"])
# Find nearest power data points.
nearest_start = min(range(len(xs)), key=lambda i: abs(xs[i] - start))
nearest_end = min(range(len(xs)), key=lambda i: abs(xs[i] - end))
# Find power data points within kernel execution
kernel_xs = np.array(xs)[nearest_start:nearest_end+1] / 1000
kernel_ys = np.array(ys)[nearest_start:nearest_end+1]
# Use Simpson's Rule to integrate and find energy usage.
return simps(y=kernel_ys, x=kernel_xs)
def plot_throughput(results, corpora, sizes, lengths, count, save, kernel):
width = .1
labels = [f"{size}MB" for size in sizes]
xs = np.arange(len(labels))
gs = gridspec.GridSpec(2, 4)
gs.update(wspace=1.0, hspace=0.5)
axes = [plt.subplot(gs[0, 1:3], ), plt.subplot(gs[1, :2]), plt.subplot(gs[1, 2:])]
for axi, ax in enumerate(axes):
corpus = corpora[axi]
ax.sharey(axes[0])
means = np.array([[int(np.mean([count / result[0] for result in results[corpus][size][length]])) for size in sizes] for length in lengths])
stds = np.array([[np.std([count / result[0] for result in results[corpus][size][length]]) for size in sizes] for length in lengths])
for i in range(len(lengths)):
ax.bar(xs - (width*len(lengths))/2 + i * width + width/2, means[i], width, label=f"{lengths[i]} characters", yerr=stds[i], color=colors[i])
if axi in [0, 1]:
ax.set_ylabel("Throughput (patterns matched/s)")
ax.set_xlabel("Corpus size (MB)")
ax.set_title(f"\"{corpus}\" corpus");
ax.set_xticks(xs)
ax.set_xticklabels(labels)
if axi == 0:
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0., title="Pattern length")
plt.suptitle(f"Average throughput for the {kernel_name_map[kernel]}")
if save:
figure = plt.gcf()
figure.set_size_inches(9, 7)
plt.savefig(f"throughput_{kernel}.png", format="png", dpi=100)
else:
plt.show()
def plot_energy(results, corpora, sizes, lengths, save, kernel):
width = .1
labels = [f"{size}MB" for size in sizes]
xs = np.arange(len(labels))
gs = gridspec.GridSpec(2, 4)
gs.update(wspace=1.0, hspace=0.5)
axes = [plt.subplot(gs[0, 1:3], ), plt.subplot(gs[1, :2]), plt.subplot(gs[1, 2:])]
for axi, ax in enumerate(axes):
corpus = corpora[axi]
means = np.array([[int(np.mean([result[1] for result in results[corpus][size][length]])) for size in sizes] for length in lengths])
stds = np.array([[np.std([result[1] for result in results[corpus][size][length]]) for size in sizes] for length in lengths])
for i in range(len(lengths)):
ax.bar(xs - (width*len(lengths))/2 + i * width + width/2, means[i], width, label=f"{lengths[i]} characters", yerr=stds[i], color=colors[i+3])
if axi in [0, 1]:
ax.set_ylabel("Energy consumption (J)")
ax.set_xlabel("Corpus size (MB)")
ax.set_title(f"\"{corpus}\" corpus");
ax.set_xticks(xs)
ax.set_xticklabels(labels)
if axi == 0:
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0., title="Pattern length")
plt.suptitle(f"Average energy consumption for the {kernel_name_map[kernel]}")
if save:
figure = plt.gcf()
figure.set_size_inches(9, 7)
plt.savefig(f"energy_{kernel}.png", format="png", dpi=100)
else:
plt.show()
def print_memory_transfers(results, corpora, sizes, lengths):
for length in lengths:
print(f"{length}", end="")
for corpus in corpora:
for size in sizes:
mean = np.mean([result[2] / 1000 for result in results[corpus][size][length]])
print(" & \\SI{{{:.2f}}}{{s}}".format(mean), end="")
print(" \\\\")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--count", help="number of patterns", type=int, required=True)
parser.add_argument("-l", "--lengths", help="length of the patterns", type=int, nargs="+", default=[], required=True)
parser.add_argument("-d", "--dir", help="directory containing results", required=True)
parser.add_argument("-t", "--corpora", help="text corpora (without file size)", nargs="+", default=[], required=True)
parser.add_argument("-s", "--sizes", help="file sizes", type=int, nargs="+", default=[], required=True)
parser.add_argument("-k", "--kernel", help="kernel", required=True)
parser.add_argument("-o", "--save", help="save as SVG", action="store_true", required=False)
args = parser.parse_args()
main(args.corpora, args.sizes, args.lengths, args.dir, args.count, args.save, args.kernel)