Column- vs. Row-major Layout Array

Apr. 27, 2023

前两天在逛知乎的时候,在一篇回答的下面看到了一个回答:“[在MATLAB中,] 先列后行比先行后列快很多”1 。也就是说,在不得以循环遍历矩阵中的每一个数组的元素时,“先遍历列元素,再遍历行元素”所花费的时间更短。于是,今天我就简单地测试了一下,采用的测试程序是“100次循环10e4阶矩阵的元素自增1”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
clc,clear,close all

%%First row, second column
tic
A = ones(10^4);
for t = 1:100
    for columns = 1:width(A)
        for rows = 1:height(A)
            A(rows,columns) = A(rows,columns)+1;
        end
    end
end
toc

%% First column, second row
clear all

tic
B = ones(10^4);
for t = 1:100
    for rows = 1:height(B)
        for columns = 1:width(B)
            B(rows,columns) = B(rows,columns)+1;
        end
    end
end
toc
1
2
Elapsed time is 24.729285 seconds.
Elapsed time is 263.073480 seconds.

可以看到,的确“先列后行”是更快的,并且速度差异是很明显的。

这种差异性是由MATLAB存储数组(Array)的方式导致的2


根据数组的存储方式,数组的布局(Array layout,order,format或representation)分为column-major layoutrow-major layout。对于column-major layout的数组,中的元素在内存中是连续的(contiguous);对于row-major layout的数组,中的元素在内存中是连续的。数组的布局方式对于代码的集成(integration),可用性(usability)和性能(performance)是非常重要的。某些算法对于特定的array layout执行得更好。

在MATLAB的编程语言和编程环境中,所有的数据都是默认采用单一的column-major layout

常见的采用column-major layout(有时也被称为”Fortran” style ordering3,Fortran-style contiguous array4)的编程语言:MATLAB,Fortran(许多流体力学的仿真采用的是这种语言),R。常见的采用row-major layout(有时也被称为”C” style ordering,或者C-style contiguous array)的编程语言:C,C++,Python NumPy package45


对于MATLAB采用column-major layout这一特性在下面的矩阵索引或运算的过程中可以得到一定的反映。

(1)矩阵索引

1
2
3
A = [1,2,3,4;...
    5,6,7,8];
A(3)
1
2
ans =
     2

(2)矩阵展开

1
2
3
A = [1,2,3,4;...
    5,6,7,8];
A(:)
1
2
3
4
5
6
7
8
9
ans =
     1
     5
     2
     6
     3
     7
     4
     8

(3)素数判断

最近我在尝试计算某一个大数下素数的个数,其中涉及到使用MATLAB的isprime函数来判断某个数是否是素数 6isprime函数首先会将输入展开成一个列向量:

image-20230428170321052

为了测试使用行向量和列向量的区别,我将isprime函数复制了一份,并将这一语句修改为X=X(:)',其余部分不变,定义为一个新的函数helperIsprime

image-20230428170545901

注:这里的herlperIsprimes函数并没有进行博客6 中提到的优化。

之后,测试了一下两个函数“重复20次判断1:2e7内所有元素是否为素数”所花费的时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
clc,clear,close all

tic
for i = 1:20
    result_builtin = isprime(1:2e7);
end
toc

clear all

tic
for i = 1:20
    result_helper = helperIsprime(1:2e7);
end
toc
1
2
Elapsed time is 2931.111347 seconds.
Elapsed time is 3875.116026 seconds.

结果表明,将列向量更改为行向量后,的确降低了代码遍历元素的速度,也从侧面验证了MATLAB采用的是column-major layout数组。


References