c++ 什么是mdspan,它的用途是什么?

8yoxcaq7  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(143)

在过去的一年里,我注意到StackOverflow上的一些与C相关的答案提到了mdspan的--但我从来没有在C代码中真正看到过这些。我试着在我的C++编译器的标准库目录和the C++ coding guidelines中寻找它们--但找不到。我确实找到了std::span的;我猜它们是相关的--但怎么找到的?这后面的“md”代表什么?
请解释一下这个神秘的实体是关于什么的,以及我什么时候可能想要使用它。

ozxc1zmp

ozxc1zmp1#

TL; DRmdspanstd::span在多个维度上的扩展-具有很多(不可避免的)关于内存布局和访问模式的灵活配置。

在你阅读这个答案之前,你应该确保你已经清楚了what a span is and what it's used for。现在已经不碍事了:由于mdspan的可能是相当复杂的野兽(通常是std::span实现的7倍或更多的源代码),我们将从一个简化的描述开始,并在下面保留高级功能。

“是什么?”(简单版)

mdspan<T>是:
1.从字面上看,是一个“multi-dimensional span”(类型为-T元素)。

  1. std::span<T>的推广,从一维/线性元素序列到多维。
    1.内存中T类型元素的连续序列的非拥有视图,解释为多维数组。
    1.基本上只是一个struct { T * ptr; size_type extents[d]; },带有一些方便的方法(在运行时确定d的尺寸)。

mdspan-解释型布局示意图

如果我们有:

std::vector v = {1,2,3,4,5,6,7,8,9,10,11,12};

字符串
我们可以将v的数据视为12个元素的一维数组,类似于其原始定义:

auto sp1 = std::span(v.data(), 12);
auto mdsp1 = std::mdspan(v.data(), 12);


或2D扩展区阵列2 x 6:

auto mdsp2 = std::mdspan(v.data(), 2, 6 );
// (  1,  2,  3,  4,  5,  6 ),
// (  7,  8,  9, 10, 11, 12 )


或3D阵列2 x 3 x 2:

auto ms3 = std::mdspan(v.data(), 2, 3, 2);
// ( ( 1,  2 ), ( 3,  4 ), (  5,  6 ) ),
// ( ( 7,  8 ), ( 9, 10 ), ( 11, 12 ) )


我们也可以把它看作是一个3x 2x2或2x2 x3的数组,或者3x 4等等。

“什么时候用?”

  • (C++23及更高版本)当你想在某个缓冲区上使用多维operator[]时,你从某处得到。因此在上面的例子中,ms3[1, 2, 0]11ms3[0, 1, 1]4
  • 当你想传递多维数据而不分离原始数据指针和维时。你在内存中得到了一堆元素,并且想使用多个维来引用它们。因此,而不是:
void print_matrix_element(
   float const* matrix, size_t row_width, size_t x, size_t y) 
{
   std::print("{}", matrix[row_width * x + y]);
}


你可以这样写:

void print_matrix_element(
    std::mdspan<float const, std::dextents<size_t, 2>> matrix,
    size_t x, size_t y)
{
   std::print("{}", matrix[x, y]);
}

  • 作为传递多维C数组的正确类型:

C完全支持multidimensional arrays..

template <typename T, typename Extents>
void print_3d_array(std::mdspan<T, Extents> ms3)
{
   static_assert(ms3.rank() == 3, "Unsupported rank");
   // read back using 3D view
   for(size_t i=0; i != ms3.extent(0); i++) {
     fmt::print("slice @ i = {}\n", i);
     for(size_t j=0; j != ms3.extent(1); j++) {
       for(size_t k=0; k != ms3.extent(2); k++)
         fmt::print("{} ",  ms3[i, j, k]);
       fmt::print("\n");
     }
   }  
}

int main() {
    int arr[2][3][2] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

    auto ms3 = std::mdspan(&arr[0][0][0], 2, 3, 2);
      // Note: This construction can probably be improved, it's kind of fugly

    print_3d_array(ms3);
}

标准化状态

虽然std::span在C20中被标准化,但std::mdspan并没有。然而,它是C23的一部分,几乎已经完成(等待最终投票)。
你已经可以使用reference implementation了,它是美国桑迪亚国家实验室"Kokkos performance portability ecosystem"的一部分。

mdspan提供了哪些”额外功能“?”

mdspan实际上有4个模板参数,而不仅仅是元素类型和范围:

template <
    class T,
    class Extents,
    class LayoutPolicy = layout_right,
    class AccessorPolicy = default_accessor<ElementType>
>
class mdspan;


这个答案已经很长了,所以我们不会给予完整的细节,但是:

  • 有些区段可以是“静态”而不是“动态”的,在编译时指定,因此不存储在示例数据成员中。只有“动态”示例被存储。例如,这:
auto my_extents extents<dynamic_extent, 3, dynamic_extent>{ 2, 4 };


.是一个对应于dextents<size_t>{ 2, 3, 4 }的extent对象,但它只在类示例中存储值24;编译器知道每当使用第二维时它需要插入3

  • 你可以让维度从minor到major,在Fortran风格中,而不是像C中那样从major到minor。因此,如果你设置LayoutPolicy = layout_left,那么mds[x,y]就在mds.data[mds.extent(0) * y + x],而不是通常的mds.data[mds.extent(1) * x + y]
  • 您可以将您的mdspan“重塑”为另一个具有不同尺寸但整体大小相同的mdspan
  • 您可以使用“strides”定义布局策略:使mdspan中的连续元素在内存中保持固定距离;使每行或维度切片的开始和/或结束具有额外的偏移量;等等。
  • 你可以在每一个维度上用偏移量“切割”你的mdspan(例如,取一个矩阵的子矩阵)-结果仍然是mdspan!.这是因为你可以有一个mdspan和一个包含这些偏移量的LayoutPolicy。这个功能在C++23 IIANM中是不可用的。
  • 使用AccessorPolicy,您可以使mdspan实际上拥有它们引用的数据,单独或共同。

进一步阅读

(some这些例子是从这些来源改编的。

相关问题