C++2b(C++23)標準ライブラリに追加される多次元ビューstd::mdspan
について。multidimensional spanの略。
// <mdspan>ヘッダ namespace std { template< class ElementType, class Extents, class LayoutPolicy = layout_right, class AccessorPolicy = default_accessor<ElementType>> class mdspan; }
まとめ:
- メモリ領域に対して多次元配列風の要素アクセスビューを提供する。
mdspan
コンストラクタと推論ガイド(deduction guide)が多数提供される(→id:yohhoy:20230308)mdspan
のコピーは定数時間。- 多次元ビューサイズがコンパイル時定数ならポインタ1個分、実行時変数なら ポインタ1個+インデクス値×次元数(rank) 程度のメモリコピー。
- 要素アクセスには多次元
[]
演算子を利用。*2- 内部的に多次元インデクスからメモリ領域へのオフセット位置へ換算する。
- 例:
m[1, 2]
,data[i, j, k]
Extents
:多次元ビューのインデクス型I
*3、次元数(rank)、各次元の要素数(extent)。LayoutPolicy
:多次元ビューのメモリレイアウトをカスタマイズ可能。layout_right
:行優先(row major)レイアウト。C/C++互換。layout_left
:列優先(column major)レイアウト。FortranやMATLAB互換。- ユーザ定義クラスによる高度なカスタマイズもサポートする(→id:yohhoy:20230315)。*6
AccessorPolicy
:要素アクセス方法をカスタマイズ可能。- C++2b(C++23)時点では
mdspan
の “部分ビュー/Slicing” 表現は存在しない。- 次期C++2c(C++26)にて
mdspan
から部分ビューを取り出すsubmdspan
関数が追加される(→id:yohhoy:20240201)。
- 次期C++2c(C++26)にて
- 1次元ビュー
std::span
とは異なり、範囲for文による要素走査はできない。
基本的な利用例
// C++2b(C++23) #include <array> #include <mdspan> #include <print> int a[] = { 1, 2, 3, 4, 5, 6 }; // 2x3要素の2次元ビュー/動的サイズ int cols = 2, rows = 3; std::mdspan m1{a, cols, rows}; for (size_t i = 0; i < m1.extent(0); ++i) for (size_t j = 0; j < m1.extent(1); ++j) std::println("m1[{},{}]=={}", i, j, m1[i, j]); // m1[0,0]==1 m1[0,1]==2 m1[0,2]==3 // m1[1,0]==4 m1[1,1]==5 m1[1,2]==6 // 3x2要素の2次元ビュー/固定サイズ using Mat3x2 = std::mdspan<int, std::extents<size_t, 3, 2>>; Mat3x2 m2{a}; // m2[0,0]==1 m2[0,1]==2 // m2[1,0]==3 m2[1,1]==4 // m2[2,0]==5 m2[2,1]==6 // 列優先3x2要素の2次元ビュー using FortranMatrix = std::mdspan<int, std::dextents<size_t, 2>, std::layout_left>; FortranMatrix m3{a, 3, 2}; // m3[0,0]==1 m3[0,1]==4 // m3[1,0]==2 m3[1,1]==5 // m3[2,0]==3 m3[2,1]==6
メモリレイアウト
mdspan
ではレイアウトポリシーLayoutPolicy
に従って、多次元インデクスからメモリ領域オフセット位置への変換を行う。同ポリシーはクラステンプレートmapping<Extents>
としてレイアウト・マッピング(layout mapping)特性の問合せインタフェースを定義する。
required_span_size
:変換後にアクセスする可能性のあるメモリ領域の範囲(サイズ)を返す。is_(always_)unique
:一意性。異なる多次元インデクスが同じオフセット位置を指さない。ある要素に対応する多次元インデクスが1つに定まる。is_(always_)exhaustive
:網羅性。取りうる多次元インデクスに対応する要素位置に “隙間” がない。全次元の要素数の乗算結果がrequired_span_size()
に等しい。*8is_(always_)strided
:各次元のずらし幅(stride)とインデクス値からオフセット位置を算出。例:3次元ビュー(L x M x N)インデクスi,j,k
より((i * M) + j) * N + k
。
C++標準ライブラリ提供のレイアウトポリシー特性:
ポリシー | unique | exhaustive | strided |
---|---|---|---|
layout_right |
true |
true |
true |
layout_left |
true |
true |
true |
layout_stride |
true |
(値に依存) | true |
std::layout_stride::mapping<Extents>
のis_exhaustive
はオブジェクトの値(各次元のずらし幅)に依存する。is_always_exhaustive
はfalse
固定。
using Extents = std::extents<int, 2, 3, 4>; using Mapping = std::layout_stride::mapping<Extents>; static_assert(not Mapping::is_always_exhaustive()); std::array<int, 3> strides{12, 1, 3}; Mapping mapper{Extents{}, strides}; assert( mapper.is_exhaustive() ); // mapperは3次元インデクス [i,j,k] のうち // jの次元を連続メモリ配置(strides[1]==1) // strides{12, 4, 1}はstd::layout_right相当 // strides{1, 2, 6}はstd::layout_left相当 std::array<int, 3> sparse_strides{1, 4, 16}; Mapping sparse_mapper{Extents{}, sparse_strides}; assert( not sparse_mapper.is_exhaustive() ); // sparse_mapperは3次元インデクス [i,j,k] のうち // i, jの次元末尾にそれぞれ隙間(padding)が存在 // NG: layout_stride変換は常にuniqueであること std::array<int, 3> bad_strides{1, 1, 1}; Mapping bad_mapper{Extents{}, bad_strides}; // 事前条件(Preconditions)違反により未定義動作
C++2c以降に向けた提案文書P2642では、BLASやLAPACKライブラリがサポートするオーバーアライン(overaligned)・レイアウトに対応したlayout_left_padded
, layout_right_padded
が検討されている。*9
2024-06-04追記:2024年3月会合でP2642R6が採択され、C++2c(C++26)標準ライブラリにlayout_left_padded
, layout_right_padded
が追加される。
関連URL
- P0009R18 MDSPAN
- https://github.com/kokkos/mdspan
- mdspan in C++: A Case Study in the Integration of Performance Portable Features into International Language Standards, arXiv 2020
- Standard Parallelism, CppCon2021
- MDSPAN A Deep Dive Spanning C++, Kokkos & SYCL, CppCon2022
- Why is mdspan::offset_policy needed?, reddit
- [C++]WG21月次提案文書を眺める(2021年05月) - 地面を見下ろす少年の足蹴にされる私
- [C++]WG21月次提案文書を眺める(2022年08月) - 地面を見下ろす少年の足蹴にされる私
- [C++]WG21月次提案文書を眺める(2022年09月) - 地面を見下ろす少年の足蹴にされる私
- https://twitter.com/yohhoy/status/1577475432605429760
*1:std::string_view(C++17)やstd::span(C++20)と同様の設計。
*2:C++2bに向けて採択済み(PDF)P2128R6の利用。std::mdspan のために提案されたC++言語拡張。C++20現在はP1161R3により operator[] 内でのカンマ(,)利用が非推奨(deprecated)とされている。
*3:Mandates: IndexType is a signed or unsigned integer type
*4:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2553r1.html
*5:提案文書P0009R18 §2.5引用:The fundamental reason to allow expressing extents at compile time is performance.
*6:提案文書P0009R18 §2.6引用:"Custom" layouts besides these could include space-filling curves or "tiled" layouts.
*7:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0367r0.pdf
*8:提案初期は contiguous となっていたが、P2604R0にて exhaustive へと変更された。
*9:C++2b時点でも std::layout_stride を用いてBLASやLAPACK互換メモリレイアウトを表現できる。P2642では専用レイアウト型を用意することで最適化促進を目指している。