【问题标题】:Reasons for differences in memory consumption and performances of np.zeros and np.fullnp.zeros 和 np.full 的内存消耗和性能差异的原因
【发布时间】:2020-03-11 16:22:09
【问题描述】:

在测量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.zerosnp.full之间的哪些区别可以解释不同的内存消耗和不同的运行时间?

【问题讨论】:

标签: python performance numpy


【解决方案1】:

对于这些内存基准测试,我不信任 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.fullnp.zeros 之间的时间差异,请比较malloccalloc 的手册页,即np.zeros 可以转到an allocation routine which gets zeroed pages。请参阅 PyArray_Zeros --> 调用 PyArray_NewFromDescr_int 传入 1 以获取 zeroed 参数,然后使用 a special case 更快地分配零:

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

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

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

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

【讨论】:

  • 我相信 psutils 比 tracemalloc (stackoverflow.com/q/50148554/5769463) 更多,即使对于 numpy,内存使用情况应该被正确记录。但是,tracemalloc 并没有显示真正提交的内存——我认为操作系统最清楚实际使用了多少内存。
  • 我也怀疑是否有比 np.full 使用的更快的零内存方法(python/numpy 开销在这里没有起到很大的作用) - 否则 np.full 会使用它。在您的测量中,np.zeros 将以大约 53333GB/s 的速度将内存归零,这是不可能的。
  • 嗯,您对np.zeros 的看法是正确的,这里肯定有其他事情发生 - 我将删除我的答案的那部分并继续调查。我应该更仔细地检查绝对数字。也许隐藏在某个地方的一些惰性初始化。
  • @wim:几乎可以肯定,大型预置零分配更快,因为它依赖于操作系统提供预置零页面。同样的方式calloc 可以是malloc+memset 用于小型分配,但普通malloc 用于大型分配(大到足以从操作系统发出专用的内存请求,例如通过 Windows 的VirtualAlloc 或 UNIX -like 的匿名 mmap),没有归零步骤。在 Linux 上,这些页面是零页面的写时复制映射,这意味着对每个页面的第一次写入更昂贵; np.full 将强制提前复制,并具有一致的写入性能。
  • @wim:是的,刚刚检查过。如果分桶空闲列表中没有缓存的归零数据,则npy_alloc_cache_zero 推迟到PyDataMem_NEW_ZEROEDPyDataMem_NEW_ZEROED 的主要工作是通过 calloc 调用完成的,它具有我在大多数分配器上提到的优化。
【解决方案2】:

numpy.zeros 函数直接使用 Numpy 库的 C 代码层,而函数 onesfull 通过初始化相同的方式工作一个值数组并在其中复制所需的值。

那么 zeros 函数不需要任何语言解释,而对于其他函数,onesfull,Python 代码需要解释为 C 代码。

看看源代码自己弄明白:https://github.com/numpy/numpy/blob/master/numpy/core/numeric.py

【讨论】:

  • 对不起,我用的是法语:)
猜你喜欢
  • 2015-02-21
  • 1970-01-01
  • 2017-02-09
  • 1970-01-01
  • 2015-06-16
  • 1970-01-01
  • 2014-02-13
  • 1970-01-01
  • 2012-06-03
相关资源
最近更新 更多