MDSPAN:一次横跨 C++、Kokkos 与 SYCL 的深度解析

标题:MDSPAN: A Deep Dive Spanning C++, Kokkos & SYCL

日期:2024/01/27

作者:Nevin Liber

链接:https://www.youtube.com/watch?v=AqibvB5Kl8I

注意:此为 AI 翻译生成 的中文转录稿,详细说明请参阅仓库中的 README 文件。

备注一:mdspan 要到 GCC 15 才能用,版本号要求太高了。

备注二:这个演讲其实也不是我想看的,就顺手贴下。


我是 Nevin Lieber。 我在美国芝加哥地区的阿贡国家实验室(Argonne National Laboratory)工作。我主要负责 C++ 标准委员会的工作,以及 SYCL 和 Kokkos 项目。我在我们的新超级计算机 Aurora 上工作。在委员会中,我是库演进孵化器(Library Evolution Incubator)的副主席,也是美国代表团的副主席。我同时担任行政主席,也是 Khronos SYCL 委员会的成员。基本上,我就是不断自愿承担更多的工作。然后你们把这些头衔放到了网站上。

那么,MDSPAN 是什么? 它是 C++23 中的一种非拥有性(non-owning)多维数组视图。我们将其加入的原因是,我们将其视为一种词汇类型(vocabulary type),既可以在不同领域间通用,也是你希望在接口中使用的东西,就像 string_viewspan 一样。你需要这些东西。

它的样子如下。它有四个模板参数,一堆构造函数和一种索引方式。还有其他一些成员,但并不多。元素类型(Element types)很明显,比如 mdspan of intmdspan of doubleExtents 描述维度。它包含一个类型和每个维度的大小。如果维度是 std::dynamic_extent,那么该维度是在运行时确定的。否则,它是在编译时确定的,这会给你带来更好的优化。

然后我们有一个叫做 dextents 的类。它的作用是,如果你有像 dextents<n, 3> 这样的东西,通过一些模板机制,它会给你 n, dynamic, dynamic, dynamic 这样的 extents。Corentin(注:原文误听为 quarantine)和我真的试图缩短这个名字,但在 C++20 中我们某种程度上失败了,因为我们知道这个特性即将到来。所以,在委员会里,你通常得不到你最想要的东西,你只能得到你能忍受的东西。

接下来是布局策略(layout policy),比如:是行主序(row major)?是列主序(column major)?还是跨步(strided)的?布局策略是一个具体类型。在它内部有一个模板化的映射类,基于 extents 进行模板化。

然后是一种访问数据的方式(accessor)。比如你是否想要一种“花式指针(fancy pointer)”之类的东西,如果你想要 restrict(这目前是非标准的),或者原子访问等等。默认的访问器就像你想象的那样返回一个引用,就像普通数组一样,即 p[i]。如果你想要偏移量,比如 p + i,对于普通指针来说这两者是相同的,但对于你的花式指针来说可能并非如此。

你构造这个东西的方式之一是用一个指针和一个 extents 列表。你只需要用普通的方括号来索引它。我说“终于(finally)”,但这很奇怪,因为高性能计算(HPC)社区只要能得到 mdspan 就已经很高兴了,但我认为从人体工程学角度来看,方括号简直是天赐之物。


它是如何诞生的?

这才是本次演讲的重点内容。这是一个为期八年的任务。你看到了那个类的大小对吧?并不大,对吧?但在 2014 年,那时候我……更年轻,也更天真。

当时,Tiago Macieira 和 Herb Sutter 带来了一个提议进行审查。这个 array_view 会接受一个边界框(bound box),并接受一个索引指向那个框内,比如还需要两个类型。如果你想做一些不仅仅是连续空间的事情,还有一个跨步数组视图(strided array view)。

那时候,我们想:“是的,我们想要那个可变参数方括号语法”,但没人愿意等待语言层面的支持。它只是稍微涉及静态和动态 extents。于是我们在那一届会议上进行了投票。

委员会的投票方式被称为“意向投票(Straw polls)”。除了全体会议(plenary)投票外,它们在技术上是非正式的。我们会分五种情况投票:第一种是强烈赞成(Strongly in Favor),然后是弱赞成(Weakly in Favor),中立(Neutral),弱反对(Weakly Against),强烈反对(Strongly Against)。如果你需要这五组数字,这就是我们要做的。

所以:

  • 我们想要这种方括号里带圆括号的语法吗?是的,当然。

  • 我们想要这种函数调用语法吗?因为我们知道那个支持可变参数。呃,看起来没那么热情。

  • 两种方式都做怎么样?不,没人喜欢那样。

  • 我们应该推迟论文直到有一个仅支持静态 extents 的固定数组视图吗?不,我们不想推迟论文。

  • 应该有迭代器吗?不,我们可以等。

  • 关于布局(layout),比如 layout_left 这种?是的,我们要那个。我们要等它吗?不。

  • 所以我们应该把 array_view 放入数组 TS(Array TS)吗?这是一个响亮的“是”。谢谢,我讲完了。

