-
Matthieu Schaller authoredMatthieu Schaller authored
plot_scaling_results.py 11.21 KiB
#!/usr/bin/env python
#
# Usage:
# python3 plot_scaling_results.py input-file1-ext input-file2-ext ...
#
# Description:
# Plots speed up, parallel efficiency and time to solution given a "timesteps" output file generated by SWIFT.
#
# Example:
# python3 plot_scaling_results.py _hreads_cosma_stdout.txt _threads_knl_stdout.txt
#
# The working directory should contain files 1_threads_cosma_stdout.txt - 64_threads_cosma_stdout.txt and 1_threads_knl_stdout.txt - 64_threads_knl_stdout.txt, i.e wall clock time for each run using a given number of threads
import sys
import glob
import re
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats
import ntpath
params = {
"axes.labelsize": 14,
"axes.titlesize": 18,
"font.size": 12,
"legend.fontsize": 12,
"xtick.labelsize": 14,
"ytick.labelsize": 14,
"text.usetex": True,
"figure.subplot.left": 0.055,
"figure.subplot.right": 0.98,
"figure.subplot.bottom": 0.05,
"figure.subplot.top": 0.95,
"figure.subplot.wspace": 0.14,
"figure.subplot.hspace": 0.12,
"lines.markersize": 6,
"lines.linewidth": 3.0,
}
plt.rcParams.update(params)
version = []
branch = []
revision = []
hydro_scheme = []
hydro_kernel = []
hydro_neighbours = []
hydro_eta = []
threadList = []
hexcols = [
"#332288",
"#88CCEE",
"#44AA99",
"#117733",
"#999933",
"#DDCC77",
"#CC6677",
"#882255",
"#AA4499",
"#661100",
"#6699CC",
"#AA4466",
"#4477AA",
]
linestyle = (
hexcols[0],
hexcols[1],
hexcols[3],
hexcols[5],
hexcols[6],
hexcols[8],
hexcols[2],
hexcols[4],
hexcols[7],
hexcols[9],
)
numTimesteps = 0
legendTitle = " "
inputFileNames = []
# Work out how many data series there are
if len(sys.argv) == 1:
print("Please specify an input file in the arguments.")
sys.exit()
else:
for fileName in sys.argv[1:]:
inputFileNames.append(fileName)
numOfSeries = int(len(sys.argv) - 1)
# Get the names of the branch, Git revision, hydro scheme and hydro kernel
def parse_header(inputFile):
with open(inputFile, "r") as f:
found_end = False
for line in f:
if "Branch:" in line:
s = line.split()
line = s[2:]
branch.append(" ".join(line))
elif "Revision:" in line:
s = line.split()
revision.append(s[2])
elif "Hydrodynamic scheme:" in line:
line = line[2:-1]
s = line.split()
line = s[2:]
hydro_scheme.append(" ".join(line))
elif "Hydrodynamic kernel:" in line:
line = line[2:-1]
s = line.split()
line = s[2:5]
hydro_kernel.append(" ".join(line))
elif "neighbours:" in line:
s = line.split()
hydro_neighbours.append(s[4])
elif "Eta:" in line:
s = line.split()
hydro_eta.append(s[2])
return
# Parse file and return total time taken, speed up and parallel efficiency
def parse_files():
totalTime = []
sumTotal = []
speedUp = []
parallelEff = []
for i in range(0, numOfSeries): # Loop over each data series
# Get path to set of files
path, name = ntpath.split(inputFileNames[i])
# Get each file that starts with the cmd line arg
file_list = glob.glob(inputFileNames[i] + "*")
threadList.append([])
# Remove path from file names
for j in range(0, len(file_list)):
p, filename = ntpath.split(file_list[j])
file_list[j] = filename
# Create a list of threads using the list of files
for fileName in file_list:
s = re.split(r"[_.]+", fileName)
threadList[i].append(int(s[1]))
# Re-add path once each file has been found
if len(path) != 0:
for j in range(0, len(file_list)):
file_list[j] = path + "/" + file_list[j]
# Sort the thread list in ascending order and save the indices
sorted_indices = np.argsort(threadList[i])
threadList[i].sort()
# Sort the file list in ascending order acording to the thread number
file_list = [file_list[j] for j in sorted_indices]
parse_header(file_list[0])
branch[i] = branch[i].replace("_", "\\_")
# version.append("$\\textrm{%s}$"%str(branch[i]))# + " " + revision[i])# + "\n" + hydro_scheme[i] +
# "\n" + hydro_kernel[i] + r", $N_{ngb}=%d$"%float(hydro_neighbours[i]) +
# r", $\eta=%.3f$"%float(hydro_eta[i]))
totalTime.append([])
speedUp.append([])
parallelEff.append([])
# Loop over all files for a given series and load the times
for j in range(0, len(file_list)):
times = np.loadtxt(file_list[j], usecols=(9,))
updates = np.loadtxt(file_list[j], usecols=(6,))
totalTime[i].append(np.sum(times))
sumTotal.append(np.sum(totalTime[i]))
# Sort the total times in descending order
sorted_indices = np.argsort(sumTotal)[::-1]
totalTime = [totalTime[j] for j in sorted_indices]
branchNew = [branch[j] for j in sorted_indices]
for i in range(0, numOfSeries):
version.append("$\\textrm{%s}$" % str(branchNew[i]))
global numTimesteps
numTimesteps = len(times)
# Find speed-up and parallel efficiency
for i in range(0, numOfSeries):
for j in range(0, len(file_list)):
speedUp[i].append(totalTime[i][0] / totalTime[i][j])
parallelEff[i].append(speedUp[i][j] / threadList[i][j])
return (totalTime, speedUp, parallelEff)
def print_results(totalTime, parallelEff, version):
for i in range(0, numOfSeries):
print(" ")
print("------------------------------------")
print(version[i])
print("------------------------------------")
print("Wall clock time for: {} time steps".format(numTimesteps))
print("------------------------------------")
for j in range(0, len(threadList[i])):
print(str(threadList[i][j]) + " threads: {}".format(totalTime[i][j]))
print(" ")
print("------------------------------------")
print("Parallel Efficiency for: {} time steps".format(numTimesteps))
print("------------------------------------")
for j in range(0, len(threadList[i])):
print(str(threadList[i][j]) + " threads: {}".format(parallelEff[i][j]))
return
# Returns a lighter/darker version of the colour
def color_variant(hex_color, brightness_offset=1):
rgb_hex = [hex_color[x : x + 2] for x in [1, 3, 5]]
new_rgb_int = [int(hex_value, 16) + brightness_offset for hex_value in rgb_hex]
new_rgb_int = [
min([255, max([0, i])]) for i in new_rgb_int
] # make sure new values are between 0 and 255
# hex() produces "0x88", we want just "88"
return "#" + "".join([hex(i)[2:] for i in new_rgb_int])
def plot_results(totalTime, speedUp, parallelEff, numSeries):
fig, axarr = plt.subplots(2, 2, figsize=(10, 10), frameon=True)
speedUpPlot = axarr[0, 0]
parallelEffPlot = axarr[0, 1]
totalTimePlot = axarr[1, 0]
emptyPlot = axarr[1, 1]
# Plot speed up
speedUpPlot.plot(threadList[0], threadList[0], linestyle="--", lw=1.5, color="0.2")
for i in range(0, numSeries):
speedUpPlot.plot(threadList[0], speedUp[i], linestyle[i], label=version[i])
speedUpPlot.set_ylabel("${\\rm Speed\\textendash up}$", labelpad=0.0)
speedUpPlot.set_xlabel("${\\rm Threads}$", labelpad=0.0)
speedUpPlot.set_xlim([0.7, threadList[0][-1] + 1])
speedUpPlot.set_ylim([0.7, threadList[0][-1] + 1])
# Plot parallel efficiency
parallelEffPlot.plot(
[threadList[0][0], 10 ** np.floor(np.log10(threadList[0][-1]) + 1)],
[1, 1],
"k--",
lw=1.5,
color="0.2",
)
parallelEffPlot.plot(
[threadList[0][0], 10 ** np.floor(np.log10(threadList[0][-1]) + 1)],
[0.9, 0.9],
"k--",
lw=1.5,
color="0.2",
)
parallelEffPlot.plot(
[threadList[0][0], 10 ** np.floor(np.log10(threadList[0][-1]) + 1)],
[0.75, 0.75],
"k--",
lw=1.5,
color="0.2",
)
parallelEffPlot.plot(
[threadList[0][0], 10 ** np.floor(np.log10(threadList[0][-1]) + 1)],
[0.5, 0.5],
"k--",
lw=1.5,
color="0.2",
)
for i in range(0, numSeries):
parallelEffPlot.plot(threadList[0], parallelEff[i], linestyle[i])
parallelEffPlot.set_xscale("log")
parallelEffPlot.set_ylabel("${\\rm Parallel~efficiency}$", labelpad=0.0)
parallelEffPlot.set_xlabel("${\\rm Threads}$", labelpad=0.0)
parallelEffPlot.set_ylim([0, 1.1])
parallelEffPlot.set_xlim([0.9, 10 ** (np.floor(np.log10(threadList[0][-1])) + 0.5)])
# Plot time to solution
for i in range(0, numOfSeries):
pts = [1, 10 ** np.floor(np.log10(threadList[i][-1]) + 1)]
totalTimePlot.loglog(pts, totalTime[i][0] / pts, "k--", lw=1.0, color="0.2")
totalTimePlot.loglog(
threadList[i], totalTime[i], linestyle[i], label=version[i]
)
y_min = 10 ** np.floor(np.log10(np.min(totalTime[:][0]) * 0.6))
y_max = 1.0 * 10 ** np.floor(np.log10(np.max(totalTime[:][0]) * 1.5) + 1)
totalTimePlot.set_xscale("log")
totalTimePlot.set_xlabel("${\\rm Threads}$", labelpad=0.0)
totalTimePlot.set_ylabel("${\\rm Time~to~solution}~[{\\rm ms}]$", labelpad=0.0)
totalTimePlot.set_xlim([0.9, 10 ** (np.floor(np.log10(threadList[0][-1])) + 0.5)])
totalTimePlot.set_ylim(y_min, y_max)
totalTimePlot.legend(
bbox_to_anchor=(1.21, 0.97),
loc=2,
borderaxespad=0.0,
prop={"size": 12},
frameon=False,
title=legendTitle,
)
emptyPlot.axis("off")
for i, txt in enumerate(threadList[0]):
if (
2 ** np.floor(np.log2(threadList[0][i])) == threadList[0][i]
): # only powers of 2
speedUpPlot.annotate(
"$%s$" % txt,
(threadList[0][i], speedUp[0][i]),
(threadList[0][i], speedUp[0][i] + 0.3),
color=hexcols[0],
)
parallelEffPlot.annotate(
"$%s$" % txt,
(threadList[0][i], parallelEff[0][i]),
(threadList[0][i], parallelEff[0][i] + 0.02),
color=hexcols[0],
)
totalTimePlot.annotate(
"$%s$" % txt,
(threadList[0][i], totalTime[0][i]),
(threadList[0][i], totalTime[0][i] * 1.1),
color=hexcols[0],
)
# fig.suptitle("Thread Speed Up, Parallel Efficiency and Time To Solution for {} Time Steps of Cosmo Volume\n Cmd Line: {}, Platform: {}".format(numTimesteps),cmdLine,platform))
fig.suptitle(
"${\\rm Speed\\textendash up,~parallel~efficiency~and~time~to~solution~for}~%d~{\\rm time\\textendash steps}$"
% numTimesteps,
fontsize=16,
)
return
# Calculate results
(totalTime, speedUp, parallelEff) = parse_files()
legendTitle = version[0]
plot_results(totalTime, speedUp, parallelEff, numOfSeries)
print_results(totalTime, parallelEff, version)
# And plot
plt.show()