try ai
科普
编辑
分享
反馈
  • 稀疏矩阵向量乘积:性能、算法与应用

稀疏矩阵向量乘积:性能、算法与应用

SciencePedia玻尔百科
核心要点
  • 稀疏矩阵向量乘积的主要性能瓶颈是不规则的内存访问,这使得该操作是内存受限而非计算受限。
  • 诸如 CSR、ELL 和 HYB 等数据结构格式,以及 RCM 等矩阵重排算法,对于改善数据局部性和计算效率至关重要。
  • 最大化性能需要根据特定硬件特性定制算法,例如 CPU 上的 SIMD 单元和 GPU 上的内存合并访问。
  • SpMV 是许多领域的基石操作,为科学模拟中的迭代求解器以及网络和数据科学中的 PageRank 和 SVD 等关键算法提供动力。

引言

在广阔的科学计算和数据科学领域,世界上许多最复杂的系统——从粒子的量子行为到庞大的互联网——都是由绝大多数元素为空的矩阵来描述的。这些就是稀疏矩阵,而将它们与向量相乘是现存最基本的计算核心之一。虽然这个操作看起来简单,但其高效执行却是一项巨大的挑战,它创造了一个算法、数据结构和硬件架构相互碰撞的战场。本文旨在解决这个核心问题:当大部分数据为零,且内存访问而非计算成为主要瓶颈时,我们如何才能快速执行这种乘法?

为了解开这个谜题,我们将开启一段分为两部分的旅程。在第一章“原理与机制”中,我们将剖析 SpMV 操作,揭示其在“内存墙”中的“阿喀琉斯之踵”,并探索为克服它而设计的精妙数据格式和重排算法。我们还将看到像 CPU 和 GPU 这样的现代硬件如何要求一种深入的协同设计方法来释放真正的性能。随后,在“应用与跨学科联系”中,我们将见证这一个操作的惊人影响力,看它如何驱动从物理模拟、网络分析到数据挖掘和数论等一切,巩固了其作为一种计算通用语言的地位。

原理与机制

为了理解一个大型、大部分为空的矩阵与一个向量相乘所面临的挑战和解决方案,我们现在将深入探讨其底层机制。虽然该操作看似简单,但其高效实现揭示了算法、数据结构和硬件考量之间复杂的相互作用。本节不仅探讨该操作是如何执行的,还探讨了为什么需要各种专门技术来实现高性能。

核心算法及其阿喀琉斯之踵

首先,如果我们的矩阵是稠密的——即充满了数字——那么乘法就很直接。对于矩阵的每一行,你遍历其所有列,将每个矩阵元素与对应的向量元素相乘,然后将结果累加起来。但我们的矩阵是​​稀疏​​的;它们大部分充满了零。存储并乘以所有这些零是对时间和空间的巨大浪费。这就像派一个邮递员走遍全国的每家每户,却只为了给其中少数几家送信一样。

因此,第一个聪明的想法是只存储那些非零的数字。一种非常高效的方法是​​压缩稀疏行(Compressed Sparse Row, CSR)​​格式。想象一下,你把所有非零值逐行地排列成一条长龙。这就是你的 values 数组。对于其中的每一个值,你记下它来自哪一列。这就是你的 col_idx 数组。现在,你怎么知道一行在哪里结束,下一行又从哪里开始呢?你创建第三个数组,一种“目录”,称为 row_ptr。row_ptr[i] 告诉你第 iii 行的数据在 values 和 col_idx 数组中的起始索引。就这么简单!

计算 y=Axy = Axy=Ax 的算法如下:

  1. 对于从 000 到 m−1m-1m−1 的每一行 iii:
  2. 将一个临时和初始化为零。
  3. 使用 row_ptr[i] 和 row_ptr[i+1] 找到该行数据的开始和结束位置。
  4. 遍历该行的非零元素:
  5. 获取值 VkV_kVk​ 及其列索引 JkJ_kJk​。
  6. 从输入向量中获取相应的元素 xJkx_{J_k}xJk​​。
  7. 将它们相乘,Vk⋅xJkV_k \cdot x_{J_k}Vk​⋅xJk​​,并加到临时和中。
  8. 处理完该行的所有非零元素后,将和存储在 yiy_iyi​ 中。

现在,请仔细观察这所产生的内存访问模式。当我们处理矩阵数据——values 和 col_idx 数组时——我们是按顺序流式访问它们的。我们的邮递员只是沿着街道走,按顺序送信。这太棒了!计算机喜欢顺序访问。它们可以预取数据,保持流水线充满并高效运行。这一切的总时间与行数加上非零元素数成正比,即 O(m+nnz)O(m + nnz)O(m+nnz)。