是的,我希望如此。我们的委员会非常乐观。不仅仅是入境方面。不仅仅像我给自己起的头衔“讽刺的乐观主义者(snarky optimist)”那样,而是我们总是认为我们可以比实际情况更快地把东西加进去。

所以我们有了一个 Array TS。原因是有提议要做栈数组(stack arrays),但没有边界检查,可能会造成栈溢出。所以很多人坚持我们必须有一种安全的方式来访问它。这里有一堆不同种类的东西。你听到 Bjarne 在他的主题演讲中提到了这个。顺便说一下,这些是脚本,因为这些名字一直在变,这一个……是的,这一个又要变了。我会留一张更大的幻灯片。

因为,这个模型正好是 N 个元素,但在运行时确定。如果你注意到,模板参数里没有分配器(allocator)。因为你在构造它时分配,然后它永远存在。结果,委员会的人,尤其是编译器供应商陷入了困境。他们问:如果是嵌入在另一个类型中这怎么工作?如果那个聚合类型在堆上,这块空间是在栈上还是堆上?这怎么运作?他们不知道如何解决这个问题。所以委员会做了什么?嘿,把它写进文档里,我们稍后再弄清楚。

所以 Array TS 有点无聊。Issaquah(注:原文误听为 Issaclan)会议来了,问题变成了:我们还有什么数组相关的东西可以放入这个技术规范(TS)?array_view 是显而易见的选择。还有 array_ref,这有点像 span。还有 string_ref,后来根据 Google 的 Jeffrey Yasskin 的提议改名为 string_view

人们添加的一件事是:我们是否要做支持多维的 std::array?我们是否添加更多模板参数?这会破坏 ABI,但在那时候,我们大多数人并没有真正考虑这个问题。还有我们得到的像:能做 make_array 吗?某种程度上得到了。能扩展 shared_ptr 来处理数组吗?是的,那个并没有持续太久。在 2016 年的 Jacksonville 会议上,我们投票否决了它。所以它持续了三年。

我们确实学到了很多。

回到 2014 年。嘿,我们再来一轮吧?在 Rapperswil(注:原文误听为 Rappers’ Royal)得到了一些措辞。还有一些其他变化。我们应该把它送到库基础 TS 第 2 版(Library Fundamentals TS v2)吗?我想是的。哦,我们也达成了共识。通常大概是 2 比 1,这三个加起来对比那三个。是的,那是共识。

发生了一些小的修正。我们应该把它放在正式动议页面上吗?这意味着整个委员会将投票决定它是否进入。在会议结束后,比如在 Urbana 会议上,有一堆意见,比如“哪怕我们应该添加可变参数操作符,函数调用操作符,这样我们可以用那种方式查找”。我们仍然有固定的 array_view。进行了一些投票。好的,所以对于一维的情况,我们现在可以同时拥有两种语法。

大约在那个时候,Sandia 实验室致力于 Kokkos 项目的 Carter Edwards,他不喜欢这个提案。所以他写了一篇论文说:“你知道,这对性能有重大影响,包括 SIMD。布局性能,平铺(tiling)和填充(padding)应该是可以做到的。我们需要跨步(strided)作为一种布局选项。”这都是基于 Kokkos 在生产环境中所做的事情。你知道,“应该能够混合编译时和运行时维度。理论上你可以替换索引和边界,因为它们实际上只是数组,所以当然可以。而且它并没有真正说明它如何与内存管理交互。”

所以建议是 不要 将此放入 Array TS。第一年!耶!

得了吧!我们开了一次仅限库的会议。那次会议的一半时间是 ASIO 网络提案的开始。我们甚至没有一篇论文。我们在审查 Git 上的一个 SHA 哈希。小组的一半人分出去审查那个,我在另一半人里。我喜欢说,我们要比网络组审查多得多的论文。嘿!我们完成了,而且状态良好,可以在 Lenexa 会议上推进。再次,我们很乐观。

到了第六版修订,我们在 Cologne(注:原文误听为 clone)提出的变更被加入了。然后进行了全体讨论。一些 Issaquah 的反馈从未被纳入。还有一些在 Rapperswil 提出的问题没有被 Carter 解决,就是 Carter 指出的那些。所以 LWG(库工作组)支持 Carter 所说的方向。于是我们实际上进行了投票。在全体会议上,大概就是赞成、反对、弃权票。那 不是 能够进入标准或技术规范或任何我们发布的官方文档的共识。所以它被撤回了。

我们来简要地看一个侧面。因为我同时也致力于 SYCL 和 Kokkos。人们问我的一个问题是,区别是什么?它们都是为了性能可移植性。所以我们在超级计算机上获得高性能计算。我们需要一种方式,让用户不必每次有新的超级计算机时都重写代码。因为通常合同会给不同的公司。所以是不同的架构。我们需要能够相当简单地移植代码。

