Skip to content
Snippets Groups Projects
Select Git revision
  • 422be9150f464924581a82f93b83834c0083875f
  • master default protected
  • for_isabel
  • fix_max_toplevel_cell_rounding
  • zoom_space_split
  • zoom_regrid
  • zoom_cellid_test
  • zoom_test_init_fix
  • GEARRT_Stan_project_cosmology
  • GEARRT_cosmo_redshifting
  • karapiperis/PolarisedAlfvenWave
  • MHD_canvas
  • gear_sink_imf_sampling_merged
  • zoom_cellid_funcs
  • zoom_maketasks
  • zoom_task_types
  • GEARRT_cosmo_IonMassFraction_example
  • zoom_merge
  • GEARRT_cosmo_subcycling_Stan
  • GEARRT_cosmo_thermochem
  • fstasys/VP_CosmoRun.GalileanInvariance
  • v1.0.0
  • v0.9.0
  • v0.8.5
  • v0.8.4
  • v0.8.3
  • v0.8.2
  • v0.8.1
  • v0.8.0
  • v0.7.0
  • v0.6.0
  • v0.5.0
  • v0.4.0
  • v0.3.0
  • v0.2.0
  • v0.1.0-pre
  • v0.1
  • v0.0
38 results

plot_scaling_results.py

Blame
  • Forked from SWIFT / SWIFTsim
    7100 commits behind the upstream repository.
    plot_scaling_results.py 11.30 KiB
    #!/usr/bin/env python
    #
    # Usage:
    #  python 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:
    # python 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,
        "text.latex.unicode": True,
    }
    plt.rcParams.update(params)
    plt.rc("font", **{"family": "sans-serif", "sans-serif": ["Times"]})
    
    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()