有没有一种方法可以检查NumPy数组是否共享相同的数据?

58

我的印象是,在NumPy中,两个数组可以共享同一块内存。以以下示例为例:

import numpy as np
a=np.arange(27)
b=a.reshape((3,3,3))
a[0]=5000
print (b[0,0,0]) #5000

#Some tests:
a.data is b.data #False
a.data == b.data #True

c=np.arange(27)
c[0]=5000
a.data == c.data #True ( Same data, not same memory storage ), False positive

很明显,b并没有复制a,它只是创建了一些新的元数据并将其附加到与a使用相同的内存缓冲区。有没有办法检查两个数组是否引用同一内存缓冲区?

我的第一印象是使用a.data is b.data,但这会返回false。我可以使用a.data == b.data,它返回True,但我认为这并不检查ab是否共享相同的内存缓冲区,只是检查ab引用的内存块是否具有相同的字节。


2
以下是最相关的之前提出的问题:https://dev59.com/c2gv5IYBdhLWcg3wPObz - Robert Kern
1
@RobertKern -- 谢谢。我确实看到了那篇文章,但由于我找不到numpy.may_share_memory的文档(除了内置的help),所以我认为可能还有其他东西--例如numpy.uses_same_memory_exactly。(我的用例比另一个用例略微特殊,因此我认为可能会有更明确的答案)。无论如何,既然在几个numpy邮件列表中看到了您的名字,我猜想答案是“没有这样的函数”。 - mgilson
2
numpy.may_share_memory()没有出现在参考手册中,这只是由于参考手册的组织方式不当造成的偶然事件。使用它是正确的选择。不幸的是,目前还没有uses_same_memory_exactly()函数。要实现这样一个函数需要解决一个有界线性丢番图方程,这是一个NP难问题。问题规模通常不会太大,但编写算法很麻烦,所以尚未完成。如果我们完成了,它将被合并到numpy.may_share_memory()中,因此我建议使用它。 - Robert Kern
@RobertKern -- 感谢您的建议。我会确保使用np.may_share_memory()。 我主要用它来进行调试/优化,以确保我不会意外地分配数组。再次感谢。 - mgilson
参见:https://dev59.com/P1cP5IYBdhLWcg3wFmve - gerrit
4个回答

39
你可以使用base属性来检查一个数组是否与另一个数组共享内存:
>>> import numpy as np
>>> a = np.arange(27)
>>> b = a.reshape((3,3,3))
>>> b.base is a
True
>>> a.base is b
False

不确定它是否解决了你的问题。 如果数组拥有自己的内存,则基础属性将为None。 请注意,即使它是一个子集,数组的基础也将是另一个数组:

不确定这是否解决了您的问题。如果数组拥有自己的内存,则基本属性将为None。请注意,即使它是子集,数组的基础仍然是另一个数组:

>>> c = a[2:]
>>> c.base is a
True

这对我的目的可能已经足够好了。不过很遗憾它不是双向的。我会等待看看是否有更好的东西出现。在此期间,谢谢。(+1) - mgilson
你可以这样写 a.base is b or b.base is a - user545424
9
这是不可靠的。每个数组可能都有一系列的 .base 属性,例如 a.base.base is b 可能为真。数组也可以构造成指向相同内存但未共享相同 .base 对象。 - Robert Kern
@user545424 -- 我最好的建议是 a.base is b or b.base is a or a.base is b.base,但这似乎并不是很流畅。 - mgilson
@mgilson def base(a): return a if a.base is None else base(a.base); base(a) is base(b). 当然,如果数组共享数据但没有相同的最终基础,则仍然无法解决问题。 - ecatmur
2
@jterrace永远永远不要信任基准测试。尝试这样做:m=matrix(b),然后m.base is a将引发False,但是,m.base.base is a将引发True。因此,一个人应该始终依赖于may_share_memory - Wang

12

要准确解决问题,您可以使用

import numpy as np

a=np.arange(27)
b=a.reshape((3,3,3))

# Checks exactly by default
np.shares_memory(a, b)

# Checks bounds only
np.may_share_memory(a, b)

两个函数np.may_share_memorynp.shares_memory都可以接受一个可选的max_work参数,让您决定要付出多少努力以确保没有误报。由于这个问题是NP完全问题,因此始终找到正确答案可能非常计算密集。


10

我认为jterrace的答案可能是最好的方法,但这里还有另一种可能性。

def byte_offset(a):
    """Returns a 1-d array of the byte offset of every element in `a`.
    Note that these will not in general be in order."""
    stride_offset = np.ix_(*map(range,a.shape))
    element_offset = sum(i*s for i, s in zip(stride_offset,a.strides))
    element_offset = np.asarray(element_offset).ravel()
    return np.concatenate([element_offset + x for x in range(a.itemsize)])

def share_memory(a, b):
    """Returns the number of shared bytes between arrays `a` and `b`."""
    a_low, a_high = np.byte_bounds(a)
    b_low, b_high = np.byte_bounds(b)

    beg, end = max(a_low,b_low), min(a_high,b_high)

    if end - beg > 0:
        # memory overlaps
        amem = a_low + byte_offset(a)
        bmem = b_low + byte_offset(b)

        return np.intersect1d(amem,bmem).size
    else:
        return 0

示例:

>>> a = np.arange(10)
>>> b = a.reshape((5,2))
>>> c = a[::2]
>>> d = a[1::2]
>>> e = a[0:1]
>>> f = a[0:1]
>>> f = f.reshape(())
>>> share_memory(a,b)
80
>>> share_memory(a,c)
40
>>> share_memory(a,d)
40
>>> share_memory(c,d)
0
>>> share_memory(a,e)
8
>>> share_memory(a,f)
8

这里展示了在我的电脑上,每次调用 share_memory(a,a[::2]) 所需的时间随着 a 中元素数量的变化而变化的图表。

share_memory函数


5
例如,即使具有不同的项大小,一个人可以拥有共享内存的观点。例如,我可能会得到一个交错实部和虚部的“float32”数组,并将其视为“complex64”数组。更可靠的实现在“numpy.may_share_memory()”中。 - Robert Kern
@RobertKern: 很好的观点。我更新了我的答案。您是否看到该解决方案存在潜在问题? - user545424
我想我终于搞对了。share_memory()需要的内存大小是每个数组大小之和,但它非常快。 - user545424

7

Just do:

a = np.arange(27)
a.__array_interface__['data']

第二行将返回一个元组,其中第一个条目是内存地址,第二个条目是数组是否为只读。结合形状和数据类型,您可以确定数组覆盖的确切内存地址范围,因此您也可以从中计算出一个数组是另一个数组的子集的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接