但这里有个陷阱。请看第 6 步:获取 xJkx_{J_k}xJk​​。通常情况下,列索引 JkJ_kJk​ 没有任何特定的顺序。它们到处乱跳!前一刻我们需要 x10x_{10}x10​,下一刻需要 x5000x_{5000}x5000​,然后又是 x243x_{243}x243​。这就是​​不规则​​或​​间接内存访问​​。我们的邮递员在给一户人家送信后,必须瞬间移动到一个完全随机的地址去送下一封。这就是稀疏矩阵向量乘积的阿喀琉斯之踵。它是我们故事中的核心反派。

内存墙的暴政

为什么这个“瞬间移动”的邮递员会是这么大的问题?这是因为现代计算机中存在一个根本性的不平衡:处理器快得惊人,但主存(RAM)相比之下却慢得令人难以置信。这个差距通常被称为​​内存墙​​。处理器可以在一瞬间完成一次乘法,但它可能要花费数百个周期无所事事地等待数据从内存中送达。

为了量化这一点,我们可以定义一个叫做​​计算强度​​(arithmetic intensity)的概念。它是我们执行的浮点运算(FLOPs)次数与我们必须从内存中移动的字节数之比。对于 SpMV,我们为每个非零元素执行两次浮点运算(一次乘法和一次加法)。我们移动的数据包括矩阵值、其列索引以及对应的向量元素。如果我们不小心,可能还需要频繁访问行指针和输出向量。这意味着每移动几个字节,我们只做两次小小的运算。SpMV 的计算强度非常低。

这种低强度意味着计算几乎总是​​内存受限​​的。速度的限制因素不是处理器做数学运算有多快,而是内存系统传输数据有多快。这就像试图用一根花园水管来装满一个游泳池;无论水可以流多快,你都受限于水管的直径。对向量 xxx 的不规则访问就像不得不持续地将水管移动到一个新的、不可预测的水龙头上,这让情况变得更糟。

洗牌的艺术

那么,我们能做什么呢?我们是聪明的工程师和科学家!如果数据组织是问题所在,那么我们就重新组织它!真正的艺术从这里开始。

为局部性而重排

一个稀疏矩阵可以被看作一个图,其中行(或列)是顶点,一个非零项 AijA_{ij}Aij​ 对应于顶点 iii 和顶点 jjj 之间的一条边。对矩阵的行和列进行重排,等同于简单地对图的顶点重新编号。这不会改变图的结构或矩阵的数学性质,但它可以极大地改变非零元素的模式。

想象一下,我们的原始矩阵来自一个简单的一维问题,所以它很好地呈现​​带状​​结构——所有非零元素都聚集在主对角线附近。这太棒了!任何给定行中的列索引 JkJ_kJk​ 都会接近行索引 iii。我们的邮递员只需要在一个小街区内走动。我们需要的向量 xxx 的部分都靠得很近,这意味着它们很可能位于快速的缓存内存中。

现在,如果我们应用一个随机置换会怎么样?这就像把一个城市的街道标志随机打乱一样。一片混乱!非零元素散布在各处。矩阵​​带宽​​爆炸式增长。我们的邮递员现在在整个城市里瞬间移动,缓存几乎变得毫无用处。

但现在是见证奇迹的时刻。我们可以使用巧妙的图算法,如 ​​Reverse Cuthill-McKee (RCM)​​ 算法,来找到一个新的排序。RCM 就像一位英明的城市规划师,他重新给所有房屋编号,以使配送路线再次变得高效。它找到一种能减少矩阵带宽的排序,将非零元素重新聚集到对角线附近。对于许多问题,运行 RCM 重排可以使 SpMV 操作快上几倍,仅仅是通过让内存访问模式对缓存更友好。这是一个绝佳的例子,展示了一个抽象的算法思想如何能够对计算速度产生直接的、物理上的影响。

选择合适的工具:替代格式

重排功能强大,但有时矩阵结构就是太不规则了。对于这些情况,我们可能需要一个完全不同的数据结构。

