diff --git a/exercises/exerciseC/plot.py b/exercises/exerciseC/plot.py index bd4872a..ec73761 100644 --- a/exercises/exerciseC/plot.py +++ b/exercises/exerciseC/plot.py @@ -1,67 +1,53 @@ -import sys -import collections +import os import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patheffects as PathEffects -dts = collections.defaultdict(dict) +N_processes = 5 +N_threads = 5 -for fname in sys.argv[1:]: - values = open(fname).read().split() - n1 = int(values[0]) - n2 = int(values[1]) +# Load measured timings +times = np.empty((N_processes, N_threads)) +for fname in os.listdir('timings'): + values = open(f'timings/{fname}').read().split() + n_processes = int(values[0]) + n_threads = int(values[1]) dt = float(values[2]) + times[n_processes-1][n_threads-1] = dt +print(times) - dts[n1][n2] = dt +""" Plot measured time""" +fig_time, axs_time = plt.subplots() +im = axs_time.imshow(times.T, origin='lower') +axs_time.set_title('Computation time') +fig_time.colorbar(im, ax=axs_time, label='Measured computation time (s)') -print(dts) -N1 = max(dts) -N2 = max(max(v) for v in dts.values()) +""" Plot speedup """ +workers = np.arange(N_processes + 1)[:, None] * np.arange(N_threads + 1) +speedup = times[0, 0] / times -print(N1, N2) +fig_speedup, axs_speedup = plt.subplots() +im = axs_speedup.imshow(speedup.T, origin='lower') +axs_speedup.set_title('Computation speed-up') +fig_speedup.colorbar(im, ax=axs_speedup, label='Speed-up') -x = np.empty((N1 + 1, N2 + 1)) -for n1, values in dts.items(): - for n2, v in values.items(): - x[n1, n2] = v +# Set same style for both plots +for axs, data in zip([axs_time, axs_speedup], [times, speedup]): + axs.set_xlabel('# processes') + axs.set_ylabel('# threads') + axs.set_xticks(np.arange(N_processes)) + axs.set_xticklabels(np.arange(N_processes)+1) + axs.set_yticks(np.arange(N_threads)) + axs.set_yticklabels(np.arange(N_threads)+1) -x[:, 0] = np.nan -x[0, :] = np.nan + for i in range(N_processes): + for j in range(N_threads): + txt = axs.text(i, j, f'{data[i, j]:.2f}', fontsize=10, color='w', + ha='center', va='center', fontweight='bold') + txt.set_path_effects([PathEffects.withStroke(linewidth=0.5, foreground='k')]) + axs.spines[['right', 'top']].set_visible(False) -print(x) +# Save plots +fig_time.savefig('time.png', dpi=300) +fig_speedup.savefig('speedup.png', dpi=300) -from matplotlib import pyplot - -fig, axes = pyplot.subplots() -im = axes.imshow(x, origin='lower') -axes.set_ylabel('# processes') -axes.set_xlabel('# threads') -axes.spines[['right', 'top']].set_visible(False) -axes.set_title('time') -fig.colorbar(im, ax=axes, label='s') - -fig_small, axes = pyplot.subplots() -im = axes.imshow(x[:5, :5], origin='lower') -axes.set_ylabel('# processes') -axes.set_xlabel('# threads') -axes.spines[['right', 'top']].set_visible(False) -axes.set_title('time') -fig_small.colorbar(im, ax=axes, label='s') - -workers = np.arange(N1 + 1)[:, None] * np.arange(N2 + 1) -speedup = x[1,1] / x - -speedup[:, 0] = np.nan -speedup[0, :] = np.nan - -figs, axes = pyplot.subplots() -im = axes.imshow(speedup, origin='lower') -axes.set_ylabel('# processes') -axes.set_xlabel('# threads') -axes.spines[['right', 'top']].set_visible(False) -axes.set_title('speedup') -figs.colorbar(im, ax=axes, label='s') - -fig.savefig('time.svg') -fig_small.savefig('time_inset.svg') -figs.savefig('speedup.svg') - -pyplot.show() diff --git a/exercises/exerciseC/process_images.py b/exercises/exerciseC/process_images.py index db0774a..99743e7 100644 --- a/exercises/exerciseC/process_images.py +++ b/exercises/exerciseC/process_images.py @@ -3,17 +3,11 @@ import sys from multiprocessing import Pool as ProcessPool import time -def process_image(fname): - n_threads = os.getenv('OMP_NUM_THREADS', '(unset)') - print(f"Worker {fname=} OMP_NUM_THREADS={n_threads}") +def process_image(input_tuple): - # An image is an array with width, height and three (RGB) color channels - # (Sometimes there is a transparency channel too: RGBA) - im = Image.open(fname) - try: - A = np.median(im, axis=2)[::4, ::4] - except: - A = np.array(im)[::4, ::4] + fname, A = input_tuple + n_threads = os.getenv('OMP_NUM_THREADS', '(unset)') + print(f"Worker {fname=} OMP_NUM_THREADS={n_threads}", flush=True) # Decompose image U, S, Vh = np.linalg.svd(A) @@ -36,10 +30,10 @@ if __name__ == '__main__': fnames = sys.argv[3:] # Check that the output folders exist, or create them if needed - if not os.path.isdir('processed_images'): os.mkdir('processed_images') if not os.path.isdir('timings'): os.mkdir('timings') + if not os.path.isdir('processed_images'): os.mkdir('processed_images') - print(f"Controller with {n_processes} processes and {n_threads} threads / worker") + print(f"Controller with {n_processes} processes and {n_threads} threads / worker", flush=True) # The environment that is set in the parent is inherited by child workers, # we need to set the variable before numpy is imported! @@ -50,19 +44,28 @@ if __name__ == '__main__': import numpy as np from PIL import Image - # Time the execution + # I/O Load the images + image_arrays = [] + for fname in fnames: + im = Image.open(fname) + A = np.array(im) + image_arrays.append((fname, A)) + + # Time the execution of the pool map start_time = time.time() with ProcessPool(n_processes) as p: - new_images = p.map(process_image, fnames) + new_images = p.map(process_image, image_arrays) elapsed_time = time.time() - start_time + # I/O save the processed images for im, fname in zip(new_images, fnames): im = Image.fromarray(im) im.save(fname.replace('images', 'processed_images')) - print(f'{n_processes} processes and {n_threads} threads and {len(fnames)} jobs: {elapsed_time}') + print(f'{n_processes} processes and {n_threads} threads and {len(fnames)} jobs: {elapsed_time}\n', + flush=True) - # IO: Save the timing to a unique txt file + # I/O: Save the timing to a unique txt file filename = f'timings/{n_processes:02}_processes_{n_threads:02}_threads.txt' with open(filename, 'w') as file: file.write(f'{n_processes} {n_threads} {elapsed_time:.6f}') diff --git a/exercises/exerciseC/run_with_all_configurations.sh b/exercises/exerciseC/run_with_all_configurations.sh index 60ea678..30a1414 100644 --- a/exercises/exerciseC/run_with_all_configurations.sh +++ b/exercises/exerciseC/run_with_all_configurations.sh @@ -1,7 +1,8 @@ -# This is bash, it executes the python script multiple times -for i in {1..10} +# This is bash +# It runs the python script multiple times with different arguments +for i in {1..5} # Number of processes do - for j in {1..10} + for j in {1..5} # Number of threads do python process_images.py $i $j images/* done