Last Updated on September 29, 2023
You can share numpy arrays with child processes by inheriting global variables.
In this tutorial, you will discover how to share a numpy array with child processes by variable inheritance.
Let’s get started.
Need to Share Numpy Array Between Processes
Python offers process-based concurrency via the multiprocessing module.
Process-based concurrency is appropriate for those tasks that are CPU-bound, as opposed to thread-based concurrency in Python which is generally suited to IO-bound tasks given the presence of the Global Interpreter Lock (GIL).
You can learn more about process-based concurrency and the multiprocessing module in the tutorial:
Consider the situation where we need to share numpy arrays between processes.
This may be for many reasons, such as:
- Data is loaded as an array in one process and analyzed differently in different subprocesses.
- Many child processes load small data as arrays that are sent to a parent process for handling.
- Data arrays are loaded in the parent process and processed in a suite of child processes.
Sharing Python objects and data between processes is slow.
This is because any data, like numpy arrays, shared between processes must be transmitted using inter-process communication (ICP) requiring the data first be pickled by the sender and then unpickled by the receiver.
You can learn more about this in the tutorial:
This means that if we share numpy arrays between processes, it assumes that we receive some benefit, such as a speedup, that overcomes the slow speed of data transmission.
For example, it may be the case that the arrays are relatively small and fast to transmit, whereas the computation performed on each array is slow and can benefit from being performed in separate processes.
Alternatively, preparing the array may be computationally expensive and benefit from being performed in a separate process, and once prepared, the arrays are small and fast to transmit to another process that requires them.
Given these situations, how can we share data between Processes in Python?
Run loops using all CPUs, download your FREE book to learn how.
How to Share a Numpy Array Between Processes via a Global Variable
One way to share a numpy array with other processes is via a global variable.
That is, a process can load or create a numpy array, store it in a global variable, and other processes can access it directly.
The benefit of this approach is that it is really fast.
It can be up to 34x faster than sending the array between processes using other methods, such as a function argument.
You can learn more about this approach to sharing data between processes in the tutorial:
There are some limitations to this approach though, they are:
- The array must be stored in a global variable by a parent process.
- The array can only be accessed by child processes.
- Child processes must be created using the ‘fork’ start method.
- Changes to the array made by child processes will not be reflected in each other or in the parent process.
Let’s consider these concerns in detail.
A python program can declare an explicit global variable using the ‘global‘ keyword.
For example:
1 2 3 |
... # declare the global variable global data |
This is not needed in the parent process when preparing the numpy array, but may be needed in the function executed in the child process to ensure that the program is referred to the inherited global variable and not a newly created local variable.
This is called inheriting a global variable.
You can learn more about inheriting global variables in child processes in the tutorial:
Child processes can be created either by spawning a new instance of the Python interpreter or by forking an existing instance of the Python interpreter. Spawning is the current default on Windows and macOS, whereas forking is the default on Linux.
We can explicitly set the method used to start child processes via the multiprocessing.set_start_method() function and specify the method as a string, such as ‘fork’.
For example:
1 2 3 |
... # ensure we are using fork start method set_start_method('fork') |
The fork start method is not supported on Windows at the time of writing.
You can learn more about setting the start method used for creating child processes in the tutorial:
The child process will inherit a copy of all global variables from the parent process.
Because they are a copy, any changes made to the global variable in the child process will only be available to the child process. Similarly, any changes to the global variable made in the parent process after the child processes are created will not be reflected in the child processes.
As such, this approach to sharing an array between processes is appropriate in certain situations, such as:
- The array is large, but the machine can afford to store multiple copies in memory at the same time.
- Child processes compute something using the array, but changes to the array itself are not required in the parent process or other processes.
A common example that may be appropriate for this approach would be where one or more arrays are prepared or loaded and a suite of computationally expensive statistics needs to be calculated on each. Each statistic or set of statistics can be calculated using a copy of the array in a separate process.
Now that we know how to share a numpy array between processes by inheriting it, let’s look at a worked example.
Example of Sharing a Numpy Array as a Global Variable
We can explore how to share a numpy array as an inherited global variable with child processes.
In this example, we will create a modestly sized numpy array in a parent process and store it as a global variable. We will then start a child process using the fork start method and have it access the inherited numpy array.
Firstly, we can define a task to be executed in a child process.
In this case, the task does not take any arguments. Instead, it declares an inherited global variable, then accesses a portion of it directly.
It then changes the contents of the array, by assigning all values to zero and confirming that the change took effect.
The task() function below implements this.
1 2 3 4 5 6 7 8 9 10 |
# task executed in a child process def task(): # declare the global variable global data # check some data in the array print(data[:5,:5]) # change data in the array data.fill(0.0) # confirm the data was changed print(data[:5,:5]) |
Next, we can set the start method to fork.
1 2 3 |
... # ensure we are using fork start method set_start_method('fork') |
We can then create a modestly sized numpy array, stored in a variable.
1 2 3 4 5 |
... # define the size of the numpy array n = 10000 # create the numpy array data = ones((n,n)) |
We can then create a child process, configured to execute our task() function, and then start the process.
This will make a copy of the parent process, including the numpy array stored in the ‘data‘ variable.
1 2 3 4 5 |
... # create a child process child = Process(target=task) # start the child process child.start() |
You can learn more about running a function in a child process in the tutorial:
Finally, we can wait for the child process to terminate and then report the contents of the array.
1 2 3 4 5 |
... # wait for the child process to complete child.join() # check some data in the array print(data[:5,:5]) |
Tying this together, the complete example is listed below.
Note, this example may not run on Windows as it requires support for the ‘fork’ start method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# share a numpy array as a global variable from multiprocessing import set_start_method from multiprocessing import Process from numpy import ones from numpy import zeros # task executed in a child process def task(): # declare the global variable global data # check some data in the array print(data[:5,:5]) # change data in the array data.fill(0.0) # confirm the data was changed print(data[:5,:5]) # protect the entry point if __name__ == '__main__': # ensure we are using fork start method set_start_method('fork') # define the size of the numpy array n = 10000 # create the numpy array data = ones((n,n)) # create a child process child = Process(target=task) # start the child process child.start() # wait for the child process to complete child.join() # check some data in the array print(data[:5,:5]) |
Running the example first sets the ‘fork’ start method for creating child processes.
Next, the numpy array is created and initialized to the value.
The child process is then created and started, and the main process blocks until it is terminated.
A forked copy of the parent process is created and runs the task() function. The inherited global variable is declared and then accessed, reporting a small subset of the data.
This confirms that the array was inherited correctly and contains the data initialized in the parent process.
The child process then fills the array with zero values and accesses them to confirm the data was changed.
The child process terminates and the main process then resumes. It reports the contents of a small portion of the array, confirming that the data change made in the child process was not propagated to the parent process.
This highlights how to share a numpy array with child processes by inheritance and that changes made in the child process to the inherited array do not propagate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[[1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.]] [[0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.]] [[1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.]] |
Free Concurrent NumPy Course
Get FREE access to my 7-day email course on concurrent NumPy.
Discover how to configure the number of BLAS threads, how to execute NumPy tasks faster with thread pools, and how to share arrays super fast.
Further Reading
This section provides additional resources that you may find helpful.
Books
- Concurrent NumPy in Python, Jason Brownlee (my book!)
Guides
- Concurrent NumPy 7-Day Course
- Which NumPy Functions Are Multithreaded
- Numpy Multithreaded Matrix Multiplication (up to 5x faster)
- NumPy vs the Global Interpreter Lock (GIL)
- ThreadPoolExecutor Fill NumPy Array (3x faster)
- Fastest Way To Share NumPy Array Between Processes
Documentation
- Parallel Programming with numpy and scipy, SciPi Cookbook, 2015
- Parallel Programming with numpy and scipy (older archived version)
- Parallel Random Number Generation, NumPy API
NumPy APIs
Concurrency APIs
- threading — Thread-based parallelism
- multiprocessing — Process-based parallelism
- concurrent.futures — Launching parallel tasks
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Takeaways
You now know how to share a numpy array with child processes by variable inheritance.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by geng zhang on Unsplash
Do you have any questions?