.. default-domain:: chpl .. _primers-distributions: Distributions ============= `View distributions.chpl on GitHub `_ This primer introduces Chapel's features for distributed domains and arrays, illustrating them using a few of Chapel's standard distributions. Local vs. Distributed Domains and Arrays ---------------------------------------- In Chapel, *distributions* are recipes for implementing arrays and their index sets (*domains*). Each distribution indicates how a domain's indices should be mapped to locales. It also specifies how arrays declared over such distributed domains should be stored in each locale's memory, accessed, iterated over, etc. This primer will assume you already have some familiarity with Chapel's concepts of :ref:`arrays `, :ref:`domains `, and :ref:`locales `; if not, refer to their respective primers for more information. By default, when a domain is declared, all of its indices are owned by the locale on which the current task is running. Similarly, an array declared over such a domain will store all of its elements in the current locale's memory. For this reason, we say that Chapel domains and arrays are *local* by default, since they only use a single locale's resources for their implementation. However, as this primer will demonstrate, domains and arrays can also be declared in terms of a distribution as a means of leveraging multiple locales, their memory, and processors. Crucially, the operations supported by domains and arrays in Chapel are independent of whether they are stored on a single locale or distributed across multiple. The only difference is how and where the operations are implemented, which can have impacts on performance and scalability—positive or negative. This property makes it easy to implement and debug an algorithm on a single node, and then deploy it on a large-scale supercomputer. It also permits you to change the distributions used by a program to alter its implementation. Properties of Typical Distributions ----------------------------------- Distributions for rectangular arrays can be thought of as distributing a *d*-dimensional space of possible indices over a *d*-dimensional array of locales. Most distribution types are characterized by this *rank*, *d*, as well as the index type (*idxType*) used to represent indices in each dimension. For example, a 3D distribution might map ``(int, int, int)`` indices to a 3D array of locales, defining which one owns any given index ``(i, j, k)``. It would also specify how the array elements corresponding a locale's local indices are stored in memory. Typical Chapel distributions use an "owner computes" model in which each locale owns a subset of a domain's indices as well as the array elements corresponding to those indices. When ``forall`` loops or other data-parallel operations are executed across such distributed domains and arrays, each locale executes the iterations corresponding to its indices and array elements in parallel, using its locally available processor cores. Multiple domains may share a single distribution, even if their index sets are different. For typical distributions, this ensures that a given index ``(i, j, k)`` is owned by the same locale for all of the domains, implying that computations on matching indices will not require communication. Domains with this property are said to be *aligned*. When multiple arrays are declared in terms of a single distributed domain, they are guaranteed to be aligned, since they all share the same distribution. Getting Started with Block and Cyclic Distributions --------------------------------------------------- In this primer, we'll introduce two common distributions: the first maps indices to locales using contiguous rectilinear blocks; the other deals indices out to locales in a round-robin fashion. To use these distributions in a Chapel program, their respective modules must be used or imported: .. code-block:: chapel use BlockDist, CyclicDist; A third and somewhat atypical distribution, ``replicatedDist``, is covered in :ref:`its own primer `. It does not adhere to some of the characterizations described as "typical" above. For each distribution in this primer, we'll create a distributed domain and array using that distribution. Then we'll initialize the array using the IDs of the locales owning the elements to illustrate how indices are mapped to locales. Running this example on 6 locales does a nice job of illustrating the distribution's characteristics. By default, these distributions will target the entire array of ``Locales`` on which the program is running, heuristically reshaping them into a *d*-dimensional grid whenever *d* is greater than 1. When a user wants to exert more control over how the locales are targeted—for example to specify the number of locales in each dimension, or to target just a subset of the locales—they may create their own array of locales and pass that in as an argument, as we'll see below. First, we'll declare the problem size to use for our 2D domains and arrays, configurable from the command-line: .. code-block:: chapel config const n = 8; Next, we'll declare a 2-dimensional domain ``Space``, which will form the basis of our distributed domains. .. code-block:: chapel const Space = {1..n, 1..n}; The Block Distribution ---------------------- The ``blockDist`` distribution partitions a *d*-dimensional bounding box across the *d*-dimensional target locale array. The bounding box is divided into roughly equal-size block sections, where each locale owns one of them. In this example, we declare a 2-dimensional block distribution whose bounding box is defined by ``Space``. We then use that distribution instance to create a distributed domain and an array over the domain: .. code-block:: chapel const BlkDist = new blockDist(boundingBox=Space); const BlockSpace = BlkDist.createDomain(Space); var BA: [BlockSpace] int; Since the code above only uses a single domain and array, it could also be written in one of the following ways for convenience: .. code-block:: chapel // create the domain using an anonymous distribution const BlockSpace = blockDist.createDomain({1..n, 1..n}); const BA: [BlockSpace] int; or: .. code-block:: chapel // create the array using an anonymous distribution and domain const BA = blockDist.createArray({1..n, 1..n}, int); Or, in both versions, `Space` could be substituted for the domain literal, if preferred. One motivation for declaring explicit distribution and domain variables as we've done here is to support the creation of aligned domains and arrays. Another is to amortize the overheads incurred by such types, since creating any distributed object requires communication and memory. Reasoning About Ownership ------------------------- To illustrate how our block-distributed domain and array are mapped to locales, let's use a forall loop that assigns each array element the locale ID that stores that index/element/iteration. .. code-block:: chapel forall ba in BA do ba = here.id; This loop relies on Chapel's owner-computes model by querying the locale on which a given iteration is running using ``here.id``. As mentioned above, the forall loop will be executed such that each locale will use its local processor cores to execute the iterations that were mapped to it. As a result, each value of ``BA`` will end up storing the ID of the locale that owns it. We can see this ownership map by printing the array out: .. code-block:: chapel writeln("Block Array Ownership Map"); writeln(BA); writeln(); We can also determine which indices a given locale owns using the ``.localSubdomain()`` query. This method returns a non-distributed domain representing the indices that are local to the current locale. As an example, the following line prints the indices that locale 0 owns, since that is where the current task is running: .. code-block:: chapel writeln("Locale 0 owns the following indices of BA: ", BA.localSubdomain()); writeln(); As another example, the following code iterates over all the locales, querying the local indices and checking that the corresponding array elements have the expected value: .. code-block:: chapel coforall L in Locales { on L { const myIndices = BA.localSubdomain(); for i in myIndices { if BA[i] != L.id then halt("Error: incorrect locale id"); } } } Creating an Aligned Domain -------------------------- Domains declared in terms of a ``blockDist`` distribution can also include indices outside of the bounding box. That is, the bounding box is only used to compute a partitioning of the complete *d*-dimensional space of indices and does not impose a constraint on legal domain indices. Any indices located outside the bounding box will be mapped to the locale that owns the nearest index within it. For example, we can declare a larger domain than ``BlockSpace`` as follows: .. code-block:: chapel const BigBlockSpace = BlkDist.createDomain({0..n+1, 0..n+1}); In this case, the rows and columns numbered ``0`` and ``n+1`` fall outside of the bounding box. As a result, indices like ``(0, i)`` will be owned by the same locale that owns ``(1, i)`` within the box. Similarly, index ``(n+1, 0)`` will be owned by the locale that owns ``(n, 1)``. Because ``BigBlockSpace`` shares its distribution with ``BlockSpace``, we know that any indices common to both domains will be owned by the same locale, and that the domains are *aligned*. Specifying Target Locales ------------------------- As mentioned above, most Chapel distributions support an optional ``targetLocales`` argument that permits you to specify your own array of locales to be targeted. In general, the *targetLocales* argument will match the rank of the distribution. For example, to block-distribute a domain such that each locale owns a block of rows but all of the columns, we can create a ``numLocales x 1`` view of the locale set as follows: We start by creating our own ``MyLocales`` array of locale values. Here, we use the standard array ``reshape()`` procedure for convenience. More generally, this array can be declared and created like any other. .. code-block:: chapel var MyLocales = reshape(Locales, {0..