Numpy数组操作的性能比较:C连续性和Fortran连续性
在本文中,我们将介绍在Numpy中使用C连续性和Fortran连续性的数组操作的性能问题。C和Fortran是两种不同的内存布局方式,它们在Numpy数组上的表现也有所不同。特别是,改变数组布局方式可以对数组操作的性能产生很大的影响。
阅读更多:Numpy 教程
什么是C连续性和Fortran连续性
在内存中,数组可以按照行优先(C连续性)或列优先(Fortran连续性)的方式被存储。以一个二维数组为例,C连续性在内存中以行为主要方向,即相邻的元素在内存中连续存放,而Fortran连续性在内存中以列为主要方向。
比如说,对于一个 3\times 3 的数组:
\begin{bmatrix}
1&2&3\
4&5&6\
7&8&9
\end{bmatrix}
它在内存中存储的方式可以是C连续性:
Address | Value |
---|---|
0x00 | 1 |
0x01 | 2 |
0x02 | 3 |
0x03 | 4 |
0x04 | 5 |
0x05 | 6 |
0x06 | 7 |
0x07 | 8 |
0x08 | 9 |
或者是Fortran连续性:
Address | Value |
---|---|
0x00 | 1 |
0x03 | 4 |
0x06 | 7 |
0x01 | 2 |
0x04 | 5 |
0x07 | 8 |
0x02 | 3 |
0x05 | 6 |
0x08 | 9 |
性能比较
在Numpy中进行数组操作时,选择一个合适的数组布局方式可以对操作性能产生很大的影响。比如说,调用sum()函数对数组求和操作:
import numpy as np
a = np.ones((1000, 1000), order='C') # C contiguous
b = np.ones((1000, 1000), order='F') # Fortran contiguous
%timeit a.sum()
# 1.13 ms ± 51 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit b.sum()
# 1.49 ms ± 50.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
在该例中,两个数组的元素数量相同,但在对数组求和时,C连续性的数组比Fortran连续性的数组快了约25%。
还可以通过用数组乘一个常数的方式来测试两种内存布局方式的性能:
a = np.ones((1000, 1000), order='C')
b = np.ones((1000, 1000), order='F')
%timeit a * 2
# 2.02 ms ± 115 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit b * 2
# 2.41 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
在该案例中,C连续性的数组比Fortran连续性的数组快了约16%。
影响性能的因素
影响数组操作性能的因素有很多,下面列举几个可能的原因:
CPU Cache
CPU Cache可以加速数组元素的访问,因此内存访问模式对性能有很大的影响。C连续性和Fortran连续性的内存布局方式会影响Cache的命中率,进而影响操作性能。
数据传输速度
内存传输速度是影响数组操作性能的另一个因素。在现代CPU架构中,从RAM读取数据的速度远慢于从CPU Cache读取数据,因此如果数据连续存储在Cache中,CPU可以更快地访问。
对于连续性的数组,内存中相邻的元素在物理位置上也是相邻的,因此可以更容易地进行预读取和缓存,从而加速操作过程。而对于不连续性的数组,内存中相邻的元素的物理地址并不相邻,因此不能进行这种优化。
操作类型
不同的数组操作类型对连续性要求不同。一般来说,一维数组比多维数组更容易实现连续性操作。常用的数组操作,比如加法、乘法、统计函数和矩阵乘法等,都可以通过适当的内存布局方式来加速。
如何检查数组的连续性
在Numpy中,我们可以使用flags属性来检查一个数组是否是连续的(即C连续性或Fortran连续性)。例如:
a = np.ones((3, 3), order='C')
b = np.asfortranarray(a)
print(a.flags['C_CONTIGUOUS']) # True
print(b.flags['C_CONTIGUOUS']) # False
print(a.flags['F_CONTIGUOUS']) # False
print(b.flags['F_CONTIGUOUS']) # True
其中,C_CONTIGUOUS
和F_CONTIGUOUS
属性分别表示数组是C连续性还是Fortran连续性。
总结
在本文中,我们介绍了Numpy中的C连续性和Fortran连续性,它们的内存布局方式对数组操作性能的影响,以及如何检查一个数组的连续性状态。在实际工作中,根据不同的需求选择合适的内存布局方式可以显著提高数组操作的性能。