TRIQS/nda 1.3.0
Multi-dimensional array library for C++
|
In this example, we show how to scatter, gather, broadcast and reduce nda arrays and views across MPI processes.
nda uses the mpi library to provide MPI support.
All the following code snippets are part of the same main
function:
The examples below are run on 4 processes.
Note: Only regular arrays and views are allowed in the nda MPI routines, no lazy expressions. You can use nda::make_regular to turn your lazy expressions into regular arrays.
Let us first default construct an array on all MPI ranks and then resize and initialize it on rank 0, the root rank:
Output:
As you can see, the array is only initialized on root. The reason why we use Fortran-order will be explained below.
Note: The
comm.barrier()
is not necessary. It makes sure that the
Broadcasting the array from the root to all other ranks is easy:
Output:
Although the output might not be in order, it is clear that the array is the same on all ranks after broadcasting.
Also note that the broadcast
call automatically resizes arrays if necessary. This is not true for views since they cannot be resized. The caller is responsible for making sure that the shapes of the views are correct (otherwise an exception is thrown).
Before we broadcast a view, let us again prepare our arrays:
Output:
Now we want to broadcast the first column of the array on root to the \( i^{th} \) column of the array on rank \( i \):
Output:
If A
would not be in Fortran-order, this code snippet wouldn't compile since the elements of a single column are not contiguous in memory in C-order.
Note: All MPI routines have certain requirements for the arrays/views involved in the operation. Please check out the documentation of the individual function, e.g. in this case nda::mpi_broadcast, if you have doubts.
Suppose we have a 1-dimensional array with rank specific elements and sizes:
Output:
Let us gather the 1-dimensional arrays on the root process:
Output:
Only the root process will have the gathered result available. If the other ranks need access to the result, we can do an all-gather instead:
which is equivalent to
Views work exactly the same way. For example, let us gather 2-dimensional reshaped views of 1-dimensional arrays:
Output:
Higher-dimensional arrays/views are always gathered along the first dimension. The extent of the first dimension does not have to be the same on all ranks. For example, if we gather \( M \) arrays of size \( N_1^{(i)} \times \dots \times N_d \), then the resulting array will be of size \( \prod_{i=1}^M N_1^{(i)} \times \dots \times N_d \) .
nda::mpi_gather requires all arrays/views to be in C-order. Other memory layouts have to be transposed or permuted (see nda::transpose and the more general nda::permuted_indices_view) before they can be gathered.
Output:
The resulting array B_fg
has to be in C-order. If we want it to be in Fortran-order, we can simply transpose it:
Output:
Scattering of an array/view is basically the inverse operation of gathering. It takes an array/view and splits it along the first dimensions as evenly as possible among the processes.
For example, to scatter the same array that we just gathered, we can do
Output:
Similar to the nda::mpi_gather, nda::mpi_scatter requires the input and output arrays to be in C-order.
If the extent along the first dimension is not divisible by the number of MPI ranks, processes with lower ranks will receive more data than others:
Output:
Here, a 2-by-2 array is scattered from rank 2 to all other processes. It is split along the first dimension and the resulting 1-by-2 subarrays are sent to the ranks 0 and 1 while the ranks 2 and 3 do not receive any data.
Let us reduce the same 2-by-2 arrays from above. Be default, mpi::reduce
performs an element-wise summation among the ranks in the communicator and makes the result available only on the root process:
Output:
To use a different reduction operation or to send the result to all ranks, we can do
or the more compact
It is also possible to perform the reduction in-place, i.e. the result is written to the input array:
Output:
In contrast to the standard mpi::all_reduce
function, the in-place operation does not create and return a new array. Instead the result is directly written into the input array.