2.12 连续数组:为什么contiguous这么重要?
目录
2.12.1 C顺序与Fortran顺序对比
NumPy 的 ndarray
支持两种主要的内存顺序:C 顺序(行优先)和 Fortran 顺序(列优先)。了解这两种顺序的差异和影响对于优化内存访问至关重要。
- C 顺序的基本原理:行优先存储。
- Fortran 顺序的基本原理:列优先存储。
- 选择合适的顺序:在不同场景下选择合适的内存顺序。
import numpy as np# 创建一个 C 顺序的数组
a_c = np.array([[1, 2, 3], [4, 5, 6]], order='C')
print(f"C 顺序数组 a_c: \n{a_c}")
print(f"a_c 的步长: {a_c.strides}") # 输出步长# 创建一个 Fortran 顺序的数组
a_f = np.array([[1, 2, 3], [4, 5, 6]], order='F')
print(f"Fortran 顺序数组 a_f: \n{a_f}")
print(f"a_f 的步长: {a_f.strides}") # 输出步长
2.12.2 跨步数组重排
跨步(strides)是 ndarray
中非常重要的概念,通过调整步长可以实现数组的重排,而不需要创建新的数据副本。合理的跨步设置可以显著提高性能。
- 跨步的基本概念:步长的定义和作用。
- 跨步重排的方法:如何通过调整步长实现数组重排。
- 跨步重排的性能优势:避免数据复制,提高访问效率。
import numpy as np# 创建一个 3x3 的数组
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])# 通过重塑实现跨步重排
b = a.reshape(9) # 将 3x3 的数组重塑为 1x9 的数组
print(f"重塑后的数组 b: \n{b}")
print(f"b 的步长: {b.strides}") # 输出步长# 通过转置实现跨步重排
c = a.T # 转置数组
print(f"转置后的数组 c: \n{c}")
print(f"c 的步长: {c.strides}") # 输出步长
2.12.3 BLAS库兼容性
BLAS(Basic Linear Algebra Subprograms)库是许多数值计算库的核心,NumPy 也依赖于 BLAS 库来实现高效的矩阵运算。了解 ndarray
的连续性对 BLAS 库的兼容性影响可以优化计算性能。
- BLAS库的基本原理:BLAS 库的介绍和作用。
- 连续性对 BLAS 的影响:非连续数组对 BLAS 库性能的影响。
- 优化 BLAS 兼容性:如何确保数组的连续性以优化 BLAS 性能。
import numpy as np
import time# 创建一个大数组
a = np.random.rand(1000, 1000)# 非连续数组
b = a[::2, ::2] # 非连续数组# 连续数组
c = np.ascontiguousarray(b) # 转换为连续数组# 计算矩阵乘法
start_time = time.time()
result_b = np.dot(b, b.T) # 非连续数组的矩阵乘法
non_contiguous_time = time.time() - start_time
print(f"非连续数组矩阵乘法用时: {non_contiguous_time:.2f}秒")start_time = time.time()
result_c = np.dot(c, c.T) # 连续数组的矩阵乘法
contiguous_time = time.time() - start_time
print(f"连续数组矩阵乘法用时: {contiguous_time:.2f}秒")# 比较性能
speedup = non_contiguous_time / contiguous_time
print(f"连续数组矩阵乘法性能提升: {speedup:.2f}倍")
2.12.4 转置操作性能对比
转置操作在数组处理中非常常见,但不同的数组顺序(如 C 顺序和 Fortran 顺序)会影响转置的性能。了解转置操作的性能差异可以优化代码。
- 转置的基本原理:转置操作的定义和作用。
- C 顺序和 Fortran 顺序的转置性能:比较两种顺序的转置性能。
- 优化转置操作:如何优化转置操作以提高性能。
import numpy as np
import time# 创建一个 C 顺序的数组
a_c = np.random.rand(1000, 1000)# 创建一个 Fortran 顺序的数组
a_f = np.asfortranarray(a_c)# 计算 C 顺序数组的转置
start_time = time.time()
b_c = a_c.T # 转置操作
c contiguous_time = time.time() - start_time
print(f"C 顺序数组转置用时: {c_contiguous_time:.2f}秒")
print(f"b_c 的步长: {b_c.strides}") # 输出步长# 计算 Fortran 顺序数组的转置
start_time = time.time()
b_f = a_f.T # 转置操作
f_contiguous_time = time.time() - start_time
print(f"Fortran 顺序数组转置用时: {f_contiguous_time:.2f}秒")
print(f"b_f 的步长: {b_f.strides}") # 输出步长# 比较性能
speedup = c_contiguous_time / f_contiguous_time
print(f"Fortran 顺序数组转置性能提升: {speedup:.2f}倍")
2.12.5 总结
- 关键收获:理解 C 顺序和 Fortran 顺序的差异,掌握跨步数组重排的方法,了解 BLAS 库兼容性的重要性,优化转置操作的性能。
- 最佳实践:合理选择内存顺序,优化数组的跨步设置,确保数组的连续性以提高计算性能,使用
np.ascontiguousarray
和np.asfortranarray
进行内存优化。 - 实用技巧:通过实时监控内存占用和性能测试,找到最优的内存布局策略。
通过本文,我们深入探讨了 NumPy 中连续数组的重要性,包括 C 顺序和 Fortran 顺序的对比,跨步数组的重排技巧,BLAS 库的兼容性问题,以及转置操作的性能优化。希望这些内容能帮助你在实际开发中更好地优化内存使用,提高代码性能,避免常见的内存陷阱。
2.12.6 参考文献
参考资料 | 链接 |
---|---|
《NumPy Beginner’s Guide》 | NumPy Beginner’s Guide |
《Python for Data Analysis》 | Python for Data Analysis |
NumPy 官方文档 | NumPy Reference |
Dask 官方文档 | Dask Documentation |
Stack Overflow | Difference between C and Fortran order in NumPy |
Medium | Understanding NumPy’s Memory Layout |
Python Memory Management | Python Memory Management |
SciPy 官方文档 | SciPy Memory Efficiency |
Wikipedia | BLAS (Basic Linear Algebra Subprograms) |
《高性能Python》 | High Performance Python |
《Python数据科学手册》 | Python Data Science Handbook |
Intel MKL | Intel Math Kernel Library (MKL) |
OpenBLAS | OpenBLAS Documentation |
这篇文章包含了详细的原理介绍、代码示例、源码注释以及案例等。希望这对您有帮助。如果有任何问题请随私信或评论告诉我。