Distributions

View distributions.chpl on GitHub

This primer demonstrates uses of some of Chapel’s standard distributions. To use these distributions in a Chapel program, the respective module must be used:

use BlockDist, CyclicDist, BlockCycDist;
use DimensionalDist2D, ReplicatedDim, BlockCycDim;

ReplicatedDist is covered in the Replicated Distribution primer.

For each distribution, we’ll create a distributed domain and array and then initialize it just to give a brief flavor of how the distribution maps across locales. Running this example on 6 locales does a nice job of illustrating the distribution characteristics.

All of these distributions support options to map to a different virtual locale grid than the one used by default (a multidimensional factoring of the built-in Locales array), as well as to control the amount of parallelism used in data parallel loops. For more details, see the documentation on Standard Distributions.

Make the program size configurable from the command line.

config const n = 8;

Declare a 2-dimensional domain Space that we will later use to initialize the distributed domains.

const Space = {1..n, 1..n};

Block (and distribution basics)

The Block distribution distributes a bounding box from n-dimensional space across the target locale array viewed as an n-dimensional virtual locale grid. The bounding box is blocked into roughly equal portions across the locales. Note that domains declared over a Block distribution can also store indices outside of the bounding box; the bounding box is merely used to compute the blocking of space.

In this example, we declare a 2-dimensional Block-distributed domain BlockSpace and a Block-distributed array BA declared over the domain.

const BlockSpace = Space dmapped Block(boundingBox=Space);
var BA: [BlockSpace] int;

To illustrate how the index set is distributed across locales, we’ll use a forall loop to initialize each array element to the locale ID that stores that index/element/iteration.

forall ba in BA do
  ba = here.id;

The hasSingleLocalSubdomain method on arrays will return true if the index set for a locale can be represented by a single domain.

if !BA.hasSingleLocalSubdomain() then
  halt("For a Block distribution, the index set per locale should be \
      represented by a single domain");

If the distribution’s subdomains can be represented as single subdomain, we can use localSubdomain() to get the index set for the current locale.

Below, we’ll use the index set to confirm that the array elements have the correct locale ID.

for L in Locales {
  on L {
    const indices = BA.localSubdomain();
    for i in indices {
      if BA[i] != L.id then
        halt("Error: incorrect locale id");
    }
  }
}

Output the Block-distributed array to visually see how the elements are partitioned across the locales.

writeln("Block Array Index Map");
writeln(BA);
writeln();

Most of Chapel’s standard distributions support an optional targetLocales argument that permits you to pass in your own array of locales to be targeted. In general, the targetLocales argument should match the rank of the distribution. So for example, to Block-distribute a domain over a 2D numLocales * 1 view of the locale set, one could do something like the following.

We start by creating our own MyLocales array of the locale values. Here we use the standard array reshape function for convenience. Generally, this array could be accessed/assigned like any other.

