Kahibaro
Discord Login Register

Process topologies

Why Process Topologies Matter

In distributed-memory programming with MPI, processes are usually identified by a single number: their rank in MPI_COMM_WORLD. For many algorithms, however, there is a natural geometric or logical structure to how processes should interact (lines, rings, 2D/3D grids, etc.).

Process topologies let you express this structure directly in MPI so that:

Process topologies are logical: they provide a structured view of ranks through new communicators. They do not physically move processes, but they can help achieve better performance and cleaner code.

Types of Process Topologies in MPI

MPI defines two main kinds of process topologies:

Many HPC codes use Cartesian topologies because they match structured meshes, grids, and stencil computations. Graph topologies are more general and fit irregular communication patterns.

All topologies in MPI are attached to communicators. You start from an existing communicator (often MPI_COMM_WORLD), define a topology, and get back a new communicator with that topology attached.

Cartesian Topologies

Creating a Cartesian Communicator

You describe a grid of processes by specifying:

MPI helps you distribute processes into this grid with MPI_Dims_create and then builds the new communicator with MPI_Cart_create.

int ndims = 2;
int dims[2] = {0, 0};        // 0 means "let MPI choose"
int periods[2] = {0, 0};     // non-periodic in both directions
int reorder = 1;             // allow rank reordering for performance
MPI_Comm cart_comm;
int size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Let MPI choose a good 2D grid layout for "size" processes
MPI_Dims_create(size, ndims, dims);
// Create Cartesian communicator
MPI_Cart_create(MPI_COMM_WORLD, ndims, dims, periods,
                reorder, &cart_comm);

Key points:

Coordinates and Ranks

In a Cartesian communicator, each process has both:

You can convert between them:

int my_rank, my_coords[2];
MPI_Comm_rank(cart_comm, &my_rank);
// Get my coordinates in the 2D grid
MPI_Cart_coords(cart_comm, my_rank, 2, my_coords);
// Example: find rank at coordinates (x, y)
int x = 0, y = 0;
int origin_rank;
int target_coords[2] = {x, y};
MPI_Cart_rank(cart_comm, target_coords, &origin_rank);

This mapping is extremely useful in domain-decomposed codes: coordinates often directly correspond to subdomain indices.

Finding Neighbors: Stencil and Domain Decomposition

Nearest-neighbor communication appears frequently in structured-grid solvers (stencils, finite differences, etc.). Cartesian topologies make it easy to find neighbors with MPI_Cart_shift.

For a 2D grid, suppose dimension 0 is the "x" direction and dimension 1 is "y". You can find neighbors in each direction:

int source, dest;
// Shift along x-direction (dim=0)
MPI_Cart_shift(cart_comm, 0, 1, &source, &dest);
// "source" is neighbor at -1 in x
// "dest"   is neighbor at +1 in x
// Shift along y-direction (dim=1)
MPI_Cart_shift(cart_comm, 1, 1, &source, &dest);
// "source" is neighbor at -1 in y
// "dest"   is neighbor at +1 in y

If the grid is non-periodic, boundary processes will have MPI_PROC_NULL as a neighbor when there is no process in that direction. This is convenient: many MPI routines ignore MPI_PROC_NULL without extra checks.

Typical usage: exchanging "halo" or "ghost" cells with immediate neighbors by pairing MPI_Sendrecv or non-blocking sends/receives, using neighbor ranks from MPI_Cart_shift.

Periodic Boundaries

For some problems (e.g., simulation on a torus or with periodic boundary conditions), edges of the domain wrap around. This is encoded via the periods array.

Example: 2D periodic grid in both directions:

int periods[2] = {1, 1};   // periodic in x and y
MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods,
                1, &cart_comm);

In this case, MPI_Cart_shift will always return valid ranks; the "right" neighbor of the rightmost process is the leftmost process, and similarly for the other boundaries.

Sub-Communicators for Rows and Columns

Often you need to perform collective operations along rows or columns of your process grid (e.g., for parallel matrix algorithms). MPI_Cart_sub creates sub-communicators by collapsing some dimensions.

Example: from a 2D grid, create row-wise and column-wise communicators:

int remain_dims_row[2] = {0, 1};  // keep y-dimension, collapse x
int remain_dims_col[2] = {1, 0};  // keep x-dimension, collapse y
MPI_Comm row_comm, col_comm;
MPI_Cart_sub(cart_comm, remain_dims_row, &row_comm);
MPI_Cart_sub(cart_comm, remain_dims_col, &col_comm);

Each process now belongs to:

You can then perform MPI_Bcast, MPI_Allreduce, etc. along rows or columns independently, which is standard in many parallel linear algebra and FFT algorithms.

Graph and Distributed Graph Topologies

Cartesian topologies assume a grid-like structure. For more irregular communication patterns, MPI provides graph and distributed graph topologies.

Classic Graph Topology

In a graph topology, processes are vertices, and edges indicate potential communication partners. You describe the whole graph, then let MPI create a communicator that encapsulates it.

Basic ideas:

However, the classic graph interface is less scalable for very large systems because graph information can be collective and global.

Distributed Graph Topology

To handle scalability, MPI introduced distributed graph topologies (MPI_Dist_graph_create and related routines). Each process specifies its own neighbors locally, and MPI constructs a topology communicator.

Key characteristics:

Example schematic (using the C API, omitting full error checking):

// Suppose each process knows its out-degree and destination ranks
int outdegree = ...;
int *destinations = ...;    // array of neighbor ranks
MPI_Comm graph_comm;
MPI_Dist_graph_create_adjacent(
    MPI_COMM_WORLD,
    outdegree, destinations, MPI_UNWEIGHTED,
    outdegree, destinations, MPI_UNWEIGHTED,
    MPI_INFO_NULL, 1, &graph_comm);

Once created, you can query neighbors using:

You can then implement generic "send to all neighbors" / "receive from all neighbors" patterns without hard-coding rank IDs.

Graph topologies are particularly useful for:

Performance and Mapping Considerations

Process topologies are not just syntactic sugar; they can influence performance when used correctly.

Rank Reordering

Both Cartesian and graph topologies allow MPI to reorder ranks when creating the new communicator:

You should not assume any particular relationship between MPI_COMM_WORLD ranks and those in your topology communicator if reordering is allowed.

Matching Topology to Algorithm and Hardware

Some guidelines:

Letting MPI know your topology gives the library more information to:

Performance benefits vary by system and MPI implementation, but on large machines and communication-heavy codes, they can be significant.

When to Use Process Topologies

They are most beneficial when:

You may not need them when:

In practice, many mature HPC libraries (e.g., parallel linear algebra packages) internally use process topologies to organize their computations; understanding them helps you reason about how these libraries scale and behave.

Views: 11

Comments

Please login to add a comment.

Don't have an account? Register now!