SYCL 是厂商支持的工具链。我们实验室不想涉足成为编译器供应商的业务,尤其是跨多种架构。在这方面允许语言扩展。它是隐式数据移动与显式数据移动的对比。但它来源于 OpenCL,那是图形学的东西。所以它某种程度上有三维的根基和固定的布局(行主序)。我目前正致力于扩展这一点。不仅仅是我,还有很多人。

Kokkos 只是一个库。我们利用任何厂商工具链,我们有针对 CUDA、HIP、SYCL 等一堆后端。我们支持更新维度。我们可以做得更多。我的意思是,当代码被写出来时,人们还没有使用可变参数模板,但我们可以很容易地把它加进去。只是没有任何需求。而且优化更多维度变得非常困难。如果我们必须手动优化真的很困难。

这就是 Kokkos View 接口的样子。稍后我会讲它是如何实现的,因为它不像这样。 DataType,这和 element type 是一样的。这里有……这是你可以混合运行时和编译时维度的地方。有一种奇怪的简洁语法。如果你写 double**,那个星号意味着(就像正则表达式匹配任何东西一样)这是一个带有两个运行时维度的 double。记住,这里面的任何东西都必须是有效的 C++。对吧?所以你可以做 const double, ***, [3],但你不能把这些和星号混在一起。直到今天,Kokkos 还在使用这种简洁语法。如果你把它变成 const,那么它就是只读数据。

我们索引它,因为我们需要可变参数,我们使用函数调用操作符来索引。如果是一维的,当然可以用方括号索引。它有时是拥有的(owning),有时它管理它拥有的数据。有时它不管理。如果你传递它,它是引用计数的,但如果我们将它从 CPU 传递到 GPU,我们不能做引用计数,因为你只能进行内存拷贝(memcopy)。更好的方式是,在实践中它就是能工作。我们通常尽量避免未定义行为(Undefined Behavior),但这……

接下来是布局类型,比如 Left、Right。还有内存空间。是在 CPU、GPU 上?是私有内存等等。然后是 Traits。Traits 有点像后来变成 Accessor Policy 的东西。如果是托管空间或非托管空间就放在那里。Atomic,你可以在那里做像 restrict 这样的事情。但它的实现不是这样的。它被实现为属性的可变参数列表,我们会遍历该列表。

回到 Lenexa。

Christian 是 Kokkos 的项目负责人。所以他们有点像有 shared_arrayweak_arrayweak_array 就像 array_view。某种程度上分散了拥有和非拥有,有点像 shared_ptrweak_ptr 那种模式。我相信论文中有一些强行与该模式进行的类比。能够自定义 size_type 对于 GPU 上的性能很重要。作为其中一部分,也许我们应该(既然有委员会,我们也可以做语言更改),所以也许我们应该允许空的方括号(optional)。以及那种简洁的模板语法。

但这,这是一个语言更改,对吧?所以通过委员会更难。而且我们想允许方括号。委员会的人开始对这个想法感兴趣了。

来自微软的 Neil Macintosh(注:原文误听为 Real Macintosh)带来了一个关于边界安全视图(Bound Safe Views)的提案。也就是他原来的 array_view。这是测试时间:那是 std::byte 第一次出现的地方。我们要么最终像这样标准化它,并说你可以从另一种类型转换过来,所以你可以做 charunsigned charstd::byte

这个提案还有一点是,当用无效值构造 array_view 时,它会终止(terminate)。这是他们的安全模型。而不是未定义行为。委员会至少在当时并不太喜欢那种直接终止的东西。这是一个很大的提案。有点像有一个代表点的索引类型。有边界迭代器。你可以遍历。是的。我想知道这是否普遍。比如,在那些日子里。我的意思是,这是否是一种默认的方式……我们几乎总是转向未定义行为。唯一一次没有这样做,我想是 Dave Abrahams 最初的 noexcept 提案。如果不通过 noexcept 子句抛出异常那是未定义行为。人们可能争论说,出于安全原因,它应该只是终止。是的,我是说这有道理,对吧?因为你没别的可做了。Contracts(合约)现在正在纠结这个问题,对吧?那是他们的大问题之一。不,我们没有,是的,我们看看委员会的另一面,当我们有一些书面政策说“这是我们应该做的”时,工作效果会更好。比如你可能听说过 Lakos Rule,它说如果你有一个宽合约(没有前置条件),并且它不抛出异常,就在标准库中标记为 noexcept。这是标准库中的新合约,只有是的,同样如果新合约厂商可以添加它,你可以争论将其添加到你的库演进操作中。比如,我对解引用智能指针标记为 noexcept,因为有人争论说为了性能我们需要那个。我没说那个论点是正确的,我只是说有人为此争论。但如果有那条规则,会议进程会快得多,因为你只需指向规则,无论你喜欢还是讨厌它,它都能让你取得更多进展。

