232 lines
7.9 KiB
Python
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)
|