一个流行的替代方案是 ​​ELLPACK (ELL)​​ 格式。与 CSR 中行长度可变不同,ELL 强制每一行存储相同数量的非零元素,比如 KKK 个。它通过用显式的零来填充较短的行来实现这一点。数据存储在两个 N×KN \times KN×K 的数组中(一个用于值,一个用于列索引)。其巨大的优势在于内存访问变得完全规则和可预测。缺点呢?如果你的矩阵中大多数行有10个非零元,但有一行“巨无霸”有1000个,你就必须设置 K=1000K=1000K=1000。你最终会存储和处理大量填充的零,这是极其低效的。

这引出了高性能计算中的“天下没有免费的午餐”原则。那么,你该怎么办?你可以变得更聪明!你创建一个​​混合(Hybrid, HYB)​​格式。你为 ELL 部分选择一个合理的宽度,比如 K=32K=32K=32。对于非零元少于等于32个的行,你使用高效的 ELL 结构。对于那少数超过数量的“巨无霸”行,你将前32个非零元存储在 ELL 部分,然后将其余的——即“溢出”部分——转储到一个更简单的格式中,比如坐标(Coordinate, COO)格式。这样,你就能两全其美:在普遍情况下获得高性能,同时在罕见的不规则情况下保证正确性而没有疯狂的开销。

与硅的对话

故事还在深入。为了获得极致性能,我们不能只在抽象层面思考算法;我们必须与硅本身进行对话。我们需要理解硬件希望如何工作。

以向量思维:SIMD

现代 CPU 不喜欢一次只对一个数字进行操作。它们拥有​​单指令多数据(Single Instruction, Multiple Data, SIMD)​​单元,就像是数据处理的多车道高速公路。一条指令可以同时加载、相乘或相加一个包含4、8甚至更多数字的向量。要利用这一点,我们的代码循环必须简单且规则。标准 CSR 核心中的变长循环对于这种向量化来说是毒药。

所以,这里有一个奇妙的反直觉想法:有时候,为了更快,我们要做更多的工作。为了让一个有7个非零元的行适应一个向量宽度为4的 SIMD 架构,我们可以假装它有8个非零元。我们处理7个真实的非零元,然后对于第8个“槽位”,我们只需乘以一个零。这种​​填充​​引入了“浪费”的操作,但它使循环结构变得规则,从而允许编译器使用超快的 SIMD 指令。即使我们技术上做了更多的数学运算,整体的加速效果也可能非常显著!。

以线程束思维:GPU 和合并访问

图形处理器(GPU)将这种并行思想推向了极致。它们同时执行数千个线程,这些线程被组织成称为​​线程束(warps)​​的组。GPU 性能的一个关键是​​内存合并访问(memory coalescing)​​。如果一个线程束中的所有32个线程都需要读取数据,并且它们请求的内存地址彼此相邻,GPU 可以在一次高效的内存事务中满足所有32个请求。如果它们的地址是分散的,就会导致32个独立的、缓慢的事务。

这对我们的格式有巨大的影响。标准的“每行一线程” CSR 核心对于合并访问来说非常糟糕,因为一个线程束中的线程处理的是不同的行,它们的数据在内存中到处都是。但是 ELL 格式,凭借其列主序存储,是完美的!一个线程束中的线程处理连续的行,在每一步中,它们访问在内存中完全连续的数据。这是像 ELL 及其变体这样的格式在 GPU 计算中占主导地位的一个主要原因。

以布局思维:SoA vs. AoS

让我们进一步放大,到单个字节的层面。在 CSR 中,每个非零元都有一个值和一个列索引。我们可以将它们存储为​​结构体数组(Array of Structs, AoS)​​,如 [(v0, j0), (v1, j1), ...]。或者,我们可以使用​​数组结构体(Struct of Arrays, SoA)​​,一个数组存放所有值,另一个独立的数组存放所有索引:[v0, v1, ...] 和 [j0, j1, ...]。哪个更好?

对于向量化来说,SoA 是王者。使用 SoA,所有的值都是连续的,所有的索引也都是连续的。一条 SIMD 指令可以用一个简单的命令加载一个包含4个值或8个索引的块。而使用 AoS,数据是交错的。为了获得4个值,处理器必须加载一个更大的混合数据块,然后执行一系列代价高昂的“洗牌”和“置换”指令来只挑出那些值。即使当 SpMV 是内存受限时,AoS 情况下的这种额外指令开销也可能导致可测量的速度下降,通常是10-30%。

这些思想——分块、向量化和负载均衡——被结合在像 ​​CSR5​​ 这样复杂的格式中,它将非零元划分为固定大小的块,以便即使在多核 CPU 上也能创建规则的、可向量化的工作单元。算法与硬件之间的对话是一场持续演变的舞蹈。