回到这个。所以是的,你有一个边界迭代器,然后是静态边界和跨步边界,是的,还没完。还有针对维度的东西,因为你需要一种方式来表示它。然后有点像 array_view,如果你想改变 size type,如果你传入这个而不是 value type,它会从中取出 size type 而不是默认的 size_t,并将其用于你的迭代。最后,array_view。我还记得我第一次看到这个时的想法,我就坐在那里看这所有的演示,我就想:这太复杂了,我不懂这些东西,这全是类。所有懂它的人都搞明白了。我把它写进论文,那正好是我开始在阿贡工作的三年前。如果那时我就知道就好了。

然后 Span 的论文终于出现了。只是把一维版本和多维版本分开。由于之前的论文,我不再关注它,因为那太复杂了。所以这在很大程度上是我的错。如果你认为这是个好主意,那不用客气。如果你认为是个坏主意,我很抱歉。因为我带头冲锋。我的朋友 Rob Douglas,当时正在换工作,那一刻技术上还不是委员会成员,他对我说:“嘿,伙计,你需要反对这个。”因为那个查询(query)到处都在强转类型。我说:“你是对的。”所以我反对它。我反对了一些知名的 C++ 大人物,比如在这里做主题演讲的那个人。是的,你们还好,但是你知道,有点……当你在委员会上争论某事时,它是对抗性的,对吧?这很可怕。这都是在我进入 HPC 领域之前。所以,你知道,我认为区别在于我的性能是一个提醒,这就像每个委员会成员都说的,你知道,“我想要个特性”。所以是的,当然,那是在噪音中,别担心。如果我不想要,如果你不想要一个特性,你会说“我连一个周期都付不起”,你知道,“你不能那样做”。你知道,我检查了 vectorstring_view,因为你知道,那会花费你一个周期,少数几次它实际上没有被优化掉。

但对我来说,我必须反对它的原因是,它打破了与标准库其余部分的互操作性。因为标准库的其余部分使用 size_t,而让人们进行类型转换(casting)——类型转换容易出错,对吧?这是一个多对一的操作,人们总是搞错。你是把有符号转无符号,还是把无符号转有符号?你可以找到各种理由,我也搞不清哪个更糟。但我最终解决了什么问题?

所以论文系统实际上变了。这就是为什么我们得到了这么小的编号。过去每篇论文,每篇提案都是通过 ISO 发布的。那就是他们的网站,让他们发布东西有点不祥。我们有了一个新的论文系统,我们刚刚做的,我们可以做修订版。其中的第九个提案将是 mdspan。我拉了一堆人上船,从金融界到这个叫 Bryce 的孩子,你知道,他在劳伦斯伯克利实验室,你可能听说过他们。

再次强调,array_view 的核心问题在于它没有实现零开销抽象。我们需要另一个库,你知道,你想做一些尽可能接近硬件的事情,而我们需要另一个库来做那个。所以这一个有更通用的布局和填充,你可以做 constexpr extents 和 strides,以及针对其他属性的可扩展性。因为它看起来真的很像 Kokkos 的那个,除了是小写 v 而不是大写 V。但有一个问题是拥有属性,这些是不同的类型,它们代表完全相同的东西。对吧?它们是等价类型但截然不同。这意味着如果我想要一个接受某种东西的接口,我该怎么办?实际上我只有三种可能性:要么我对每一个可能的类型做单独的重载;如果我同意,好吧,一个小的运行时转换成本,有时会被优化掉;或者你留在模板世界里。这些都不理想。

所以,我们想要任何零长度 extents 吗?我们通常支持这类事情,只是数学上效果更好。我们不想特殊处理事物,即使它们看起来不太有用。哦,实现可能会特殊处理,比如 std::array 可以有一个 something, 0 的数组,这是一个特殊情况实现,但这让很多算法能正确工作。所以我们想要零长度 extents 吗?当然。想要属性列表吗?是的,听起来不错。想要边界检查吗?是的,我们想要边界检查,我们想要安全性。我们开始尝试命名它。我讨厌委员会上的命名讨论。如果是错误怎么办?因为 Contracts(合约)开始出现了。John Lakos 有一个关于合约的完整的宏提案,然后我们被说服语言提案更好。

我们需要做我的一页幻灯片侧栏关于 Contracts。我们现在只能有一个,说“这里有龙(here be dragons)”,别去那里,那是未定义行为。其他一切都是定义行为。这是我的观点。你会听到像“软未定义行为(soft undefined behavior)”和“库未定义行为(library undefined behavior)”这样的术语。我不同意那种观点。因为如果你正在定义事物,根据定义你就是在定义它。它有一些东西。但 Contracts 将给我们更多的旋钮,希望能赶上 C++26。

回到正题。所以 view 现在在名称方面已经变成了 array_ref。我们就 Signed vs Unsigned 的 size type 以及 Oulu(注:原文误听为 pre-ulu)又有了一次巨大的辩论。那时库演进小组想要一篇只有措辞的论文,然后一篇有基本原理的论文。他们想要把这些分开。所以 Kokkos 团队就把它们分开了。当然我们会那样做。我们将重新声明语法,即那个语言特性被移到了它自己的论文中,这样它可以通过语言演进小组。动机示例移到了它自己的论文中。

