目录

Python加速科学运算的一些小技巧

原地操作

使用

1
2
3
4
a = 1
b = 1
a += b
print(a) # 结果是2

而不是使用

1
a = a + b

好处是内存不会复制扩展,只使用a和b的内存运算

1
2
3
4
import numpy as np
X = np.arange(12).reshape(3, 4)
Y = np.array([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
np.concatenate([X, Y], axis=0), np.concatenate([X, Y], axis=1)

检测内存是否一致,在下面的例子中,用Python的id()函数演示了这一点, 它提供了内存中引用对象的确切地址。 运行Y = Y + X后,会发现id(Y)指向另一个位置。 这是因为Python首先计算Y + X,为结果分配新的内存,然后使Y指向内存中的这个新位置。

1
2
3
before = id(Y)
Y = Y + X
id(Y) == before

幸运的是,执行原地操作非常简单。 可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = <expression>。 为了说明这一点,首先创建一个新的矩阵Z,其形状与另一个Y相同, 使用zeros_like来分配一个全的块。

1
2
3
4
Z = np.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))

如果在后续计算中没有重复使用X, 可以使用X[:] = X + YX += Y来减少操作的内存开销。

1
2
3
before = id(X)
X += Y
id(X) == before

重复使用数据使其在缓存中,按序读写数据使其可预读取

矩阵按行存储比按列存储读取更快,预读取是按行读取的

CPU多核优势可用对应并行数优化速度

注意是使用物理核心数而不是逻辑核心数,这里的逻辑核心可能是物理核心的翻倍,需要仔细查看

由于Python的多线程只是在一个核心上跑,多进程才是在多核上跑,所以优化代码时最好使用多进程优化

多进程通信最好使用队列维护,可用兼容异步并行和同步逻辑,最好只使用尽量少的队列,尽量在一步逻辑内使用同队列一次,否则更新队列会很麻烦,逻辑考虑不周全可能会有漏洞(进程锁可解决,但太麻烦了,尽量多进程内的逻辑简单)

但进程逻辑也不能太简单,否则开销比多线程还要大,任务过于简单不如使用单核多线程了

GPU上少用控制语句

GPU的同步开销过大,支持很有限,GPU跑通用计算强但控制流不强

GPU上尽量保持内存本地性

GPU的三级缓存很小

GPU上尽量使用并行

使用数千个线程(类同CPU)

GPU和CPU不要频繁互传数据

带宽限制,同步开销,都不好频繁传数据