Home > Blockchain >  seeming memory leak in numpy for Mac?
seeming memory leak in numpy for Mac?

Time:11-22

I used the following process the generate a numpy array with size = (720, 720, 3). In principle, it should cost 720 * 720 * 3 * 8Byte = 12.3MB. However, in the ans = memory_benchmark(), it costs 188 MB. Why does it cost much more memory than expected? I think it should have same cost as the line m1 = np.ones((720, 720, 3)).

I have following two Environments. Both have same problem.

Environment1: numpy=1.23.4, memory_profiler=0.61.0, python=3.10.6, MacOS 12.6.1(Intel not M1)

Environment2: numpy=1.19.5, memory_profiler=0.61.0, python=3.8.15, MacOS 12.6.1(Intel not M1)

I did memory profile in the following

import numpy as np
from memory_profiler import profile


@profile
def memory_benchmark():
    m1 = np.ones((720, 720, 3))
    m2 = np.random.randint(128, size=(720, 720, 77, 3))
    a = m2[:, :, :, 0].astype(np.uint16)
    b = m2[:, :, :, 1].astype(np.uint16)
    ans = np.array(m1[b, a].sum(axis=2))
    m2 = None
    a = None
    b = None
    m1 = None
    return ans


@profile
def f():
    ans = memory_benchmark()
    print(ans.shape)
    print("finished")


if __name__ == '__main__':
    f()

(720, 720, 3)
finished

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     5     59.3 MiB     59.3 MiB           1   @profile
     6                                         def memory_benchmark():
     7     71.2 MiB     11.9 MiB           1       m1 = np.ones((720, 720, 3))
     8    984.8 MiB    913.7 MiB           1       m2 = np.random.randint(128, size=(720, 720, 77, 3))
     9   1061.0 MiB     76.1 MiB           1       a = m2[:, :, :, 0].astype(np.uint16)
    10   1137.1 MiB     76.1 MiB           1       b = m2[:, :, :, 1].astype(np.uint16)
    11   1160.9 MiB     23.8 MiB           1       ans = np.array(m1[b, a].sum(axis=2))
    12    247.3 MiB   -913.6 MiB           1       m2 = None
    13    247.3 MiB      0.0 MiB           1       a = None
    14    247.3 MiB      0.0 MiB           1       b = None
    15    247.3 MiB      0.0 MiB           1       m1 = None
    16    247.3 MiB      0.0 MiB           1       return ans


Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    19     59.3 MiB     59.3 MiB           1   @profile
    20                                         def f():
    21    247.3 MiB    188.0 MiB           1       ans = memory_benchmark()
    22    247.3 MiB      0.0 MiB           1       print(ans.shape)
    23    247.3 MiB      0.0 MiB           1       print("finished")



If I print(type(m1[0, 0, 0])) yields <class 'numpy.float64'>, print(type(m2[0, 0, 0, 0])) yields <class 'numpy.int64'>, print(type(ans[0, 0, 0])) yields <class 'numpy.float64'>

However, in my Ubuntu VM, I don't have above problem.

CodePudding user response:

Those numbers look fine to me:

In [772]: 720*720*3*8/1e6
Out[772]: 12.4416

In [773]: 720*720*3*8/1e6 * 77
Out[773]: 958.0032

In [775]: 720*720*77*2/1e6
Out[775]: 79.8336

Evidently once you drop it to 247.3 MiB, the interpreter/numpy decides to "hang on" to that memory, rather than return it to the OS. When tracking memory you are dealing the "choices" of several layers - OS, python interpreter, and numpy's own memory management. One or more of those layers can maintain a "free space" from which it can allocated new objects or arrays.

CodePudding user response:

I can't reproduce the results you're getting. In python 3.7.3, numpy 1.21.4, and memory_profiler 0.61.0, I'm getting the following results


Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    23     57.6 MiB     57.6 MiB           1   @profile
    24                                         def memory_benchmark():
    25     69.5 MiB     11.9 MiB           1       m1 = np.ones((720, 720, 3))
    26    527.4 MiB    457.8 MiB           1       m2 = np.random.randint(128, size=(720, 720, 77, 3))
    27    603.6 MiB     76.3 MiB           1       a = m2[:, :, :, 0].astype(np.uint16)
    28    679.9 MiB     76.3 MiB           1       b = m2[:, :, :, 1].astype(np.uint16)
    29    692.0 MiB     12.1 MiB           1       ans = np.array(m1[b, a].sum(axis=2))
    30    234.3 MiB   -457.7 MiB           1       m2 = None
    31    158.0 MiB    -76.3 MiB           1       a = None
    32     81.7 MiB    -76.3 MiB           1       b = None
    33     69.8 MiB    -11.9 MiB           1       m1 = None
    34     69.8 MiB      0.0 MiB           1       return ans


(720, 720, 3)
finished

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    37     57.6 MiB     57.6 MiB           1   @profile
    38                                         def f():
    39     69.8 MiB     12.2 MiB           1       ans = memory_benchmark()
    40     69.8 MiB      0.0 MiB           1       print(ans.shape)
    41     69.8 MiB      0.0 MiB           1       print("finished")

Printing type(m1[0,0,0,0]) yields <class 'numpy.int32'>, so the 457.8 MiB makes sense. On the other hand, your output seems weird, given that assigning m1 to None reports no difference in memory. Which python & library versions are you using?


Update: In a different machine, with python 3.10.6, numpy 1.23.5, and memory_profiler 0.61.0, I still cannot reproduce the OP output.

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     5     35.6 MiB     35.6 MiB           1   @profile
     6                                         def memory_benchmark():
     7     47.4 MiB     11.8 MiB           1       m1 = np.ones((720, 720, 3))
     8    961.2 MiB    913.8 MiB           1       m2 = np.random.randint(128, size=(720, 720, 77, 3))
     9   1037.4 MiB     76.2 MiB           1       a = m2[:, :, :, 0].astype(np.uint16)
    10   1113.6 MiB     76.2 MiB           1       b = m2[:, :, :, 1].astype(np.uint16)
    11   1125.8 MiB     12.2 MiB           1       ans = np.array(m1[b, a].sum(axis=2))
    12    212.1 MiB   -913.6 MiB           1       m2 = None
    13    136.0 MiB    -76.1 MiB           1       a = None
    14     59.9 MiB    -76.1 MiB           1       b = None
    15     48.0 MiB    -11.9 MiB           1       m1 = None
    16     48.0 MiB      0.0 MiB           1       return ans


(720, 720, 3)
finished

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    19     35.6 MiB     35.6 MiB           1   @profile
    20                                         def f():
    21     48.0 MiB     12.3 MiB           1       ans = memory_benchmark()
    22     48.0 MiB      0.0 MiB           1       print(ans.shape)
    23     48.0 MiB      0.0 MiB           1       print("finished")
  • Related