在 R3 版本中,有一件事说不令人满意的 extents 机制就是传递所有 extents 的模板。当然,那是我们选择的一个。这也是第一次它被重命名为 mdspan。还没有最终确定,但是这是提案的一部分。然后是像可以用 span 来索引的 span,而不仅仅是方括号、函数调用 (1, 5, ...),是的,这有道理,你应该能够做到。而且你应该能够用一维 mdspan 进行索引。

嘿,我们要发布全票通过了!啊,是的,Library Fundamentals TS v3。它从未包含 mdspan。直到去年年底,人们说我们需要把这个东西发出去。它已经在这里坐了好几年了。所以我们应该发布它吗?我们应该永远不发布它吗?因为就像,让我们把那个厚的……库基础 TS 版本 1 里都有一堆东西,更不用说版本……我们什么时候把这些放进标准里?我们什么时候会有足够的……是的,永远不会,或者有人应该提议移动它们。所以,不要发布那个,评估它是否只放入标准。要么我们放进去,要么我们扔掉。然后我就想,让我们把它扔掉吧。每个特性的价值……我被拉上船是因为我们摆脱了最令人震惊的……我们在委员会里经常对自己撒谎,因为我们是乐观主义者。但这正是 TS 中的那句话让我反对的,就像我在反对发布一样,即使我们一直在说“我们打算发布”,因为那时人们争论“你们打算发布,那我们就得做另一个版本”。就像我们一直说那个,然后我们就一直发布它。所以那个被撤掉了。它正在被发布的路上。我是行政主席,所以我负责论文系统。所以 ISO 每个星期六都给我发邮件说“嘿,因为这是这一堆 N 文档里的一个小号,ISO 已经发布了,你还没有发布这个”。我不知道怎么停止这个,我只能永远收到邮件。ISO 确实提供了一些好处,但是是的,有时候有些挫折。

总之在 Jacksonville。比如我们真的不需要通过 span 来索引,因为实际上没人在那样做。如果你没有被审查……然后 Daisy 提出了一个概括整个属性机制的提案,并得到了一些支持。看起来像是能够自定义基本的 mdspan。所以现在将会有某种 basic_mdspan,而 mdspan 将是通用版本。

R6 来了。所以这里有 basic_mdspan,然后你就有了 mdspan 的模板别名。我们在那里有一些措辞问题。

R7。我们如何引用 spanspan 还没有发布。引用一个将要在未来 TS 中的东西真的很难,那是当时对 span 的计划。根据实现做了一些更新。

2019 年。这是第 5 年?我们快到了。我的行踪……一周后我去夏威夷,我们在研究除草和措辞。在那时,Corentin 有一个提议,我们知道我们想要可变参数方括号,所以我们必须弃用内部的逗号,否则它就是逗号操作符。顺便说一下,这是最后一个学期。这只是 X, Y,它说评估 X,扔掉它,并返回 Y 做的事。所以那个被弃用了。

Prague(布拉格会议)。 我们的东道主。C++20 完成了。span 很热门,它进去了。mdspan 没有。会议结束后的周六,我接受了库演进副主席的职位,因为这没有多多少工作。我已经做这个好几年了,能发生什么事呢?对吧?

是的,疫情。

所以我现在是这篇论文的作者之一,我们就只开电话会议。对吧?所以 C++23 就是全电话会议版本,除了最后一点。

这里有个侧栏,关于我们如何复制对象。对吧?在 C++ 中,我们通常调用拷贝构造函数。但这需要运行代码。如果你需要在 CPU 和 GPU 之间传输,代码在哪里运行?它能访问两边的内存吗?但他们提供了 memcopy 的方法。所以我们可以复制……什么是对象表示?我们可以复制位(bits)。我们在 HPC 领域,“Trivially Copyable(平凡可复制)”是类型的代理,我们可以直接复制过去。所以这很重要。直到我加入 HPC 我才意识到这有多重要。所以会有提案说我们想让它变成平凡可复制的,我通常只是说“是”,但不怎么热情。就像“是的,你知道,如果有你可以肯定为什么不呢”,但现在我真的在乎,并试图教育委员会成员为什么这对我们很重要。