机器中的幽灵:一个关于数字的警示故事

我们花了这么多时间讨论速度。更快,更快,再快!但我们的故事还有一个最终的、微妙的转折。事实证明,由于计算机存储数字的方式,你进行计算的顺序可能会改变答案。

计算机使用​​浮点运算​​,这是一种二进制的科学记数法。因为它们的精度有限,每次计算都会被四舍五入。一个惊人的后果是,加法不满足结合律:(a+b)+c(a+b)+c(a+b)+c 并不总是等于 a+(b+c)a+(b+c)a+(b+c)!

考虑一下来自 的这个简单实验。假设我们需要对一个数字列表求和,列表中包含一个大数,比如 1.0,和一万个小数,比如 10−1610^{-16}10−16。

  • 如果我们使用 large_first(大数优先)求和,我们从 1.0 开始。当我们试图加上 10−1610^{-16}10−16 时,它相对于 1.0 实在太小了,以至于结果在四舍五入后仍然只是 1.0。这个小数的贡献完全丢失了,这种现象被称为​​淹没​​。我们加上一万个这样的小数,每一个都被丢失了。最终答案是 1.0。
  • 如果我们使用 small_first(小数优先)求和,我们首先将所有小数加起来。10000×10−16=10−1210000 \times 10^{-16} = 10^{-12}10000×10−16=10−12。这个和被精确计算。然后,我们将这个结果加到 1.0 上。最终答案是 1.0000000000011.0000000000011.000000000001,这是正确的结果。

这种差异不仅仅是性能问题,更是正确性问题。运算顺序,这个我们在数学中理所当然的事情,对数值计算的结果有着真实且有时是戏剧性的影响。在涉及大数相消的极端情况下,误差可能会严重得多,导致完全错误的结果。

至此,我们的旅程暂时告一段落。我们从一个简单的问题——稀疏矩阵与向量相乘——出发,发现了一个充满挑战和巧妙解决方案的丰富世界。我们已经看到,性能是一场对抗内存墙的战斗,这场战斗的武器是数据结构、图算法以及对底层硬件的深刻理解。最后,我们还被提醒,在其最核心之处,我们机器的有限性在数字本身上留下了幽灵般的印记,这对任何计算科学家来说,都是一个美丽而又令人谦卑的事实。

应用与跨学科联系

在我们迄今为止的旅程中,我们已经剖析了稀疏矩阵向量乘积的运作机制。我们已经看到了如何存储这些巨大的、空洞的矩阵,以及如何高效地计算它们对向量的作用。但这一切究竟是为了什么?欣赏一个算法的优雅是一回事,而将它视为解锁整个科学领域秘密的钥匙则是另一回事。现在,我们将看到,这一个操作,这个看似简单的乘法,不仅仅是一个计算上的奇观。它是一种通用语言,一个基本的工具,通过它我们可以质疑、建模和理解世界,从亚原子粒子的舞蹈到人类知识的结构。

模拟的引擎:求解宇宙的方程

许多自然界的基本定律,从量子力学到流体动力学和结构工程,都以微分方程的形式表达。为了在计算机上求解这些方程,我们必须首先将它们离散化——也就是说,将空间和时间切割成精细的网格,并用一个代数方程组来近似连续方程。这个过程几乎总是会产生一个巨大但稀疏的线性系统,形式为 Ax=bA x = bAx=b。矩阵 AAA 代表了我们网格上点与点之间的相互作用——并且由于每个点只与其直接邻居相互作用,该矩阵绝大部分都被零填充。向量 bbb 代表作用在系统上的力或源,而我们寻求的向量 xxx 则是系统的响应——无论是电子的量子波函数、涡轮叶片中的温度分布,还是桥梁在负载下的变形。

对于真正大规模的问题,有着数百万或数十亿的未知数,通过求逆矩阵 AAA 来直接求解这个系统在计算上是不可能的。取而代之,我们转向一类被称为迭代方法的优雅算法。这些方法,如共轭梯度法(Conjugate Gradient, CG)、广义最小残差法(Generalized Minimal Residual, GMRES)或双共轭梯度稳定法(Bi-Conjugate Gradient Stabilized, BiCGSTAB),通过从一个解的初始猜测开始,一步步迭代地改进它,直到它足够接近真实答案。其魔力在于改进步骤。在每次迭代中,核心操作——驱动模拟向前推进的计算引擎——就是稀疏矩阵向量乘积。算法“询问”矩阵:“你如何作用于我们当前的猜测?”然后利用答案来做出更好的猜测。尽管细节各不相同,这些方法都围绕每次迭代一到两次稀疏矩阵向量乘积构建,这使其效率至关重要。

