如何在 Numpy 中就地扩展数组?

Posted

技术标签:

【中文标题】如何在 Numpy 中就地扩展数组?【英文标题】:How to extend an array in-place in Numpy? 【发布时间】:2012-10-24 07:26:25 【问题描述】:

目前,我有一些这样的代码

import numpy as np
ret = np.array([])
for i in range(100000):
  tmp =  get_input(i)
  ret = np.append(ret, np.zeros(len(tmp)))
  ret = np.append(ret, np.ones(fixed_length))

我认为这段代码效率不高因为np.append需要返回数组的副本而不是就地修改ret

我想知道是否可以将extend 用于这样的 numpy 数组:

import numpy as np
from somewhere import np_extend
ret = np.array([])
for i in range(100000):
  tmp =  get_input(i)
  np_extend(ret, np.zeros(len(tmp)))
  np_extend(ret, np.ones(fixed_length))

这样extend 会更有效率。 有人对此有想法吗? 谢谢!

【问题讨论】:

【参考方案1】:

想象一个 numpy 数组占用一个连续的内存块。现在想象一下其他对象,比如其他 numpy 数组,它们占用了我们 numpy 数组左右两侧的内存。没有空间可以附加或扩展我们的 numpy 数组。 numpy 数组中的底层数据总是占用一个连续的内存块。

因此,任何附加或扩展我们的 numpy 数组的请求只能通过分配一个全新的更大内存块、将旧数据复制到新块中然后附加或扩展来满足。

所以:

    它不会原地发生。 不会有效率。

【讨论】:

如何将linked listblock of memory 结合起来提供extend 功能? 这很好,但是您必须重新实现链表上的所有 numpy 方法,同时透明地隐藏底层 numpy 数组不连续的事实。此外,一些 numpy 函数调用用 C 或 Fortran 编写的函数(例如 LAPACK),它们利用了输入是连续内存块这一事实。要将链接的非连续数据发送到这些函数,您必须进行分配和复制,所以这又是低效的。 但是,如果没有其他对象可以扩大内存块,它可能是有效的。我会说你的答案不完整,因为其他内存分配总是阻止就地调整大小的假设是不正确的。例如,在 C 中,您有 realloc。我对一个好的答案的期望是说明numpy 是否也有realloc-equivalent,如果没有:为什么不呢?我也经常想知道,为什么现代 64 位机器拥有大量的地址空间,为什么没有更多地使用智能分页方法来为此目的在分配之间留出虚拟空间。 @Herbert Numpy 的.resize() 确实在内部使用了realloc(),但Numpy 遵循Python 的自动内存管理约定,因此直接暴露realloc() 之类的东西是没有意义的。通常,ndarray 是针对具有静态大小而不是动态数组的大型数组量身定制的。调整性能不是主要目标。 Numpy 使用的malloc 实现可以使用您建议的复杂分配方案,但这与 Numpy 无关。 @Praxeolitic 您的声明是有效的,但不要与我的相矛盾:@unutbu 声明 resize 永远不会就地发生并且永远不会有效。这是不正确的。如果使用realloc,它可能就地发生并且它可能是有效的。这取决于内存管理实现。因此,从 python 的角度来看,没有办法做出@unutbu 所做的声明。关于建议的分配方案,我的观点是,在我看来,在效率和动态大小之间进行权衡的需求一直是人为的,即使 numpy 解决这可能不是问题。【参考方案2】:

您可以使用 ndarrays 的 .resize() 方法。它要求内存不被其他数组/变量引用。

import numpy as np
ret = np.array([])
for i in range(100):
    tmp = np.random.rand(np.random.randint(1, 100))
    ret.resize(len(ret) + len(tmp)) # <- ret is not referred to by anything else,
                                    #    so this works
    ret[-len(tmp):] = tmp

使用通常的数组内存重分配方案可以提高效率。

【讨论】:

【参考方案3】:

通常的处理方式是这样的:

import numpy as np
ret = []
for i in range(100000):
  tmp =  get_input(i)
  ret.append(np.zeros(len(tmp)))
  ret.append(np.zeros(fixed_length))
ret = np.concatenate(ret)

由于其他答案的原因,通常不可能在不复制数据的情况下扩展数组。

【讨论】:

【参考方案4】:

我在研究就地 numpy 插入方法时遇到了这个问题。

在阅读此处给出的答案时,我想到了另一种选择(可能是一个幼稚的想法,但仍然是一个想法):为什么不将 numpy 数组转换回列表,附加任何你想附加的内容并将其重新转换回数组?

如果您需要完成许多插入操作,您可以创建一种“列表缓存”,您可以在其中放置所有插入,然后一步将它们插入到列表中。

当然,如果有人试图不惜一切代价避免转换为列表并返回到 numpy,这不是一种选择。

【讨论】:

这在技术上是可行的,但我认为它有点忽略了要点——你将编写几乎相同数量的步骤,但没有提高效率

以上是关于如何在 Numpy 中就地扩展数组?的主要内容,如果未能解决你的问题,请参考以下文章

就地映射 NumPy 数组

将numpy数组设置为切片而不进行任何就地操作

从 C 扩展对 Numpy 数组进行操作,无需内存复制

如何修改 Spark 数据框中的 numpy 数组?

再次对 Numpy 数组进行就地类型转换?

如何在 Python 中就地对整数数组进行排序?