我们决定,嘿,与其放在这个不发布的库基础 TS 里,不如把它放进标准里,新的国际标准。是的,那仍然是相当强的共识。仍然对这种语法、简洁语法抱有希望。大概这个时候,Daisy Holman 提出……基本上提出了可变参数方括号,并且它应该能够接受零个或更多参数,因为你知道,我们喜欢从零开始的东西。没有针对零长度的特殊情况。我们有点搞砸了,TS 定义会匹配 operator()(函数操作符)的定义,因为有一些更改允许那是静态成员函数。像每个人都说我们应该做这个,但实际上没人提议。所以我提议了,不是为了 26。我不打算做一个国家机构评论。我和几个人谈过。Gasper 博士说:“是的,我们应该为 23 做这个。”我和 Michael Wong 谈过:“是的,我们应该做这个。”所以我们有三个国家机构评论。在国家机构评论期间很少有特性进去。这更多被认为是一个错误修复版本,而不是真正的特性提案。然后 Corentin 说:“嘿,你知道,Nevin 应该来拜访。”真的,这是原话。有时候如果你不太有压力的话,这还是个有趣的委员会。

R13,我们快到了。我们有了 dextents 类型别名。我们摆脱了旧的 mdspan。我们终于收敛了。我们不需要它。然后我们有了推导向导(Deduction Guides),我的专家推动了这个以获得语言支持。我就用一页幻灯片讲讲,他可以告诉我为什么我错了,但这……所以你可以用没有尖括号的语法调用东西并构造东西,它只是一个映射。这实际上是一组与类内部构造函数不同的重载。就像如果你知道 pair,这只会调用推导向导结构。对我们来说,比如……再一次,这是天赐之物。这使得它可用。否则有一堆大多数人并不太关心的模板参数。即使它们是默认的,如果你必须更改它们,那就很痛苦。但这是一种权衡。有些时候你仍然需要知道确切的模板参数。对吧?如果你在调试它,你必须知道它实际上映射到什么类型。但总的来说,这是语言的一个非常强大、非常好的特性。

这里是我们为 mdspan 准备的显式推导向导。如果你写这个,它会经过这里说“嘿,我想这是匹配的那个”。你知道,如果你看它,这是对指针的左值引用的 mdspan。所以移除引用,它是指针吗?是的它是。移除指针,所以是 int。所以我们将把那个传入 mdspan。所以我们得到了“好的,我们将映射到 intmdspansize_textents”,因为这是一个零维的。好的。然后有一些默认参数,让我们把那些加上。我在扮演编译器。最后,这就是被调用的构造函数。这真的很酷。

R14。嘿,好吧,我们要把这个放进标准吗?这是这次投票 P3 的意思。因为我们是一个新特性。全票通过。那是 2021 年 11 月。然后是一堆措辞审查。因为这里面有很多措辞。很多措辞审查。快到我们必须发布的时候了。

所以我们决定我们需要从提案中把 sub_mdspan 拉出来。我们可以,我们知道以后可以加进去。我们 认为 以后可以加进去。我们永远无法确定。所以我们把它拉出来,他们也没足够的时间在这里审查。

而且,我想是 Bryce,我记不清是谁做的这个,但是“让我们把 size_type 加进 extents 里,这样如果人们在乎的话我们终于可以传递它了”。因为我们确实在乎。但这摆脱了我们在 span 上有的整个争论。因为你可以这样做。当我以前做这个演讲时,第一个问我问题的人是 Bjarne,他问了那个问题:“我可以有一个以 int 为……的 span,我可以有一个以 int 为……的 mdspan 吗?”你说:“是的,你可以。”他非常高兴。所以我们提议,我们说我们将最初限制为无符号类型。我们说:“不,别麻烦了。”我们说:“酷,因为那甚至更好。”(意思是支持有符号更好)所以发送这个,这让它成了。

我们有点忘了 size_type 和标准的 span……我没有忘记,我在那次电话会议大约一小时后意识到了。就像,我不想让这个提案脱轨。我想让这个人进去。所以然后我提议我们最终修复这个问题,做一个索引类型。我们可以两者都做,除了 size_type,你知道,size 应该返回 size_type。所以我们必须在里面做 make_unsigned

然后,你知道。所以我们只是继续投票。对吧?就像,人们不想要它,因为这是延迟更改。是的。我们在内部,我想是 accessor,我们有与 data_handle_type 相关的指针,因为花式指针并不真的是指针。它只是有时看起来像一个。

当然,这也是我不关心命名讨论的原因。虽然名字很重要,但我们在委员会上做这些的方式大多数时候都很糟糕。我们只是有点像“哦,我们不喜欢那个名字,让我们现在想出一个名字列表,在两分钟内投票表决,这就是我们未来一百年要用的名字”。这不是……我不喜欢那个过程。我一直在反对它,但这是一场永无止境的战斗。

我们想要分开。此时我们不会为任何事情拿 mdspan 冒险。有人发现 empty 缺失了,所以我们也必须作为单独的论文来处理。我们没有关于 mdspan 的“不”列表。我们做的任何更改,我们会问“你在做任何更改吗?也许我们应该把它留到 C++26,当你确定你想要什么的时候”。

所以在去年的 7 月 25 日。将 R18 中的更改应用到工作草案中。全票通过。我们庆祝了。

好的,我在那里,但那不是……那是布拉格,那是我们为 C++20 的庆祝。

这里是我们的庆祝。