为了理解这一点,我们可以深入了解像 GMRES 这样的方法单次迭代的内部。该过程涉及构建一组特殊的基向量,并在此基内求解一个较小的问题。这包括一系列明确定义的步骤:一次稀疏矩阵向量乘积以观察系统如何演化,一系列向量内积和更新以保持正交性,以及一些标量运算将所有部分联系在一起。虽然每个操作都对成本有贡献,但成本与非零项数量 sss 成正比的稀疏矩阵向量乘积,以及成本与系统规模 nnn 成正比的向量操作,是主导因素。理解这种成本分解是任何希望优化大规模模拟的科学家或工程师的第一步。

这不仅仅是一个抽象的练习。当物理学家想要找到势场中电子的允许能级时,他们求解不含时薛定谔方程。当离散化后,这个量子力学问题变成一个矩阵特征值问题,通常使用同样关键依赖于稀疏矩阵向量乘积的迭代方法来求解。矩阵的大小随着问题的维度和分辨率爆炸性增长——一个简单的 N×NN \times NN×N 二维网格会产生一个有 N2N^2N2 行和列的矩阵,但只有大约 5N25N^25N2 个非零项。计算最低能态的能力直接取决于我们高效执行这些乘法的能力,其计算时间与非零元数量成正比,即 O(N2)\mathcal{O}(N^2)O(N2),而不是稠密矩阵所要求的、不可能实现的 O(N4)\mathcal{O}(N^4)O(N4)。

连接的蓝图:绘制我们的网络世界

稀疏矩阵向量乘积的用途远远超出了物理模拟的范畴。让我们将视角从空间中的网格转移到抽象的连接网络上。考虑万维网、一个社交网络,或细胞内复杂的蛋白质-蛋白质相互作用网络。这些都是图——由边连接的节点——它们可以被一个稀疏的邻接矩阵完美地描述,其中一个非零项 AijA_{ij}Aij​ 表示一个从节点 jjj到节点 iii的连接。

也许这个想法最著名的应用是谷歌的 PageRank 算法,它是其搜索引擎的基础。该算法基于一个绝妙的递归思想:一个网页如果被其他重要网页链接,那么它就是重要的。这个概念可以转化为一个迭代公式,其中“排名”向量在每一步中被更新。这个更新的核心是一个稀疏矩阵向量乘积,Pp(t)P p^{(t)}Pp(t),其中 PPP 是网络图的转移矩阵,p(t)p^{(t)}p(t) 是第 ttt 次迭代时的页面排名向量。这个乘法物理上代表了“排名”或“重要性”在网络链接间的流动。每次迭代都是对一个想象中的网络冲浪者随机点击链接的模拟的一步,而最终的 PageRank 向量描述了在任何给定页面上找到该冲浪者的概率。

当然,要对像万维网这样庞大的网络做这件事,性能就是一切。在这里,我们遇到了更深层次的美:我们实现稀疏矩阵向量乘积的方式与我们提出的问题密切相关。PageRank 迭代最自然地表达为找到矩阵 P⊤P^{\top}P⊤ 的主特征向量。为了计算乘积 P⊤xP^{\top}xP⊤x,更有效的方法不是按行(压缩稀疏行,CSR)而是按列(压缩稀疏列,CSC)来存储矩阵 PPP。这将内存布局与计算访问模式对齐,允许计算机连续地流式处理数据,避免扼杀性能的随机内存跳转。这个选择不是一个任意的技术细节;它是数学公式与计算现实的完美结合。

同样的原则也适用于其他领域。在生物信息学中,分析蛋白质-蛋白质相互作用网络可以揭示不同蛋白质的功能角色。许多量化蛋白质在网络中重要性的中心性度量,也依赖于重复的稀疏矩阵向量乘积。这些生物网络通常表现出“重尾”度分布,意味着少数“枢纽”蛋白质拥有大量的连接,而大多数蛋白质的连接很少。在这样的矩阵上执行并行稀疏矩阵向量乘积时,这种结构可能会造成严重的负载不均衡:被分配处理包含枢纽部分的矩阵的处理器,其工作量远超其他处理器。理解和缓解这些效应是计算生物学中的一个关键挑战,这表明稀疏模式本身的统计结构具有深远的性能影响。

超越显而易见:揭示隐藏结构

