矩阵和二维数组有两种不同的存储顺序:列优先和行优先。本节解释了这些存储顺序以及如何指定应该使用哪一种。
列优先和行优先存储
矩阵的元素形成二维网格。然而,当矩阵存储在内存中时,元素必须以某种方式线性排列。有两种主要方法可以做到这一点,按行和按列。
如果一个矩阵是逐行存储的,我们说它是按行优先顺序存储。首先存储整个第一行,然后存储整个第二行,依此类推。例如考虑矩阵:
$$ A=\begin{bmatrix} 8&2&2&9 \\ 9&1&4&4 \\ 3&5&4&5 \end{bmatrix}. $$
如果该矩阵以行优先顺序存储,则元素在内存中的布局如下:
8 2 2 9 9 1 4 4 3 5 4 5
另一方面,如果一个矩阵是按列存储的,则它以列优先顺序存储,从整个第一列开始,然后是整个第二列,依此类推。如果上述矩阵按列优先顺序存储,则布局如下:
8 9 3 2 1 5 2 4 4 9 4 5
此示例由以下Eigen代码说明。它使用 PlainObjectBase::data()
函数,该函数返回指向矩阵第一个元素的指针。
Matrix<int, 3, 4, ColMajor> Acolmajor;
Acolmajor << 8, 2, 2, 9,
9, 1, 4, 4,
3, 5, 4, 5;
cout << "The matrix A:" << endl;
cout << Acolmajor << endl << endl;
cout << "In memory (column-major):" << endl;
for (int i = 0; i < Acolmajor.size(); i++)
cout << *(Acolmajor.data() + i) << " ";
cout << endl << endl;
Matrix<int, 3, 4, RowMajor> Arowmajor = Acolmajor;
cout << "In memory (row-major):" << endl;
for (int i = 0; i < Arowmajor.size(); i++)
cout << *(Arowmajor.data() + i) << " ";
cout << endl;
输出:
The matrix A:
8 2 2 9
9 1 4 4
3 5 4 5
In memory (column-major):
8 9 3 2 1 5 2 4 4 9 4 5
In memory (row-major):
8 2 2 9 9 1 4 4 3 5 4 5
Eigen中的存储顺序
可以通过为 Matrix
或 Array
指定 Options
模板参数来设置矩阵或二维数组的存储顺序。Matrix
类模板有六个模板参数,其中三个是必需的(Scalar
、RowsAtCompileTime
和 ColsAtCompileTime
),三个是可选的(Options
、MaxRowsAtCompileTime
和 MaxColsAtCompileTime
)。如果 Options
参数设置为 RowMajor
,则矩阵或数组以行优先顺序存储;如果设置为 ColMajor
,则以列优先顺序存储。上述Eigen程序中使用了这种机制来指定存储顺序。
如果未指定存储顺序,则 Eigen 默认以列优先顺序存储。使用一种存储顺序的矩阵和数组可以分配给使用另一种存储顺序的矩阵和数组,就像上面程序中使用矩阵 Acolmajor
初始化矩阵 Arowmajor
那样,Eigen 将自动重新排序元素。更一般地,行优先和列优先矩阵可以根据需要混合在一个表达式中。
选择哪种存储顺序?
那么,应该在程序中使用哪种存储顺序?这个问题没有统一的答案,这取决于应用程序。有以下几点请注意:
- 编程时可能需要特定的存储顺序,或使用第三方库时,该库需要特定的存储顺序,这时使用该存储顺序是最好的选择。
- 由于更好的数据局部性,当矩阵以行优先顺序存储时,逐行遍历矩阵的算法将运行得更快。同样,列优先矩阵的逐列遍历速度更快。
- Eigen 中的默认值是列优先的。自然地,Eigen 库的大部分开发和测试都是通过列优先矩阵完成的。这意味着,尽管我们的目标是自由选择列优先或行优先存储顺序,但 Eigen 库可能最适合列优先矩阵。