56

My impression is that in NumPy, two arrays can share the same memory. Take the following example:

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

So clearly b didn't make a copy of a; it just created some new meta-data and attached it to the same memory buffer that a is using. Is there a way to check if two arrays reference the same memory buffer?

My first impression was to use a.data is b.data, but that returns false. I can do a.data == b.data which returns True, but I don't think that checks to make sure a and b share the same memory buffer, only that the block of memory referenced by a and the one referenced by b have the same bytes.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • 2
    Here is the most relevant previously asked question: http://stackoverflow.com/questions/10747748/how-do-i-check-that-two-slices-of-numpy-arrays-are-the-same-or-overlapping – Robert Kern Jul 02 '12 at 09:27
  • 1
    @RobertKern -- Thanks. I had actually seen that post, but since I couldn't find documentation for `numpy.may_share_memory` (other than the built-in `help`), I thought there might be something else -- e.g. `numpy.uses_same_memory_exactly`. (my use case is slightly less general than the other one, so I thought there might be a more definitive answer). Anyway, having seen your name on a few numpy mailing lists, I'm guessing that the answer is "there is no such function". – mgilson Jul 02 '12 at 13:29
  • 2
    `numpy.may_share_memory()` does not show up in the reference manual only due to an accident of the organization of the reference manual. It's the right thing to use. Unfortunately, there is no `uses_same_memory_exactly()` function at the moment. To implement such a function requires solving a bounded linear Diophantine equation, an NP-hard problem. The problem size is usually not too large, but just writing down the algorithm is annoying, so it hasn't been done yet. If we do, it will be incorporated into `numpy.may_share_memory()`, so that's what I recommend using. – Robert Kern Jul 02 '12 at 14:30
  • @RobertKern -- Thanks for the input. I'll be sure to use `np.may_share_memory()`. I use this mostly for debugging/optimization to make sure that I don't gratuitously allocate arrays by accident. Thanks again. – mgilson Jul 02 '12 at 14:35
  • See also: https://stackoverflow.com/questions/44865261/what-is-the-difference-between-numpy-shares-memory-and-numpy-may-share-memory – gerrit May 26 '20 at 07:15

4 Answers4

38

You can use the base attribute to check if an array shares the memory with another array:

>>> import numpy as np
>>> a = np.arange(27)
>>> b = a.reshape((3,3,3))
>>> b.base is a
True
>>> a.base is b
False

Not sure if that solves your problem. The base attribute will be None if the array owns its own memory. Note that an array's base will be another array, even if it is a subset:

>>> c = a[2:]
>>> c.base is a
True
Brionius
  • 13,858
  • 3
  • 38
  • 49
jterrace
  • 64,866
  • 22
  • 157
  • 202
  • This is probably good enough for my purposes. It's unfortunate that it isn't a 2-way street though. I'll wait and see if anything else better pops up. In the meantime, thanks. (+1) – mgilson Jul 02 '12 at 01:35
  • You could do `a.base is b or b.base is a`. – user545424 Jul 02 '12 at 02:55
  • 9
    This is unreliable. Each array may have chains of `.base` attributes, e.g. `a.base.base is b` may be true. Arrays can also be constructed to point at the same memory without sharing the same `.base` objects. – Robert Kern Jul 02 '12 at 09:30
  • @user545424 -- The best I could do with this is `a.base is b or b.base is a or a.base is b.base`, but that seems clunky at best. – mgilson Jul 02 '12 at 11:15
  • @mgilson `def base(a): return a if a.base is None else base(a.base); base(a) is base(b)`. Of course that still doesn't help if arrays share data without having the same eventual base. – ecatmur Jul 02 '12 at 20:09
  • 2
    @jterrace never never trust the base test. try this: `m=matrix(b)`, then `m.base is a` will raise `False`, however, `m.base.base is a` will raise `True`. So one should always relies on `may_share_memory` – Wang Jul 26 '12 at 15:07
10

I think jterrace's answer is probably the best way to go, but here is another possibility.

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

Example:

>>> 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

Here is a plot showing the time for each share_memory(a,a[::2]) call as a function of the number of elements in a on my computer.

share_memory function

user545424
  • 15,713
  • 11
  • 56
  • 70
  • 5
    One can have views that share memory even with different itemsizes. For example, I may get an array as a `float32` with interleaved real and imaginary components and view it as a `complex64` array. A more reliable implementation is in `numpy.may_share_memory()`. – Robert Kern Jul 02 '12 at 09:48
  • @RobertKern: Good point. I've updated my answer. Do you see any potential issues with this solution? – user545424 Jul 02 '12 at 19:20
  • I think I've finally got it right. `share_memory()` requires memory on the order of sum of the sizes of each array, but it's pretty quick. – user545424 Jul 04 '12 at 00:32
10

To solve the problem exactly, you can use

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)

Both np.may_share_memory and np.shares_memory take an optional max_work argument that lets you decide how much effort to put in to ensure no false positives. This problem is NP-complete, so always finding the correct answer can be quite computationally expensive.

b-butler
  • 101
  • 1
  • 2
7

Just do:

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

The second line will return a tuple where the first entry is the memory address and the second is whether the array is read only. Combined with the shape and data type, you can figure out the exact span of memory address that the array covers, so you can also work out from this when one array is a subset of another.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72