到目前为止,我们已经使用稀疏矩阵向量乘积来模拟系统的演化或数量的流动。但它也可以用作一种更微妙的探针,一种在不查看整个数据集的情况下揭示数据中隐藏结构的工具。一个典型的例子是奇异值分解(Singular Value Decomposition, SVD),这是一种强大的矩阵分解技术,应用于从图像压缩到推荐系统和主题建模的各个领域。对于一个大型稀疏数据矩阵 AAA(例如,用户对电影的评分),直接计算 SVD 是不可能的,因为它需要形成像 A⊤AA^{\top}AA⊤A 这样的中间矩阵,这些矩阵可能巨大且稠密。

迭代的 Krylov 子空间方法再次前来解救。像 Lanczos 双对角化这样的算法可以通过执行一系列与 AAA 和 A⊤A^{\top}A⊤ 的稀疏矩阵向量乘积,来找到 AAA 的最显著的奇异值和奇异向量。该算法从矩阵中构建一个小的、压缩的表示,一个双对角矩阵 BkB_kBk​,我们可以从它那里近似原始巨型矩阵 AAA 的奇异值。矩阵 AAA 被视为一个“黑盒”算子。我们不需要知道它的条目;我们只需要知道它如何作用于向量。这使我们能够从海量数据集中提取最有意义的模式,同时保持内存和计算成本的可控性。

然而,最令人惊讶的应用可能来自纯数学的深奥世界:整数分解。像二次筛选法这样的方法,用于寻找巨大数字的质因数,其关键一步涉及在一个巨大的、基于域 F2\mathbb{F}_2F2​(其中 1+1=01+1=01+1=0)的稀疏二元矩阵中找到一个线性依赖关系。直接的方法,高斯消元法,注定会失败。随着消元的进行,矩阵会遭受“填充效应”——原本为零的位置灾难性地变为非零,破坏了稀疏性,导致无法承受的内存和计算需求。解决方案是什么?一种迭代方法,如块 Lanczos 算法,它围绕稀疏矩阵向量乘积构建。通过从不修改矩阵,只通过乘法与其交互,它保留了宝贵的稀疏性,使计算变得可行。这是一个惊人的例子,其中解决问题的关键是不直接查看矩阵,而是仅通过稀疏矩阵向量乘积这个温和的探针与其互动。

前沿:当矩阵甚至不存在时

故事并未在此结束。我们已经看到,重要的是矩阵的作用,而不是其显式表示。这引出了最后一个、具有解放意义的想法:如果我们根本不构建矩阵呢?这就是无矩阵(matrix-free)方法背后的思想,它代表了大规模模拟的前沿。在像拓扑优化或高阶有限元方法这样的复杂问题中,矩阵条目本身就是从更小的、单元局部的片段经过复杂组装过程的结果。

无矩阵方法不是先组装全局矩阵然后再执行稀疏矩阵向量乘积,而是在运行中动态计算矩阵对向量的作用。它通过遍历局部单元片段并直接应用它们的作用,然后将结果相加来实现这一点。这完全绕过了存储全局矩阵的需求,节省了大量的内存和时间。从硬件的角度来看,这可能是一个巨大的胜利。例如,现代 GPU 对计算非常渴求。标准的稀疏矩阵向量乘积几乎总是受内存带宽限制——速度受限于你从内存中流式传输矩阵的速度,而不是处理器的计算速度。它的计算强度(计算与数据移动的比率)非常低。而无矩阵方法,特别是对于高阶离散化,对其读取的每一块数据执行大量的计算。它的计算强度可以非常高,从而能够饱和 GPU 的计算单元,实现远超存储矩阵方法所能期望的性能。当然,这不是一个通用的解决方案。有时,折衷是最好的,比如使用块压缩稀疏行(BSR)格式,它利用问题已知的物理块结构来创建一个比通用方法具有更好缓存性能和更高计算强度的“更智能”的稀疏矩阵向量乘积。

一种通用语言

从量子力学和工程设计,到网络科学和生物信息学,再到数据挖掘和数论,稀疏矩阵向量乘积一次又一次地出现。它是迭代求解器的计算核心,是网络中流动的机制,是探测隐藏数据结构的探针,也是构建更先进的无矩阵概念的基础。它证明了科学和计算中的一个深刻原则:一个系统最重要的东西通常不是它由什么构成,而是它做什么。通过关注这种作用,稀疏矩阵向量乘积为我们提供了一种强大而统一的语言,来探索构成我们世界的那些广阔、互联但又稀疏填充的系统。