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

232 lines
7.9 KiB
Python

import argparse
import subprocess
import os
import shutil
import json
from pathlib import Path
# Thanks Tristan Laan for data extraction functions.
def _calculate_wattage(data: list) -> float:
return (int(data[1]) / 1000) * (int(data[2]) / 1000) \
+ (int(data[3]) / 1000) * (int(data[4]) / 1000)
def _get_power_profile_data(directory: Path) -> list:
file = next(directory.glob('power_profile_*.csv'))
data = []
with file.open() as f:
for i, line in enumerate(f):
if i < 2:
continue
csv_data = line.split(',')
data.append({'timestamp': float(csv_data[0]),
'power': _calculate_wattage(csv_data)})
return data
def _get_timeline_data(directory: Path) -> dict:
file = next(directory.glob('timeline_trace.csv'))
data = {}
with file.open() as f:
for i, line in enumerate(f):
cells = line.split(',')
if i < 13:
continue
if cells[0] == 'Footer':
break
if cells[1].startswith("KERNEL") and cells[1].endswith("all") and cells[2] in ["START", "END"]:
data[cells[2]] = cells[0]
return data
def _is_mode(line: str):
if line == 'OpenCL API Calls':
return True
if line == 'Kernel Execution':
return True
if line == 'Compute Unit Utilization':
return True
if line == 'Data Transfer: Host to Global Memory':
return True
if line == 'Data Transfer: Kernels to Global Memory':
return True
def _get_profile_summary_data(file: Path) -> dict:
data = dict()
mode = None
skip_line = False
with file.open() as f:
for line in f:
line = line.strip()
if skip_line:
skip_line = False
continue
if _is_mode(line):
mode = line
data[mode] = []
skip_line = True
continue
if line == '':
mode = None
if not mode:
continue
csv_data = line.split(',')
if mode == 'OpenCL API Calls':
data[mode].append({
'name': csv_data[0],
'calls': int(csv_data[1]),
'time': float(csv_data[2])
})
if mode == 'Kernel Execution':
data[mode].append({
'kernel': csv_data[0],
'enqueues': int(csv_data[1]),
'time': float(csv_data[2])
})
if mode == 'Compute Unit Utilization':
data[mode].append({
'cu': csv_data[1],
'kernel': csv_data[2],
'time': float(csv_data[9])
})
if mode == 'Data Transfer: Host to Global Memory':
data[mode].append({
'type': csv_data[1],
'transfers': int(csv_data[2]),
'speed': float(csv_data[3]),
'utilization': float(csv_data[4]),
'size': float(csv_data[5]),
'time': float(csv_data[6])
})
if mode == 'Data Transfer: Kernels to Global Memory':
data[mode].append({
'interface': csv_data[3],
'type': csv_data[4],
'transfers': int(csv_data[5]),
'speed': float(csv_data[6]),
'utilization': float(csv_data[7]),
'size': float(csv_data[8])
})
return data
def read_data(directory: Path) -> dict:
data = dict()
profile = directory / 'profile_summary.csv'
try:
data['power'] = _get_power_profile_data(directory)
data['timeline'] = _get_timeline_data(directory)
except StopIteration:
pass
if profile.exists():
data.update(_get_profile_summary_data(profile))
return data
def write_data(data: dict, file: Path):
with file.open('w') as f:
json.dump(data, f)
class cd:
"""Context manager for changing the current working directory"""
def __init__(self, newPath):
self.newPath = os.path.expanduser(newPath)
def __enter__(self):
self.savedPath = os.getcwd()
os.chdir(self.newPath)
def __exit__(self, etype, value, traceback):
os.chdir(self.savedPath)
def main(repeats, count, maxmatches, lengths, dir, filenames, kernel, target, localsize, ndrange):
for filename in filenames:
for length in lengths:
benchmark(repeats, count, maxmatches, length, dir, filename, kernel, target, localsize, ndrange)
def benchmark(repeats, count, maxmatches, length, dir, filename, kernel, target, localsize, ndrange):
textfilename = f"../{dir}/{filename}"
fmfilename = f"{textfilename}.fm"
resultdir = f"../{kernel}_results/{filename}.len{length}"
testfilename = f"{resultdir}/test.txt"
if target != "hw":
env = dict(os.environ, XCL_EMULATION_MODE=target)
else:
env = os.environ
gentestargs = ["../../generate_test_data", textfilename, fmfilename, testfilename, str(count), str(length), str(maxmatches)]
benchmarkargs = ["../benchmark", fmfilename, f"{kernel}.xclbin", testfilename, str(ndrange), str(localsize)]
print(" ".join(gentestargs))
print(" ".join(benchmarkargs))
with cd(target):
shutil.rmtree(resultdir, ignore_errors=True)
Path(resultdir).mkdir(parents=True, exist_ok=True)
for n in range(repeats):
print(f"{n+1}/{repeats}")
# Create test file.
gentestproc = subprocess.Popen(gentestargs, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = gentestproc.communicate()
if stderr:
print(f">{stderr.strip()}")
ret = gentestproc.poll()
if ret != 0:
print(f"Error creating test data: {stdout.strip()}")
exit(1)
# Execute kernel
benchmarkproc = subprocess.Popen(benchmarkargs, stdout=subprocess.PIPE, universal_newlines=True, stderr=subprocess.PIPE, env=env)
_, stderr = benchmarkproc.communicate()
if stderr:
print(f">{stderr.strip()}")
ret = benchmarkproc.poll()
if ret != 0:
print("Error benchmarking test data")
exit(1)
# Move data to result directory.
data = read_data(Path("."))
write_data(data, Path(resultdir) / f"run{n}.json")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--repeats", help="number of times to repeat each experiment", type=int, required=True)
parser.add_argument("-c", "--count", help="number of patterns", type=int, required=True)
parser.add_argument("-m", "--maxmatches", help="maximum number of matches per pattern", 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 FM-indices and original texts (with the same name)", required=True)
parser.add_argument("-f", "--files", help="FM-index files to benchmark", nargs="+", default=[], required=True)
parser.add_argument("-k", "--kernel", help="FPGA kernel", required=True)
parser.add_argument("-t", "--target", help="compilation target", default="sw_emu", required=False)
parser.add_argument("-s", "--localsize", help="NDRange local size", type=int, required=True)
parser.add_argument("-r", "--ndrange", help="use NDRange", type=int, required=True)
args = parser.parse_args()
main(args.repeats, args.count, args.maxmatches, args.lengths, args.dir, args.files, args.kernel, args.target, args.localsize, args.ndrange)