var MyLocaleView = {0..#numLocales, 1..1};
var MyLocales: [MyLocaleView] locale = reshape(Locales, MyLocaleView);

Then we declare a distributed domain/array that targets this view of the locales:

const BlockSpace2 = Space dmapped Block(boundingBox=Space,
                                        targetLocales=MyLocales);
var BA2: [BlockSpace2] int;

Now we can do a similar computation as before to verify where each array element ended up:

forall ba in BA2 do
  ba = here.id;

writeln("Block Array Index Map");
writeln(BA2);
writeln();

We can use the targetLocales method available on an array to get the locales array used as targets:

for (L, ML) in zip(BA2.targetLocales(), MyLocales) do
  if L != ML then
    halt("Error: BA2.targetLocales() should equal MyLocales");

Cyclic

Next, we’ll perform a similar computation for the Cyclic distribution. Cyclic distributions start at a designated n-dimensional index and distribute the n-dimensional space across an n-dimensional array of locales in a round-robin fashion (in each dimension). As with the Block distribution, domains declared using the Cyclic distribution may have lower indices than the distribution’s starting index. The starting index should just be considered a parameterization of how the distribution is defined.

const CyclicSpace = Space dmapped Cyclic(startIdx=Space.low);
var CA: [CyclicSpace] int;

forall ca in CA do
  ca = here.id;

writeln("Cyclic Array Index Map");
writeln(CA);
writeln();

The domain returned by localSubdomain need not be a dense block, as is the case for the Cyclic distribution.

on Locales[0] {
  const indices = CA.localSubdomain();
  for i in indices {
    if CA[i] != 0 then
      halt("Error: Cyclic array values on Locale 0 should be zero");
  }
}

Block-Cyclic

Next, we’ll use a BlockCyclic distribution. Block-Cyclic distributions also deal out indices in a round-robin fashion, but rather than dealing out singleton indices, they deal out blocks of indices. Thus, the BlockCyclic distribution is parameterized by a starting index (as with Cyclic) and a block size (per dimension) specifying how large the chunks to be dealt out are.

const BlkCycSpace = Space dmapped BlockCyclic(startIdx=Space.low,
                                              blocksize=(2, 3));
var BCA: [BlkCycSpace] int;

forall bca in BCA do
  bca = here.id;

writeln("Block-Cyclic Array Index Map");
writeln(BCA);
writeln();

A locale’s index set for a Block-Cyclic distribution cannot be represented by a single subdomain.

if BCA.hasSingleLocalSubdomain() then
  halt("A Block-Cyclic index set cannot be represented by a single subdomain");

If the local index set cannot be represented by a single subdomain, we can use the localSubdomains iterator to yield a number of domains that represent the whole index set.

Let’s write a function that uses localSubdomains to verify the correctness of the array values.

proc verifyID(Data: []) {
  for L in Locales {
    on L {
      for indices in Data.localSubdomains() {
        for i in indices {
          if Data[i] != L.id then
            halt("Error: incorrect locale id");
        }
      }
    }
  }
}
verifyID(BCA);

The localSubdomains iterator is also available on distributions that can represent a locale’s index set with a single domain. This allows us to write more general code that will work for all distributions.

This means that we can call the ‘verifyID’ function on any array, like the ‘BA’ array from earlier.

verifyID(BA);

2D Dimensional

The DimensionalDist2D distribution lets us build a 2D distribution as a composition of specifiers for individual dimensions. Under such a “dimensional” distribution each dimension is handled independently of the other.

The dimension specifiers are similar to the corresponding multi-dimensional distributions in constructor arguments and index-to-locale mapping rules. However, instead of an array of locales, a specifier constructor accepts just the number of locales that the indices in the corresponding dimension will be distributed across.

The DimensionalDist2D constructor requires: an [0..nl1-1, 0..nl2-1] array of locales, where nl1 and nl2 are the number of locales in each dimension, and two dimension specifiers, created for nl1 and nl2 locale counts, resp.

Presently, the following dimension specifiers are available (shown here with their constructor arguments):

  • ReplicatedDim(numLocales)
  • BlockDim(numLocales, boundingBoxLow, boundingBoxHigh)
  • BlockCyclicDim(lowIdx, blockSize, numLocales)

The following example creates a dimensional distribution that replicates over 2 locales (when available) in the first dimension and distributes using block-cyclic distribution in the second dimension. The example computes nl1 and nl2 and reshapes MyLocales correspondingly.

var (nl1, nl2) = if numLocales == 1 then (1, 1) else (2, numLocales/2);
MyLocaleView = {0..#nl1, 0..#nl2};
MyLocales = reshape(Locales[0..#nl1*nl2], MyLocaleView);

const DimReplicatedBlockcyclicSpace = Space
  dmapped DimensionalDist2D(MyLocales,
                            new ReplicatedDim(numLocales = nl1),
                            new BlockCyclicDim(numLocales = nl2,
                                               lowIdx = 1, blockSize = 2));

var DRBA: [DimReplicatedBlockcyclicSpace] int;

The ReplicatedDim specifier always accesses the local replicand. (This differs from how the Replicated distribution works.)

This example visits each replicand. The behavior is the same regardless of the second index into MyLocales below.

for locId1 in 0..#nl1 do on MyLocales[locId1, 0] {

  forall drba in DRBA do
    drba = here.id;

  writeln("Dimensional2D(Replicated,BlockCyclic) Array Index Map",
          " from ", here);

  // Technicality: 'writeln(DRBA)' would read DRBA always on Locale 0.
  // Since we want to see what DRBA contains on the current locale,
  // we use 'Helper' that is mapped using the default distribution.
  // 'Helper = DRBA' captures the view of DRBA on the current locale,
  // which we then print out.

  const Helper: [Space] int = DRBA;
  writeln(Helper);
  writeln();

}