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


16

在对密集矩阵计算进行编程时,是否有任何理由要选择超过行的主布局的行主布局?

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

以行为主的布局似乎更自然,更简单(至少对我而言)。但是用Fortran编写的LAPACK之类的主要库都使用列的主要布局,因此必须有一些选择的理由。


如果考虑使用x列向量计算b = A * x,则对于行为主的A,我们可以使用向量的内积A(i,:)^ T x来获得b(i);对于主要列,我们可能只需要标量乘法向量sum_i A(:,i)x(i)。在我看来,专修专业要好得多!你怎么看?
张晖

训练自己喜欢大专栏。当将向量可视化为列或将其转置为行时,这很容易。它使矩阵乘法的可视化变得非常简单,并使遵循大量已发布的数学变得容易。
Mike Dunlavey

Answers:


18

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

通常,就内存带宽使用率和缓存性能而言,按在内存中排列顺序访问数组元素的效率要高得多。根据矩阵的存储方式,您将需要选择可以利用此算法的算法。

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


11

在不考虑任何现有软件的情况下,从代码角度来看,没有理由偏爱列专业而不是行专业。但是,大多数数学文献都是以将向量存储为列而不是行的方式将向量分组为矩阵的方式编写的。例如,当你写的完全特征方程时,X一种X=XΛX矩阵包含以列写出的所有特征向量。您永远不会真正看到它是用其他方式写的(尽管我听说统计人员喜欢行向量)。因此,很自然,最早的软件采用列主格式,因此,如果您有一个由一组向量组成的矩阵,则任何单个向量的存储都是连续的。因此,我认为传统一直延续到今天,并且,如果您想与ye Forde Fortran进行交互,则希望使用column major。因此,几乎所有高效的数值线性代数都是在列专业中完成的。

C是行专业的原因在某种程度上是其数组语法的结果。您将3行2列的数组声明为double a[3][2],而较后的索引的变化要快于较早的索引,这对于2D数组而言使其成为主要行。结合自然的西方阅读顺序(从左到右),使行专业显得更加自然。


2
我认为这些说法很糟糕。'''double a [3] [2]'''中的最后一个索引变化最快的事实并非巧合-这是一种有意识的设计决策,与在Fortran中有意识的设计决策相同当您有一个'''real(3,2)'''数组时,可以采用另一种方法。
Wolfgang Bangerth

1
此外,几乎所有的高效数值线性代数都不是列专业。对于BLAS和LAPACK可能仍然是这样,但是对于过去15年中出现的每个主要线性代数库来说,都并非如此:例如,PETSc和Trilinos都使用行主要稀疏矩阵存储格式。
Wolfgang Bangerth

我知道C约定是一个有意识的决定,可能基于自然的阅读顺序。我的意思是说它可能没有考虑数字线性代数的设计,这使它成为行专业的巧合。其次,我不希望该论据仅适用于稀疏矩阵。对于稀疏的情况,压缩后的行和列格式都有点混杂。
维克多·刘

5
不用担心,而是C最初是基于较早的语言B和BCPL的系统语言,运行在像PDP-11这样的系统上,该系统最初没有浮点数。说他们在设计时考虑了数字是很困难的。
维克多·刘

7
到那里等。C中的矩阵移动最后一个索引最快的原因是因为C没有矩阵。它具有向量的向量,可以透明地实现为固态内存块或数组的指针数组。(我猜)使索引顺序与Fortran兼容甚至不在Dennis Ritchie的关注范围之内。
Mike Dunlavey

2

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

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

为简单起见,所有具有大行顺序的事物都应至少视为奇怪的形式。逐个切片是自然顺序,它表示列优先顺序(如Fortran)。我们的父亲/母亲选择它们的理由非常充分。

不幸的是,由于缺乏经验,在以行为主的顺序创建了几个有趣的库之前。

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


2

×ñ

  • 元件 一世Ĵ一世×+Ĵ 如果使用行优先顺序
  • 元件 一世Ĵ 将存储在索引处 Ĵ×ñ+一世 如果使用列主要顺序

现在想象一下以下算法:

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

如果使用行优先顺序,则它将遍历所有线性索引 一世×+Ĵ顺序地,导致良好的内存局部性,而如果使用列优先顺序,则连续的内存访问将分散在内存中。后果可能非常严重,尤其是当虚拟内存/交换进入现场时。

结论:

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.