ein*_*ica 9 c++ c++-faq std-span mdspan
在过去一年左右的时间里,我注意到 StackOverflow 上有一些与 C++ 相关的答案mdspan,但我从未在 C++ 代码中真正看到过这些答案。我尝试在 C++ 编译器的标准库目录和C++ 编码指南中查找它们- 但找不到它们。我确实找到了std::span;我猜它们是相关的——但是如何呢?添加“md”代表什么?
请解释一下这个神秘实体的用途,以及我何时需要使用它。
ein*_*ica 15
TL;DR:mdspan是多维度的扩展std::span- 在内存布局和访问模式方面具有大量(不可避免的)灵活可配置性。
在阅读此答案之前,您应该确保清楚aspan是什么以及它的用途。现在这已经不成问题了:由于mdspans 可能是相当复杂的野兽(通常〜7倍或更多的源代码作为实现std::span),我们将从简化的描述开始,并在下面进一步保留高级功能。
一个mdspan<T>是:
T元素的)。std::span<T>,从元素的一维/线性序列到多维。T,解释为多维数组。struct { T * ptr; size_type extents[d]; } 一些方便的方法(对于d在运行时确定的尺寸)。mdspan-解释布局的插图如果我们有:
std::vector v = {1,2,3,4,5,6,7,8,9,10,11,12};
Run Code Online (Sandbox Code Playgroud)
我们可以将 的数据视为v包含 12 个元素的一维数组,类似于其原始定义:
auto sp1 = std::span(v.data(), 12);
auto mdsp1 = std::mdspan(v.data(), 12);
Run Code Online (Sandbox Code Playgroud)
或范围 2 x 6 的二维数组:
auto mdsp2 = std::mdspan(v.data(), 2, 6 );
// ( 1, 2, 3, 4, 5, 6 ),
// ( 7, 8, 9, 10, 11, 12 )
Run Code Online (Sandbox Code Playgroud)
或 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 ) )
Run Code Online (Sandbox Code Playgroud)
我们也可以将其视为 3 x 2 x 2 或 2 x 2 x 3 数组,或 3 x 4 等等。
operator[](C++23 及更高版本)当您想在从某处获得的某个缓冲区上使用多维时。因此,在上面的例子中,ms3[1, 2, 0]is11和ms3[0, 1, 1]is 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]);
}
Run Code Online (Sandbox Code Playgroud)
你可以写:
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]);
}
Run Code Online (Sandbox Code Playgroud)
作为传递多维 C 数组的正确类型:
C 完美支持多维数组...只要它们的维度在编译时给出,并且您不尝试将它们传递给函数。这样做有点棘手,因为最外层的维度会经历衰减,所以您实际上会传递一个指针。但是使用 mdspans,你可以这样写:
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);
}
Run Code Online (Sandbox Code Playgroud)
虽然std::span在 C++20 中已标准化,但std::mdspan事实并非如此。然而,它是 C++23 的一部分,几乎已经完成(等待最终投票)。
您已经可以使用参考实现。它是美国桑迪亚国家实验室“Kokkos性能便携生态系统”的一部分。
mdspan?”Anmdspan实际上有 4 个模板参数,不仅仅是元素类型和范围:
template <
class T,
class Extents,
class LayoutPolicy = layout_right,
class AccessorPolicy = default_accessor<ElementType>
>
class mdspan;
Run Code Online (Sandbox Code Playgroud)
这个答案已经相当长了,所以我们不会给出完整的细节,但是:
某些范围可以是“静态”而不是“动态”,在编译时指定,因此不存储在实例数据成员中。仅存储“动态”实例。例如,这个:
auto my_extents extents<dynamic_extent, 3, dynamic_extent>{ 2, 4 };
Run Code Online (Sandbox Code Playgroud)
... 是与 对应的范围对象,但仅存储类实例中的dextents<size_t>{ 2, 3, 4 }值2和;编译器知道只要使用第二个维度4就需要插入。3
您可以使用 Fortran 风格将维度从小到大,而不是像 C 中那样从大到小。因此,如果您设置LayoutPolicy = layout_left,则mds[x,y]是 atmds.data[mds.extent(0) * y + x]而不是通常的mds.data[mds.extent(1) * x + y]。
您可以将您的产品“重塑”mdspan为mdspan具有不同尺寸但总体尺寸相同的另一个产品。
您可以使用“strides”定义布局策略:让 mdspan 中的连续元素在内存中保持固定距离;有额外的偏移量以及每条线或维度切片的开头和/或结尾;ETC。
您可以在每个维度上使用偏移量“切割”您的mdspan(例如,采用矩阵的子矩阵) - 结果仍然是mdspan!...那是因为你可以有一个包含这些偏移量的mdspana LayoutPolicy。此功能在 C++23 IIANM 中不可用。
使用AccessorPolicy,您可以使mdspan's 真正拥有它们所引用的数据,无论是单独的还是集体的。
std::mdspan提案,已被 C++23 接受。std::mdspancppreference.com上的页面mdspan》。(一些例子改编自这些来源。)