TL;DR: mdspan
是一个扩展std::span
对于多个维度 - 具有大量(不可避免的)灵活的可配置性。内存布局和访问模式。
在阅读此答案之前,您应该确保您清楚what a span是什么以及它的用途 https://stackoverflow.com/q/45723819/1593077。现在这已经不成问题了:因为mdspan
可能是相当复杂的野兽(通常〜7x或更多源代码作为std::span
实现),我们将从简化的描述开始,并在下面进一步保留高级功能。
“它是什么?” (简单版)
An mdspan<T>
is:
- 从字面上看,一个“multi-d尺寸跨度”(类型-
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};
我们可以查看的数据v
作为 12 个元素的一维数组,类似于其原始定义:
auto sp1 = std::span(v.data(), 12);
auto mdsp1 = std::mdspan(v.data(), 12);
或范围为 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 ) )
我们也可以将其视为 3 x 2 x 2 或 2 x 2 x 3 数组,或 3 x 4 等等。
“我应该什么时候使用它?”
-
(C++23 及更高版本)当您想使用多维operator[]
在你从某处获得的某个缓冲区上。因此在上面的例子中,ms3[1, 2, 0]
is 11
and 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]);
}
你可以写:
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支持多维数组 https://stackoverflow.com/questions/tagged/multidimensional-array完美......只要它们的维度在编译时给出,并且您不尝试将它们传递给函数。这样做是有点棘手 https://stackoverflow.com/q/4051/1593077因为最外层的维度经历衰减,所以你实际上会传递一个指针。但是使用 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);
}
标准化状况
While std::span
在 C++20 中标准化,std::mdspan
不是。然而,它是 C++23 的一部分,几乎已经完成(等待最终投票)。
您已经可以使用参考实现 https://github.com/kokkos/mdspan。它是美国桑迪亚国家实验室的一部分“Kokkos 性能便携生态系统” https://kokkos.github.io/.
“那些‘额外能力’是什么?mdspan
优惠吗?”
An 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 }
,但只存储值2
and 4
在类实例中;编译器知道它需要插入3
每当使用第二个维度时。
-
您可以使用 Fortran 风格将维度从小到大,而不是像 C 中那样从大到小。因此,如果您设置LayoutPolicy = layout_left
, then mds[x,y]
is at mds.data[mds.extent(0) * y + x]
而不是通常的mds.data[mds.extent(1) * x + y]
.
-
你可以“重塑”你的mdspan
进入另一个mdspan
尺寸不同但总体尺寸相同。
-
您可以使用“strides”定义布局策略:让 mdspan 中的连续元素在内存中保持固定距离;有额外的偏移量以及每条线或维度切片的开头和/或结尾; ETC。
-
你可以“切割”你的mdspan
每个维度都有偏移(例如,采用矩阵的子矩阵) - 结果仍然是mdspan
! ...那是因为你可以拥有mdspan
with a LayoutPolicy
其中包含了这些偏移量。此功能在 C++23 IIANM 中不可用。
-
使用AccessorPolicy
, 你(们)能做到mdspan
实际上是do单独或集体拥有他们引用的数据。
进一步阅读
- 官方std::mdspan提议 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0009r18.html,被 C++23 接受。
- The std::mdspan page https://en.cppreference.com/w/cpp/container/mdspan on cppreference.com http://cppreference.com/
-
mdspan 的简单介绍 https://github.com/kokkos/mdspan/wiki/A-Gentle-Introduction-to-mdspan,在Kokkos 参考实现 https://github.com/kokkos/mdspan's wiki.
-
一看mdspan https://www.ashermancinelli.com/std-mdspan-tensors的,作者:Asher Macinelli。
(some examples were adapted from these sources.)