真实的截图。我不是编的。但是是的,我们成功了。你知道,在当时,就像在八月,我在 eel.is C++ draft 上看了看,它在那里。Kokkos 我们有一个参考实现,人们正在使用。Nvidia,我最近没看,但我们在拿它……拿我们的表示放进去。所以我们用了八年时间把它弄进去了。


未来

那么我们未来要做什么?我也在 SYCL 委员会,我们正在试图弄清楚我们如何……我们想在 SYCL 中使用标准里的东西。所以我们怎么使用这个?有点像……有点像两种内存模型。有 accessors,它做一个关于数据依赖性如何工作的隐式图。然后是统一共享内存(Unified Shared Memory)。我们可以用这个来,你知道,也许统一这两个,摆脱那三个维度。

所以 SYCL accessor 只是一个拥有内存的缓冲区的非拥有视图。它有像访问模式和相关的主机任务。我们认为 mdspan 对此有改进。你知道,某种程度上能够做矩形拷贝,我们应该能够做所有这些目前在 SYCL 中无法表达的事情。

目前的状况确实是,我们不得不取消我们的……由于一些公司的旅行限制,我们不得不取消上个月的会议。所以我们需要一次面对面的会议来决定我们要往哪个方向走,或者一次长时间的电话会议来决定。其中一件事是,我们去 C++23 吗?因为方括号需要那个。再次,我相信在人体工程学上,你知道,那很重要。我的意思是 Kokkos 的实现有一个回退到函数调用操作符的机制。

然后我们在 Kokkos 中做的事情。所以我们有点像有五篇相关的论文正在被瞄准。实际上我们正在重构我们的 Kokkos View 以在内部使用 mdspan

我们要了五篇论文:mdarraysub_mdspan,填充布局(padded layouts),原子引用(atomic refs)和原子访问器(atomic accessors)。

mdarray 有点像拥有版本,它是一个适配器。所以空间实际上被底层的某种东西拥有。它可以是数组,可以是向量,小向量(small vector),任何你想要或需要的。最初的提案有一个容器策略。人们并不真正喜欢那个。

为什么用适配器?这样我们可以使用其他东西,特别是在 GPU 上。我们在每个平台上都不能使用 std::array,因为某些平台上的成员函数必须被标记为“我可以在设备上运行这个”。所以我们必须有我们自己的版本说你可以在设备上运行这个。这很令人沮丧,但是是的,这是现实世界。我的意思是标准不知道 GPU。对吧?标准甚至不知道进程。对吧?那些线程和核心。那是一种可能性,但我希望在那通过我们的委员会之前很久就退休了。

所以是的,所以我们现在你应该只是传递你想要的容器。如果是像 extents,我们可以默认为数组,否则使用向量。我想我们只是说整个默认就是向量。人们喜欢适配器设计。这是一致的。你知道,我们的三个主要东西更符合 mdspan 发布的内容。然后只是细微的东西。

还有这个版本,我是这个版本的一员,但我有一些家庭事务,所以我必须阅读论文以确保它实际上修复了移动后状态(move-from state)的问题。提议像简化那里的一堆构造函数。我可以……我摆脱了大约三分之二的构造函数。还有更多功能。就像 flat_map,你知道,有很多构造函数并使用可变参数,你可以只是转发一堆东西,事情工作得好得多。早期论文中的推导向导有一些问题,我修复了那些。

