我有一个 2D NumPy 数组,其中的条目是紧密聚集在 1.0 周围的随机浮点数,例如1.015、0.989等。行数为n_rows = 10^4,列数为2^13:
n_rows = 10**4
n_columns = 2**13
my_array = 1 + ((np.random.random(n_rows*n_columns).reshape((n_rows, n_columns)) - 1/2) * 2 * 0.025)
我的 2020 款配备 8GB 内存的 MacBook Air 说这个数组占用了大约 6.55 亿字节(通过写入 my_array.nbytes),这似乎不是问题。到目前为止一切顺利。
现在,在我的项目中,我需要累计乘以每列中的条目。为此,我写了
products = np.cumprod(my_array, axis=0)
在我的 Jupyter 笔记本中使用“%timeit”语法会产生以下结果: 每个循环 6.62 s ± 258 ms(7 次运行,每次 1 次循环)。
这对于我的目的来说太慢了,所以我开始尝试 np.cumprod 函数并调整 n_rows 和 n_columns,这样做时,我发现了一些非常奇怪的事情:如果我 increase num_columns 从 2^13 到 2^13 + 1,性能大幅提升。事实上,%timeit 每个循环产生 1.31 s ± 4.97 ms,速度快了 5 倍以上!
如果我使用 n_columns = 2^13 - 1,也会出现同样的现象(急剧加速)。如果我使用 np.cumsum 而不是 np.cumprod,也会看到相同的模式。当我让列数由另一个 2 的幂给出时,我也可以重现这种奇怪的行为,例如2^12 或 2^14。然而,当我在任一函数中设置 axis=1 时,这种异常行为似乎消失了,此外,当我使用 np.prod 或 np.sum 时,这种行为也消失了。
如果有人能解释这些奇怪结果的原因,我将非常感激。
我生成了数十个具有上述形状的随机二维数组,结果是相同的。
我原以为 num_columns = 2^13 的执行时间与 n_columns = 2^13 + 1 和 n_columns = 2^13 - 1 的情况几乎相同。没想到速度相差 5 倍!
要重现我的工作,请在 Jupyter 笔记本中尝试以下操作:
细胞 1:
import numpy as np
n_rows = 10**4
n_columns = 2**13 # or 2**13 +/- 1
my_array = 1 + ((np.random.random(n_rows*n_columns).reshape((n_rows, n_columns)) - 1/2) * 2 * 0.025)
细胞 2:
%timeit products = np.cumprod(my_array, axis=0)
数组的几何形状会对 NumPy 管理内存访问和缓存利用率的方式产生很大影响,这与您看到的性能异常有关,尤其是在使用 2 的幂时。
正如您之前所注意到的,当您将 n_columns 设置为 2^13 + 1 或 2^13 - 1 时,性能会大大提高。通过进行此调整,可以解决对齐问题,并且 CPU 可以更快地访问内存。