首页 > 解决方案 > np.zeros 和 np.full 的内存消耗和性能差异的原因

问题描述

测量内存消耗时np.zeros

import psutil
import numpy as np

process = psutil.Process()
N=10**8
start_rss = process.memory_info().rss
a = np.zeros(N, dtype=np.float64)
print("memory for a", process.memory_info().rss - start_rss)

结果是意外8192字节,即几乎为 0,而 1e8 双打则需要 8e8 字节。

当替换np.zeros(N, dtype=np.float64)为字节np.full(N, 0.0, dtype=np.float64)所需的内存a时。800002048

运行时间也有类似的差异:

import numpy as np
N=10**8
%timeit np.zeros(N, dtype=np.float64)
# 11.8 ms ± 389 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit np.full(N, 0.0, dtype=np.float64)
# 419 ms ± 7.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

np.zeros对于大尺寸来说,速度提高了 40 倍。

不确定这些差异是否适用于所有架构/操作系统,但我至少在 x86-64 Windows 和 Linux 上观察到了这种差异。

np.zeros和之间的哪些差异np.full可以解释不同的内存消耗和不同的运行时间?

标签: pythonperformancenumpy

解决方案


我不相信psutil这些内存基准,而且 rss(驻留集大小)可能一开始就不是正确的指标。

使用 stdlib tracemalloc,您可以获得正确的内存分配数字 - 对于这个 N 和 float64 dtype,它应该是大约 800000000 字节的增量:

>>> import numpy as np
>>> import tracemalloc
>>> N = 10**8
>>> tracemalloc.start()
>>> tracemalloc.get_traced_memory()  # current, peak
(159008, 1874350)
>>> a = np.zeros(N, dtype=np.float64)
>>> tracemalloc.get_traced_memory()
(800336637, 802014880)

np.full对于and之间的时间差异np.zeros,比较and 的手册页malloccallocnp.zeros可以转到一个分配例程,该例程将页面归零。请参阅PyArray_Zeros--> 调用PyArray_NewFromDescr_int传入参数1zeroed然后有一个特殊情况可以更快地分配零:

if (zeroed || PyDataType_FLAGCHK(descr, NPY_NEEDS_INIT)) {
    data = npy_alloc_cache_zero(nbytes);
}
else {
    data = npy_alloc_cache(nbytes);
}

看起来np.full没有这条快速路径。那里的性能将类似于首先执行 init 然后执行复制O(n)

a = np.empty(N, dtype=np.float64)
a[:] = np.float64(0.0)

numpy如果填充值为零,开发人员可能已经添加了一条快速路径np.full,但是为什么还要添加另一种方法来做同样的事情 - 用户可以np.zeros首先使用。


推荐阅读