【问题标题】:Why doesn't numpy.zeros allocate all of its memory on creation? And how can I force it to?为什么 numpy.zeros 在创建时不分配所有内存?我怎么能强迫它呢?
【发布时间】:2018-12-21 04:38:28
【问题描述】:

我想在 Python 中创建一个空的 Numpy 数组,以便稍后用值填充它。下面的代码会生成一个 1024x1024x1024 数组,其中包含 2 字节整数,这意味着它应该在 RAM 中至少占用 2GB。

>>> import numpy as np; from sys import getsizeof
>>> A = np.zeros((1024,1024,1024), dtype=np.int16)
>>> getsizeof(A)
2147483776

getsizeof(A),我们看到该数组占用了 2^31 + 128 个字节(可能是标头信息)。但是,使用我的任务管理器,我可以看到 Python 只占用了 18.7 MiB 的内存。

假设数组被压缩,我给每个内存槽分配了随机值,所以它不可能。

>>> for i in range(1024):
...   for j in range(1024):
...     for k in range(1024):
...         A[i,j,k] = np.random.randint(32767, dtype = np.int16)

循环仍在运行,并且我的 RAM 正在缓慢增加(可能是因为组成 A 的数组因不可压缩的噪音而膨胀。)我假设它会使我的代码更快地强制 numpy 从一开始就扩展这个数组。奇怪的是,我在任何地方都没有看到这个记录!

那么,1. 为什么 numpy 会这样做? 2.如何强制numpy分配内存?

【问题讨论】:

  • 1.我会怀疑内存效率和速度。 2. 如果需要,您可以使用随机数初始化一个 numpy 数组,只需 A=np.random.randn(1024,1024,1024)。不知道你为什么要强制 numpy 这样做。
  • That's normal calloc behavior. 我不明白为什么如果你强制系统“真正”预先分配所有内存,这会更快。
  • 哦,在谷歌上搜索后很有意义。我以前不熟悉calloc。我曾假设 Numpy 以智能方式存储每个数组,并在请求时将其替换为实际数组。
  • 强制它提前分配内存可能会更慢,而不是更快。偶尔会有这样做的理由,但它们主要与您正在突破 RAM 的限制并与操作系统的过度使用作斗争的情况有关,或者您试图获得更详细的特定于平台的基准测试或分析的情况,等等,通常你最终不得不做一些低级和特定于平台的事情。
  • 如果您确实需要这样做,您通常想要手动创建一个(匿名或磁盘支持的)np.memmapmmap.mmap、@987654329 @ 和 MADV_SEQUENTIAL 它(或您平台的相关等效项),然后使用映射创建一个数组进行存储。这仍然不会强制内核按照你想要的方式分配内存,但它强烈鼓励它这样做。

标签: python numpy memory


【解决方案1】:

让我们看一下小案例的一些时间安排:

In [107]: A = np.zeros(10000,int)
In [108]: for i in range(A.shape[0]): A[i]=np.random.randint(327676)

我们不需要制作A 3d 来获得同样的效果;总大小相同的 1d 也一样好。

In [109]: timeit for i in range(A.shape[0]): A[i]=np.random.randint(327676)
37 ms ± 133 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

现在将该时间与一次调用生成随机数的替代方法进行比较:

In [110]: timeit np.random.randint(327676, size=A.shape)
185 µs ± 905 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

快得多。

如果我们做同样的循环,只是将随机数分配给一个变量(然后扔掉):

In [111]: timeit for i in range(A.shape[0]): x=np.random.randint(327676)
32.3 ms ± 171 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

时间几乎与原始情况相同。将值分配给 zeros 数组并不是最重要的消费者。

我没有像你一样测试一个非常大的案例,我的A 已经完全初始化了。因此,欢迎您重复与您的尺寸进行比较。但我认为这种模式仍然存在 - 迭代 1024x1024x1024 次(比我的示例大 100,000 次)是大时间消耗者,而不是内存分配任务。

您可能会尝试其他一些方法:只需在A 的第一个维度上进行迭代,然后分配randomint 的形状与其他两个维度相同。例如,将我的A 扩展为 10 尺寸:

In [112]: A = np.zeros((10,10000),int)
In [113]: timeit for i in range(A.shape[0]): A[i]=np.random.randint(327676,size=A.shape[1])
1.95 ms ± 31.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

A 比 [107] 大 10 倍,但填充时间减少了 16 倍,因为它仅迭代 10 倍。在numpy 中,如果您必须迭代,请尝试在更复杂的任务上执行几次。

timeit 多次重复测试(例如 7*10),因此它不会捕获任何初始内存分配步骤,即使我为此使用了足够大的数组。

【讨论】:

    【解决方案2】:

    您的第一个问题的简洁答案也可以在in this StackOverflow answer 找到。

    要回答您的第二个问题,您可以强制以或多或少的有效方式分配内存:

    A = np.empty((1024,1024,1024), dtype=np.int16)
    A.fill(0)
    

    因为那时记忆被触动了。 在我的机器上使用我的设置,

    A = np.empty(0)
    A.resize((1024, 1024, 1024))
    

    也能做到这一点,但我找不到这种行为的记录,这可能是一个实现细节; realloc 在 numpy 的底层使用。

    【讨论】:

    • 这应该是公认的答案。分配内存不会创建内存页面,它只保留一个地址范围。仅当实际触及该地址范围时,才会实例化包含该内存的页面。这与 Numpy 无关,与操作系统无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-22
    • 2012-01-20
    • 1970-01-01
    • 2012-10-30
    相关资源
    最近更新 更多