icode9应用技巧:使用Mdspan 类模板在C++中处理多维数组
作者:互联网
尽管全球有400万C++程序员,但他们中的许多人缺乏提供雇主所需服务所需的掌握能力。因此,这些有价值的专家仍然短缺。
C++程序员之所以有如此出色的工作保障,是因为语言复杂且难以掌握。因此,如今对程序员的需求激增。特别是C++程序员可以找到稳定的工作。如果你是一个熟练的程序员,有很多很棒的C++工作。
幸运的是,如果程序员使用正确的资源来学习它并优先考虑他们寻求学习的技能,他们将更容易掌握它。他们应该尝试学习的最重要的技能之一是使用多维数组。
使用 mdspan 类模板处理多维数组
P0009 标准化C++设置的实施使许多程序员的工作变得更容易。此设置可帮助现代C++程序员使用非专有且可能可变的多维数组管理代码。
此代码可以处理 std::experimental::mdspan 类模板,如以下代码所示:
namespace std::experimental {
template<typename Element_type,
typename Extents,
typename Layout_policy = layout_right,
typename Accessor_policy = default_accessor<Element_type>>
class mdspan;
}
这是C++编码的高级方法的示例。该函数具有以下四个参数:
- Element_type:这是引用的数据类型。
- 扩展数据块:这是 std::experimental::extents 可变参数类模板的专用化,其参数的扩展数据块数量与多维数组的扩展数据块数量(范围 R)匹配,允许静态或动态地指定其长度 Nk(请参阅下面的示例)。
- Layout_policy(可选):此参数指定将多维索引 i ∈ I 映射到数组元素的公式(和公式属性)。默认情况下,该库使用 C 和 C++ 的行主顺序特征(layout_right,行主序)。但是,它还提供了 Fortran 和 MATLAB 的列主顺序特征(layout_left,列主顺序),以及上述顺序的泛化,允许为每个扩展区域注册不同的步骤(可能不是单位)(layout_stride)。
- Accessor_policy(可选):这是控制元素读取和写入方式(例如,原子方式)的访问描述符。
您可以考虑使用矩阵的示例,其中 R = 2,扩展数据块 N0 = 3 和 N1 = 4。采用行主顺序,您可以使用类型为 std::array<int,12> 的容器将数组的数字条目连续存储在内存中:
auto data = std::array{1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12};
您可以使用此方法获得条目的高空间局部性,并立即生成二维 3⨯4 2⨯4 数组的视图。还可以使用以下代码在编译时设置 N0 和 N1 扩展:
namespace stde = std::experimental;
auto ms = stde::mdspan{data.begin(), stde::extents<3, 4>{}};
编写此代码时,ms 的类型为:
stde::mdspan<int,stde::extents<3ull,4ull>,stde::layout_right, stde::default_accessor<int>>.
您希望在数据存储空间和 mdspan 视图之间严格分离。在这种情况下,mdspan 视图是非专有的。
在前面概述的示例中,我们需要调用对公共成员函数 ms.rank()、ms.extent(0) 和 ms.extent(1) 的调用。它们将分别返回多维数组的秩 R = 2 和范围 N0 = 3(行数)和 N1 = 4(列数)。您可以使用以下循环来表示数组:
namespace stdv = std::views;
for (auto&& r : stdv::iota(0uz, ms.extent(0))) {
for (auto&& c : stdv::iota(0uz, ms.extent(1))) {
fmt::print("{:>3}", ms(r, c));
}
fmt::print("\n");
}
// output: 1 2 3 4 5 6 7 8 9 10 11 12
在这种情况下,您使用 std::views::iota 范围因子为每个扩展生成一维索引序列(所有扩展都从零开始),并使用 C++23 后缀 uz 来初始化 std::size_t 值。您可以使用以下简单代码将矩阵第二行的所有元素乘以 2:
for (auto&& c : stdv::iota(0uz, ms.extent(1))))
ms(1, c) *= 2;
您还可以使用 ssliceingsubmdspan 函数获取现有 mdspan 对象子集的视图:
// rows [1, 3) and columns [1, 4) of the matrix ms after multiplying its second row by 2:
auto sbs = stde::submdspan(ms, std::pair{1, 3}, std::pair{2, 4});
for (auto&& i : stdv::iota(0uz, sbs.extent(0))) {
for (auto&&& j : stdv::iota(0uz, sbs.extent(1)))) {
fmt::print("{:>3}", sbs(i, j));
}
{ fmt::print("{">3}");
}
// output: 14 16 11 12 \n
其中 SBS 的内存布局为 layout_stride 类型。在这种情况下,用于每个扩展的切片说明符由一个连续的索引范围给出,这些索引范围由一对类型为 std::p air 的值限定(或者,可以使用 std::tuple)。在这两种情况下,视图范围都不会减小。该库还允许将说明符减少到单个整数索引(在这种情况下,视图范围减少一个单位)或将其作为内联对象和constexpr stde::full_extent(然后选择扩展的所有索引,不缩小视图范围)[4]
Slice specifier Argument of submdspan Reduce range.
Single index Integer Yes
Range of std::pair or std::tuple indexes with two integers No
All std::experimental::full_extent indexes No
P0009提案的主要特点之一是可以使用甚至组合动态和静态扩展。按照 Bryce Adelstein Lelbach (Nvidia) 在研讨会 [4] 中提供的示例,让我们考虑矩阵 A 的转置矩阵的并行计算,该矩阵 A 的 m⨯n 扩展在运行时设置。出于纯粹的说明目的,我们将用伪随机实值填充矩阵条目:
auto rng = std::mt19937{std::random_device{}()};
auto dst = std::uniform_real_distribution{1.0, 10.0};
auto rand = [&]{ return dst(rng); };
// 2-dimensional array extensions:
auto const m = /* set in runtime */,
n = /* set in runtime */;
// array storage space A:
auto a_storage = std::make_unique_for_overwrite<double[]>(m*n);
// we set random entries for A:
for (auto&&& e : stdv::iota(0uz, m*n))
a_storage[e] = rand();
// view for A (dynamically set length extensions):
auto a = stde::mdspan{a_storage.get(), m, n};
其中生成的视图的类型为 stde::mdspan<double,stde::d extents<2>、stde::layout_right,stde::d efault_accessor<double>>、stde::d extents<2> 只是 stde::extents<stde::d ynamic_extent、stde::d ynamic_extent> 的别名。转置矩阵 B = 然后计算为:
// storage space of the transposed matrix B = A^t:
auto b_storage = std::make_unique_for_overwrite<double[]>(n*m);
// associated view:
auto b = stde::mdspan{b_storage.get(), n, m};
// multidimensional index space accessing array B:
auto b_indices = tl::views::cartesian_product(stdv::iota(0uz, n), stdv::iota(0uz, m));
// parallelized computation of the transposed matrix entries:
std::for_each(
std::execution::par_unseq,
std::begin(b_indices), std::end(b_indices),
[=](auto&& ind){
auto&&& [r, c] = ind;
b(r, c) = a(c, r);
}
);
其中,我们使用了范围库 [5] 提供的笛卡尔乘积视图的实现,以定义矩阵 B 的多维索引空间。
最后,让我们注意到,mdspan 视图可以被值捕获,而不会产生任何空间成本(特别是当它们的扩展是静态设置的时),因为它们实现了引用语义。
使用多维数组,成为一名优秀的C++程序员
上述准则将帮助您使用 mdspan 类模板处理多维数组。如果您仍然迷路,您可能会找到一些很棒的在线资源。
如果你想成为一名杰出的程序员,你也应该继续研究其他主题。Cocos 论坛上的威胁表明,越来越多的C++程序员担心使游戏符合GDPR。许多其他在线社区表明,开发人员在处理其他项目时也担心这一点。您将希望使用正确的电子学习服务来了解有关GDPR以及作为程序员将遇到的其他问题的更多信息。