既然这个在这个会议上被提了几次。对吧?(你知道,移动后的问题是什么?我们希望我们的类型有强不变量。所以如果我在包装某样东西并且我有一个不变量要保持,我必须知道移动后的状态。标准说对于大多数类型来说,“它处于有效但未指定的状态”。这对于可组合性来说是不够的。更糟糕的是,如果是全静态 extents,它拥有的不变量之一是空间总是可访问的。所以如果下面有一个向量,你做了移动,移动后的向量是空的,我也不能调整它的大小。我的意思是,我可以尝试,如果我知道它是一个向量,我可以尝试调整它的大小,但这可能会抛出异常,那我该怎么办?所以这是一个可能失败的选项。

array 就没有问题,因为元素可以是移动后的状态,但这并不破坏空数组的不变量。vector::clear 并不意味着(对吧?所以 flat_map 可以通过像 vector::clear 这样的事情来维持它们的不变量)。优先级队列(priority queue)当他们添加移动时没人打扰。它没说任何东西。我很确定没有任何实现真的尝试它。对吧?因为优先级队列必须处于堆顺序。没有保证你移动后的东西……适应这个保留在堆中是要么清空要么保留在堆顺序。对吧?没有要求。

我试图为 flat_mapmulti_map 修复这个问题。我被拒绝了,但要明确的是……是的,对我来说,我希望那是明确的。clear 可以为 flat_map 解决这个问题。

呃,sub_mdspan 我最近没有关注,但我希望这仍然正确。这只是被拉出来的部分。你可以有一个索引类型和全范围的索引。有点像,你知道,它也有一个跨步索引范围(stride index range),你知道,有一个偏移量,你在哪里开始,你想在 extent 内哪里开始,这不是结束,只是子范围的结束和跨步。这有一些自定义点。一些 mdspan 映射函数和 sub_mdspan 偏移函数。所以它正在通过。它现在正在经过 LWG 措辞审查。我不记得 LEWG 是否已经批准了它。我不记得了。

另一个提议是模式布局(pattern layouts)。如果你想要有一些填充的存储。所以我得到这里是连续的,但下一个必须像,你知道,下一条缓存线或者下一个……

我要谈的下一个是绑定到内存顺序和原子访问器的原子引用(atomic refs)。所以我们提出了三个有界原子类。是的,明白了。所以我们提出了三个原子……较少的宽松(relaxed),获取释放(acquire-release)和顺序一致(sequentially consistent)。获取释放很奇怪,有点奇怪,因为它的加载是通过获取完成的,存储是通过释放完成的,而不是通过内存顺序。所以这经过了 SG1,也就是并发并行小组,他们想要有界的内存顺序这三个,并且他们想说你不能改变。所以在原子引用中,你必须指定顺序,就像它的参数一样。我们也可以说你可以用参数覆盖内存顺序。SG1 说“不,不,我们不想让人们那样做”。如果他们需要那个功能,他们可以做别的事情。我们只是没看到那有什么需求。还有一个未解决的问题。这个问题是我的错。所以我将其指定为仅用于说明(exposition only)的类型,你做模板别名,现在人们说我们应该公开那个,并使其成为一个完全成熟的模板类型,人们可以使用。所以这在 LEWG 中仍然是一个未解决的问题。希望下周在 Varna 我们会解决它。但这真的是为了我们可以得到那个。对吧?你有一个原子访问器,你所做的,那个和默认访问器之间唯一的区别是我们把引用从 T& 改成了 atomic_ref。访问变成了宽松的原子引用。宽松使用原子引用宽松。然后你把它们拉进来,它们就能工作了。我的意思是,如果你看,这显示了……的力量,你知道,我们认为我们把 mdspan 弄对了。主要是对的,我想。但是是的,我们不必说,你知道,百分之百的任何事情。

所以是的,我们要提一下,这花了我们八年时间。这不是委员会设计(designed by committee),这是委员会共识(consensus by committee)。委员会投票就像,你知道,人们有不同的……不是人们错了,是我们基于我们所做的工作有不同的观点。所以这是权衡,我们试图找出正确的权衡是什么。而且这也是基于 Kokkos 多年的实际部署经验。我们从很多不同的地方得到了支持。你知道,那有帮助。对吧?你不是试图偷偷把什么东西塞进去,你不是试图,你知道,碾压某些东西。你是在尽力解决每个人的担忧。我认为这是 23 中的旗舰库之一。我想是的。你知道,我已经去了……这就是雇用我和 Kokkos……

我一般的……我必须在各处写这个,我们如何获得资助。就是这样。谢谢。有什么问题吗?

问答环节

  • 问题: 你能使用负索引吗?

    • 回答: 是的,你可以。如果你……如果你假设你有答案,你知道,你在混合类型。是的。

  • 问题: 我记得好像不支持类似压缩的东西,我的意思是 vector<bool>

    • 回答: 嗯,你是说 mdarray?我的意思是 mdspan 并不获取……mdspan 只是获取指向内存的指针。是的,对吧?mdarray 不会支持压缩的 vector<bool>vector<bool> 背后不是连续空间。对吧?你得到的是代理(proxies)。你不知道空间是什么。它不是连续空间。所以不,mdspan 不会支持它。

  • 问题: 我的意思是,你喜欢那个吗?在委员会里那有点像是个沉重的负担(albatross)吧?

    • 回答: 我会给出我的侧栏评论。我不得不提交一个针对 vector<bool> 的错误报告,当时我正在处理那个……我们将一堆代码从 push_back 改为 emplace_back。结果发现没人把 emplace_back 放进 vector<bool> 里。然后问题是它应该是可变参数的吗?Alisdair Meredith 告诉我“是的,原因如下”。给我一个理由。所以我必须提议那个。具有讽刺意味的是,我们唯一使用它的地方是我们的测试框架。我们在生产代码的任何地方使用 vector<bool> 吗?没有。所以是的,为什么你会想要 vector<bool>,因为它甚至没有被规范保证压缩?对吧?不,我把数字写在这里了,我们可以在那个方面变得很好。好的。

  • 问题: 是的,嗯,社区希望看到一个更好的替代品,这样我们可以弃用 vector<bool>

    • 回答: 我有一个秘密计划。我的秘密计划是 small_vector,它不会做那种特化,然后弃用除了 vector<bool> 之外的所有东西,因为在委员会里弃用 vector<bool> 太难了。那只是我的秘密愚人节计划。不,这是个好问题。我的意思是,这对我们在委员会里是很好的数据。实际上。

还有更多问题吗?

非常感谢。