矩阵的行主要与列主要布局

计算科学 矩阵 正则
2021-12-07 22:27:21

在对密集矩阵计算进行编程时,是否有任何理由选择行优先布局而不是列优先布局?

我知道,根据所选矩阵的布局,我们需要编写适当的代码来有效地使用高速缓存来提高速度。

以行为主的布局似乎更自然、更简单(至少对我而言)。但是像 LAPACK 这样的用 Fortran 编写的主要库使用列主布局,所以做出这个选择肯定是有原因的。

4个回答

列主要布局是 Fortran 使用的方案,这就是它在 LAPACK 和其他库中使用的原因。

一般来说,在内存带宽使用和缓存性能方面,按照数组元素在内存中的布局顺序访问数组元素会更有效。根据矩阵的存储方式,您需要选择利用这一点的算法。

内部存储器 列主要格式的内部存储

在不考虑任何现有软件的情况下,从代码的角度来看,没有理由更喜欢列专业而不是行专业。但是,大多数数学文献都是以将向量分组为矩阵的方式编写的,方法是将它们存储为列而不是行。例如,当您编写完整的特征值方程时,AX=XΛX矩阵包含列中写出的所有特征向量。您永远不会真正看到它以其他方式编写(尽管我听说统计人员喜欢行向量)。因此,最早的软件很自然地采用列主要格式,因此如果您有一个矩阵是一组向量,那么任何单个向量的存储都是连续的。因此,我想这个传统才刚刚发扬光大,如果你想和老的 Fortran 交互,你想使用列专业。所以几乎所有高效的数值线性代数都是在列专业中完成的。

C 是 row major 的原因在某种程度上是它的数组语法的结果。您将 3 行 x 2 列数组声明为double a[3][2],以后的索引比以前的索引变化得更快,这对于 2D 数组来说是行主要的。这与自然的西方从左到右的阅读顺序相结合,使行专业看起来更自然。

列优先顺序似乎更自然。例如,假设您想将电影逐张保存到文件中,那么您使用的是列顺序,这非常直观,没有人会以行优先顺序保存它。

如果你是 C/C++ 程序员,你应该使用一些更高级别的矩阵库(Eigen,Armadillo,...),默认列优先顺序。尽管 C/C++ 提供了一些提醒矩阵索引的东西,但只有疯子才会使用具有行优先顺序的原始 C 指针。

为简单起见,所有具有行优先顺序的东西都应该被认为至少是奇怪的形式。逐片切片只是自然顺序,它意味着列优先顺序(如 Fortran)。我们的父亲/母亲选择它有一个很好的理由。

不幸的是,在它变得清晰之前,可能由于缺乏经验,以行优先顺序创建了几个有趣的库。

为了澄清行主顺序的定义,其中右索引在内存中一步变化得更快,例如 A(x,y,z) 它是 z-index,这意味着在内存中来自不同切片的像素是相邻的,我们会不想。对于电影 A(x,y,t),最后一个索引是时间 t。不难想象,以行优先模式保存电影根本不可能。

由于内存和缓存的工作方式以及多个索引转换为线性索引的方式,行优先/列优先索引的选择会对性能产生重大影响。在内部,内存是一个单一的一维数组,矩阵的元素将线性排列:m×n

  • 如果使用行优先顺序,元素将存储在索引mi,ji×m+j
  • 如果使用列优先顺序,元素将存储在索引mi,jj×n+i

现在想象以下算法:

for i from 1 to m
   for j from 1 to n
      do something with m(i,j)

如果使用行优先顺序,那么这将依次遍历所有线性索引,从而产生良好的内存局部性,而如果使用列优先顺序,则连续的内存访问将分散在内存中。后果可能非常严重,尤其是当虚拟内存/交换进入场景时。i×m+j

结论:

  1. 是的,它很重要,但选择取决于访问数据的方式。对于前面的示例,如果使用列顺序,您可以做的只是交换两个循环。

  2. 经验法则:快速变化的索引应该映射到内存中的连续位置。

  3. 更重要的是,测量/基准测试选择的影响是基础,因为它取决于许多参数(数据大小、缓存大小、使用的语言将多个索引映射到线性索引的方式、操作方式系统管理虚拟内存,循环嵌套在您使用的线性代数库中的方式......)