Views¶
The C++20 ranges library introduced range adaptors: lazy, composable transformations written with a pipe.
std::views::take(n) does not copy or modify anything; it produces a lightweight view of the first n elements of
a range. KaMPIng-v2 applies the same idea to MPI buffers. Its views attach or override MPI properties — the
datatype, the element count, per-rank counts and displacements — and compose with each other and with the standard
views:
v2::send(data | std::views::take(n), dest, comm); // standard view
v2::send(data | views::with_type(my_type), dest, comm); // override the datatype
v2::send(data | std::views::take(n) | views::with_type(my_type), // both
dest, comm);
Every view produces a buffer again, so it works with every operation. All views live in kamping::v2::views; the
reference lists them.
How views work¶
A view wraps a buffer and forwards all accessors to it, except those it is responsible for:
views::with_type(dt) returns dt from type() and passes ptr() and count() through unchanged. Views that
compute something do so lazily, when an operation first asks for it, and only once: views::auto_displs computes
the displacements as the exclusive prefix sum of the counts. Composing views therefore allocates and computes
nothing, and costs nothing if their result is never used.
Ownership¶
Like the standard views, a view borrows an lvalue and owns an rvalue:
std::vector<int> v = ...;
auto a = v | views::with_type(dt); // refers to v
auto b = std::move(v) | views::with_type(dt); // owns the vector
This carries through to the results of operations: buffers that were moved in are handed back by value, borrowed buffers by reference (see Non-blocking operations).
Attaching per-rank counts and displacements¶
For variable-size collectives, with_counts and with_displs attach existing counts and displacements, and
auto_displs computes the displacements from the counts:
// counts and displacements you already have
auto a = data | views::with_counts(counts) | views::with_displs(displs);
// displacements computed from the counts
auto b = data | views::with_counts(counts) | views::auto_displs;
If the displacements you provide are monotonically non-decreasing, say so with
views::with_displs(monotonic, displs): a receive buffer can then be sized from the last count and displacement
alone.
views::flatten_v() builds a variable-size send buffer from a range of per-rank ranges, and views::chunked splits
a variable-size buffer back into per-rank std::spans:
std::vector<std::vector<int>> per_rank = ...; // per_rank[i] goes to rank i
auto [_, recv] = v2::alltoallv(per_rank | views::flatten_v(),
v2::auto_recv_v<int>(), comm);
for (auto from_rank : recv | views::chunked) { ... } // std::spans
Single values¶
views::ref_single(x) makes a single value a buffer of one element:
int sum = local;
v2::allreduce(v2::inplace, views::ref_single(sum), comm);