在 numpy.zeros 中第一次触及内存后如何处理?

How is memory handled once touched for the first time in numpy.zeros?

我最近看到当通过 np.emptynp.zeros 创建一个 numpy 数组时,那个 numpy 数组的内存实际上并没有像 (and 中讨论的那样由操作系统分配) ,因为 numpy 利用 calloc 分配数组的内存。

In fact, the OS isn't even "really" allocating that memory until you try to access it.

因此,

l = np.zeros(2**28)

不会增加系统报告的已用内存,例如 htop。 只有一次我触摸内存,例如通过执行

np.add(l, 0, out=l)

使用的内存增加了。

由于这种行为,我有几个问题:

1。被触摸的记忆是在引擎盖下复制的吗?

如果我只是在一段时间后才接触内存块,操作系统是否会在后台复制 numpy 数组的内容以保证内存是连续的?

i = 100
f[:i] = 3

while True:
    ... # Do stuff
    f[i] = ... # Once the memory "behind" the already allocated chunk of memory is filled
                # with other stuff, does the operating system reallocate the memory and
                # copy the already filled part of the array to the new location?
    i = i + 1

2。触摸最后一个元素

由于numpy数组的内存在内存中是连续的,所以我认为

f[-1] = 3

可能需要分配整个内存块(不触及整个内存)。 然而,事实并非如此,htop 中使用的内存不会随着数组的大小而增加。 为什么不是这样?

OS isn't even "really" allocating that memory until you try to access it

这取决于目标平台(通常是 OS 及其配置)。一些平台直接在物理内存中分配页面(例如 AFAIK XBox 和一些嵌入式平台一样)。然而,主流平台确实做到了。

1. Is touched memory copied under the hood?
If I touch chunks of the memory only after a while, is the content of the numpy array copied under the hood by the operating system to guarantee that the memory is contiguous?

分配在虚拟内存中执行。当在给定的 内存页面 (固定大小的块,例如 4 KiB)上完成第一次触摸时,OS 映射虚拟页面 到一个物理的。所以当你只设置数组的一项时,只有一页将被物理映射(除非该项目跨越两页,这只发生在病理情况下)。

对于一组连续的虚拟页面,物理页面可能不是连续的。但是,这不是问题,您不应该关心它。这主要是 OS 的工作。也就是说,现代处理器有一个名为 TLB 的专用单元,用于 将虚拟地址 (您可以通过调试器看到的地址)转换为物理地址(因为此翻译相对昂贵且性能关键)。

由于分页,Numpy 数组的内容不会重新分配或复制(至少从用户的角度来看,即在虚拟内存中)。

2. Touching the last element
I thought f[-1] = 3 might require the entire block of memory to be allocated (without touching the entire memory). However, it does not, the utilized memory in htop does not increase by the size of the array. Why is that not the case?

由于分页,只有与 Numpy 数组关联的虚拟内存中的最后一页被映射。这就是为什么您看不到 htop 有很大变化的原因。但是,如果仔细观察,您应该会看到细微的变化(您平台上的页面大小)。否则,这应该意味着由于其他先前的回收分配,页面已经被映射。实际上,分配库可以预分配内存区域以加快分配速度(通过减少对 OS 的慢速请求的数量)。该库还可以在 Numpy 释放内存时保持内存映射,以加速下一次分配(因为内存不必取消映射然后再次映射)。这在实践中不太可能发生在巨大的数组上,因为对内存消耗的影响太昂贵了。