SciPy Image Convolution Benchmark

This little benchmark performs an image convolution in parallel using SciPy’s signal.convolve2d call. It can be run with dragon or standard python3 to compare performance on your machine.

The code demonstrates the following key concepts working with Dragon:

  • How to write programs that can run with either: Dragon or standard Python3

  • How to use multiprocessing.Pool to parallelize scientific workloads

Listing 13 scipy_image_demo.py: A small parallel image convolution benchmark
 1import os
 2import time
 3import numpy as np
 4
 5import dragon
 6import multiprocessing as mp
 7from scipy import signal
 8
 9NUM_WORKERS = 4
10IMAGE_SIZE = 1024
11MEMSIZE = 32 * 1024**2  # 32 MB
12NUM_ITER = 10
13NUM_BURN = 2
14
15
16def convolve_image_filter(args: tuple) -> np.ndarray:
17    """Use scipy to convolve an image with a filter
18
19    :param args: tuple containing image and filter
20    :type args: tuple
21    :return: convolved image
22    :rtype: np.array
23    """
24    image, random_filter = args
25    return signal.convolve2d(image, random_filter)[::5, ::5]
26
27
28if __name__ == "__main__":
29
30    # check if we should use the Dragon
31    if "DRAGON_PATCH_MP" in os.environ:
32        mp.set_start_method("dragon")
33    else:
34        mp.set_start_method("spawn")
35
36    # create the worker pool
37    pool = mp.Pool(NUM_WORKERS)
38
39    # create images, stay within MEMSIZE
40    image = np.zeros((IMAGE_SIZE, IMAGE_SIZE))
41    num_images = int(float(MEMSIZE) / float(image.size))
42
43    rng = np.random.default_rng(42)
44    filters = [rng.standard_normal(size=(4, 4)) for _ in range(num_images)]
45    images = [np.zeros((IMAGE_SIZE, IMAGE_SIZE)) for _ in range(num_images)]
46
47    print(f"# of workers = {NUM_WORKERS}", flush=True)
48    print(f"memory footprint = {MEMSIZE}", flush=True)
49    print(f"# of images = {num_images}", flush=True)
50
51    # run a small benchmark
52
53    results = []
54    for _ in range(NUM_BURN + NUM_ITER):
55        beg = time.perf_counter()
56        pool.map(convolve_image_filter, zip(images, filters))
57        end = time.perf_counter()
58        results.append(end - beg)
59
60    results = results[NUM_BURN:]  # throw away burn in results
61
62    pool.close()
63    pool.join()
64    print(f"Average execution time {round(np.mean(results), 2)} second(s)", flush=True)
65    print(f"Standard deviation: {round(np.std(results), 2)} second(s)")

This code can be run either with standard Python3 with python3 scipy_image_demo.py:

1>$python3 scipy_image_demo.py
2# of workers = 4
3memory footprint = 33554432
4# of images = 32
5Average execution time 0.73 second(s)
6Standard deviation: 0.03 second(s)

or using the Dragon runtime, potentially also on multiple nodes with dragon scipy_image_demo.py:

1>$dragon scipy_image_demo.py
2# of workers = 4
3memory footprint = 33554432
4# of images = 32
5Average execution time 1.02 second(s)
6Standard deviation: 0.2 second(s)
7+++ head proc exited, code 0