提问人:einpoklum 提问时间:3/19/2023 最后编辑:Jan Schultkeeinpoklum 更新时间:11/16/2023 访问量:1367
什么是 mdspan,它的用途是什么?
What is an mdspan, and what is it used for?
问:
在过去一年左右的时间里,我注意到 StackOverflow 上一些与 C++ 相关的答案指的是 - 但我从未在 C++ 代码中真正看到过这些答案。我尝试在 C++ 编译器的标准库目录和 C++ 编码指南中查找它们 - 但找不到它们。我确实找到了;我猜它们是相关的——但是怎么回事?这个添加的“md”代表什么?mdspan
std::span
请解释一下这个神秘实体是关于什么的,以及我什么时候可能想使用它。
答:
TL;DR:是多维度的扩展 - 具有许多(不可避免的)灵活的可配置性,以及内存布局和访问模式。mdspan
std::span
在阅读此答案之前,您应该确保您清楚什么是跨度
以及它的用途。现在已经不碍事了:由于 可能是相当复杂的野兽(通常 ~7 倍或更多的源代码作为实现),我们将从简化的描述开始,并保留高级功能在下面进一步介绍。mdspan
std::span
“这是什么?”(简易版)
一个是:mdspan<T>
- 从字面上看,是一个“multi-d imensional span”(类型元素)。
T
- 将 ,从一维/线性元素序列推广到多维。
std::span<T>
- 内存中连续的类型元素序列的非拥有视图,解释为多维数组。
T
- 基本上只是一个带有一些方便的方法(用于在运行时确定的维度)。
struct { T * ptr; size_type extents[d]; }
d
-interpreted 布局的图示mdspan
如果我们有:
std::vector v = {1,2,3,4,5,6,7,8,9,10,11,12};
我们可以将 的数据视为包含 12 个元素的一维数组,类似于其原始定义:v
auto sp1 = std::span(v.data(), 12);
auto mdsp1 = std::mdspan(v.data(), 12);
或范围为 2 x 6 的 2D 数组:
auto mdsp2 = std::mdspan(v.data(), 2, 6 );
// ( 1, 2, 3, 4, 5, 6 ),
// ( 7, 8, 9, 10, 11, 12 )
或 2 x 3 x 2 的 3D 阵列:
auto ms3 = std::mdspan(v.data(), 2, 3, 2);
// ( ( 1, 2 ), ( 3, 4 ), ( 5, 6 ) ),
// ( ( 7, 8 ), ( 9, 10 ), ( 11, 12 ) )
我们也可以将其视为 3 x 2 x 2 或 2 x 2 x 3 阵列,或 3 x 4 等。
“我应该什么时候使用它?”
(C++23 及更高版本)当你想在某个缓冲区上使用多维时,你从某个地方得到。因此,在上面的例子中,是和是。
operator[]
ms3[1, 2, 0]
11
ms3[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 完美地支持多维数组......只要它们的维度是在编译时给出的,并且你不要尝试将它们传递给函数。这样做有点棘手,因为最外层的维度会衰减,所以你实际上会传递一个指针。但是使用 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); }
标准化现状
虽然在 C++20 中是标准化的,但不是。但是,它是 C++23 的一部分,它几乎已经完成(等待最终投票)。std::span
std::mdspan
您已经可以使用参考实现。它是美国桑迪亚国家实验室“Kokkos 性能可移植性生态系统”的一部分。
“这些'额外功能'提供了哪些?”mdspan
实际上有 4 个模板参数,而不仅仅是元素类型和范围:mdspan
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 }
2
4
3
您可以让维度以 Fortran 样式从次要到主要,而不是像 C 那样从主要到次要。因此,如果将 设置为 ,则 是 at 而不是通常的 。
LayoutPolicy = layout_left
mds[x,y]
mds.data[mds.extent(0) * y + x]
mds.data[mds.extent(1) * x + y]
您可以将您的“重塑”为另一个具有不同尺寸但整体尺寸相同的人。
mdspan
mdspan
您可以使用“strides”定义布局策略:将 mdspan 中的连续元素在内存中保持固定距离;具有额外的偏移量以及每行或维度切片的开头和/或结尾;等。
你可以用每个维度的偏移量来“切割”你的 (例如,取矩阵的子矩阵) - 结果仍然是 !...这是因为你可以有一个包含这些偏移量的 A 和 a。此功能在 C++23 IIANM 中不可用。
mdspan
mdspan
mdspan
LayoutPolicy
使用 ,您可以单独或集体地使 实际上拥有它们所引用的数据。
AccessorPolicy
mdspan
延伸阅读
- 官方
的std::mdspan
提案,被C++23接受。 - cppreference.com 上的
std::mdspan
页面 - 对 mdspan 的温和介绍,在 Kokkos 参考实现的 wiki 上。
- 看看
mdspan
的,作者是 Asher Macinelli。
(一些例子改编自这些来源。
评论