【发布时间】:2019-03-13 15:29:21
【问题描述】:
我很好奇使用np.empty 而不是np.zeros 真正产生了多大的差异,以及与np.ones 的差异。我运行这个小脚本来对每个创建一个大数组所花费的时间进行基准测试:
import numpy as np
from timeit import timeit
N = 10_000_000
dtypes = [np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64,
np.float16, np.float32, np.float64]
rep= 100
print(f'{"DType":8s} {"Empty":>10s} {"Zeros":>10s} {"Ones":>10s}')
for dtype in dtypes:
name = dtype.__name__
time_empty = timeit(lambda: np.empty(N, dtype=dtype), number=rep) / rep
time_zeros = timeit(lambda: np.zeros(N, dtype=dtype), number=rep) / rep
time_ones = timeit(lambda: np.ones(N, dtype=dtype), number=rep) / rep
print(f'{name:8s} {time_empty:10.2e} {time_zeros:10.2e} {time_ones:10.2e}')
结果得到下表:
DType Empty Zeros Ones
int8 1.39e-04 1.76e-04 5.27e-03
int16 3.72e-04 3.59e-04 1.09e-02
int32 5.85e-04 5.81e-04 2.16e-02
int64 1.28e-03 1.13e-03 3.98e-02
uint8 1.66e-04 1.62e-04 5.22e-03
uint16 2.79e-04 2.82e-04 9.49e-03
uint32 5.65e-04 5.20e-04 1.99e-02
uint64 1.16e-03 1.24e-03 4.18e-02
float16 3.21e-04 2.95e-04 1.06e-02
float32 6.31e-04 6.06e-04 2.32e-02
float64 1.18e-03 1.16e-03 4.85e-02
由此我得出两个有点令人惊讶的结论:
-
np.empty和np.zeros的性能几乎没有区别,可能除了int8的一些区别。我不明白为什么会这样。创建一个空数组应该会更快,实际上我已经看到了这方面的报告(例如Speed of np.empty vs np.zeros)。 -
np.zeros和np.ones之间存在很大差异。我怀疑这与用于内存归零的高性能方法有关,它不适用于用常量填充内存区域,但我真的不知道它是如何工作的,或者在什么级别上工作。
这些结果的解释是什么?
我在 Windows 10(带有 MKL)上使用 NumPy 1.15.4 和 Python 3.6 Anaconda,并且我有一个 Intel Core i7-7700K CPU。
编辑:根据 cmets 中的建议,我尝试运行基准测试,将每个单独的试验交错并在最后取平均值,但我看不到结果有显着差异。不过,在相关的说明中,我不知道 NumPy 中是否有任何机制可以重用刚刚删除的数组的内存,这会使措施不切实际(尽管时间似乎确实会随着数据类型的大小而增加)对于空数组)。
【问题讨论】:
-
@PavenMinaev 在this post 中有一条评论指出,诸如 FreeBSD 之类的操作系统可以在 CPU 空闲时清零未使用的内存块,因此当调用
calloc时,它只会返回其中之一块,而不必当场将块归零。不确定 Windows 10 是否发生了类似的事情。此外,为了最大限度地减少缓存的影响,也许不是在运行另一个 dtype 之前一次性运行相同 dtype 的所有代表,而是交错运行它们,累积时间,然后最后分开。 -
“重用刚刚删除的数组的内存” 我很确定它确实如此。在 shell 中你可以尝试类似
np.empty(4)np.ones(4)np.empty(4)。从第二次打电话到empty,我收到了四个电话,这可能不是巧合。
标签: python performance numpy initialization