from concurrent.futures import ProcessPoolExecutor
import dascore as dc
def my_patch_processing_function(patch):
"""A custom function for processing patches."""
...
spool = dc.get_example_spool("random_das")
executor = ProcessPoolExecutor()
spool.map(my_patch_processing_function, client=executor)Parallel Processing
This recipe shows a few strategies to parallelize “embarrassingly parallel” spool processing workflows.
Processes and Threads
Spool.map is the easiest way to process patches in a spool in parallel. Here is an example using the Python standard library module concurrent.futures:
The ThreadPoolExecutor from the same module also works. On a free-threaded (no-GIL) build of CPython threads run patch processing in parallel; on a standard build the GIL limits the speed-up to whatever the work releases it for, which for DASCore is mostly file reading and NumPy/SciPy computation.
There are two downsides to this approach. First, if the patches aren’t chunked adequately it may exhaust the available memory. Second, it will only work on a single machine. The next section presents a more scalable option.
Thread Safety
DASCore’s core test suite — no optional dependencies, no network tests — runs on a free-threaded CPython build with the GIL disabled. The guarantees below are what that supports; they apply equally to threads on a standard build.
Configuration in threads
Configuration has two tiers, described in runtime configuration. set_config changes the process-wide base and is visible from every thread. config_context overrides the config for the current context only, and a newly started thread begins with a fresh context, so a scoped override does not automatically reach threads started inside it.
Spool.map handles this for you: it binds the configuration active when map is called and re-applies it inside each worker, for thread pools and process pools alike. When starting threads yourself, either set the configuration permanently before starting them or re-apply the scoped override inside each thread.
MPI4Py
This section shows how to use the “mpi4py” library to parallelize dascore code.
Installation
First, make sure you have installed DASCore on your machine. See DASCore Installation. Secondly, you need to properly install the mpi4py library. After installing and loading the Open MPI module on your machine (e.g., on Linux: load module to/mpi/openmpi/gcc/compiler/path), install mpi4py. It might be easier to install using conda-forge as below:
conda install -c conda-forge mpi4py openmpiPlease note that this procedure was last tested with Python 3.11 and Open MPI GCC 3.1.3.
Parallel script
Here is an example for parallelization of Patches over processors:
mpi_spool.py
#| execute: false
import sys
import dascore as dc
from mpi4py import MPI
# Load the spool
spool = dc.get_example_spool("random_das")
# Initiate MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
# Check the spool on the first processor
if len(spool)<1:
if rank==0:
raise ValueError('No Patch of data found within the spool.')
else:
sys.exit(1)
for i in range(rank, len(spool), size):
patch = spool[i]
print(f"rank: {rank}, patch number: {i}, patch: {patch}")
...
comm.barrier()
sys.exit(0)Run the script
If you like to run the mpi_spool.py script using n = 4 processors (which means each processor will run the script separately), you can use:
mpiexec -n 4 python mpi_spool.pyor
mpirun -n 4 python mpi_spool.py