How do I iterate an mdspan?
Asked Answered
E

2

8

So, I decided I want to use an mdspan rather than a combination of plain span + element access function. But - one obvious thing I want to do with my mdspan is iterate its elements. This is how I would do it with a 1D span:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto sp = std::span(vec.data(), 12);
for (auto x : sp) {
    std::cout << x << ' ';
}
std::cout << '\n';

... but not for mdspan's (using the Kokkos implementation):

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::mdspan(vec.data(), 12);
for (auto x : ms) {
    std::cout << x << ' ';
}
std::cout << '\n';

Trying the above in GodBolt (with GCC trunk), I get:

<source>:10:19: error: 'begin' was not declared in this scope
   10 |     for (auto x : ms) {
      |                   ^~

so, it seems mdspans are not ranges - even if they're one-dimensional (and I was even hoping to iterate 2D or 3D spans...). How come? And how do I iterate them?

Elena answered 18/3, 2023 at 22:21 Comment(0)
B
6

The iterator support for mdspan has been early on moved from the mdspan paper to deal with the specification to another paper (for which there does not seem to be any progress).

The mdspan proposal that has been voted into C++23 does neither contain iterators nor submdspans which would allow for creating slices of mdspan objects (on the latter there is being actively worked on).

There is a workaround to create a iterateable view on mdspan which can be used with algorithms from std::ranges using C++23 views::cartesian_product:

std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
auto ms = std::experimental::mdspan(vec.data(), 2, 6);

auto coeff_view =
    std::views::cartesian_product(std::views::iota(0uz, ms.extent(0)),
                                    std::views::iota(0uz, ms.extent(1))) |
    std::views::transform([ms](const auto index) {
        const auto [r, c] = index;
        return ms[r, c];
    });

std::cout << std::ranges::max(coeff_view) << '\n';

The bad news is that this workaround is way slower than operating on the underlying data directly as the index calculation seemingly cannot be optimized by the compiler as seen in this example: https://godbolt.org/z/7a4T6KxY6.

Another thing that can be useful is the layout information baked into the mdspan type. When the layout_type of a mdspan is layout_right it is my understanding that it is safe to assume that the underlying data is contiguous and thus data_handle() can be safely transformed into a std::span which can be used with std::ranges algorithms:

template <typename Mdspan>
concept mdspan_specialization = std::same_as<
    Mdspan, std::experimental::mdspan<
                typename Mdspan::element_type, typename Mdspan::extents_type,
                typename Mdspan::layout_type, typename Mdspan::accessor_type>>;

template <mdspan_specialization Mdspan>
    requires(std::same_as<typename Mdspan::layout_type,
                          std::experimental::layout_right>)
auto to_span(const Mdspan ms) {
    return std::span{ms.data_handle(), ms.size()};
}
Babyblueeyes answered 19/3, 2023 at 6:29 Comment(3)
This basically means don't use mdspan. Where's the consistency? It was initiated as a view; Now just doesn't satisfy the requirements of a view.Schoolboy
@Schoolboy I wouldn't go that far to say don't use mdspan. It still serves as a vocabulary type, allowing to consume multidimensional data from different sources (imagine allowing to pass matrices from eigen, opencv, ipp all with a single mdspan interface). With the template "metadata" there still seem to be ways to achieve good performance, just not out of the box. Maybe people will find better ways to deal with this as soon as first implementations make their way into the std libraries.Babyblueeyes
The risk of out of bounds indexing is best avoided using proper range algorithms and range-based for. If you need to define own wrappers, then redefining mdspan would be cheaper. Otherwise mere syntax sugar for indexing is not any appealing. Specifically if mdarray is supposed to be anything near this kind of mdspan, it is not worth the trouble.Schoolboy
E
2

It seems you need to iterate an mdspan just like a plain C array:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::experimental::mdspan(vec.data(), 12);
for (auto i : std::views::iota(0uz, ms.extent(0))) {
    std::cout << ms[i] << ' ';
}
std::cout << '\n';

... and the same goes for multi-dimensional spans - just like multidimensional C arrays, but with the multi-argument operator[]:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::experimental::mdspan(vec.data(), 2, 6);
for (auto r : std::views::iota(0uz, ms.extent(0))) {
    for (auto c : std::views::iota(0uz, ms.extent(1))) {
        std::cout << ms[r, c] << ' ';
        // ... and note that ms[r][c] won't work! :-(
    }
    std::cout << '\n';
}

See this second example at work: GodBolt.

Now, I'm not sure why you can't just directly iterate - since mdspan's could definitely have been made iterable. Perhaps the idea is to emphasize how the order in memory is not guaranteed? I wonder.

Elena answered 18/3, 2023 at 22:21 Comment(1)
As far as I understand, mdspan is not iterable because the authors didn't find an efficient general way to iterate over the arbitrary layouts that the library wants to support. please correct me if I am wrong. you will appreciate this library (of mine) which precisely addresses this problem: github.com/correaa/boost-multiMopey

© 2022 - 2024 — McMap. All rights reserved.