Repository: numba/numba

Issue:
Memory leak after updating numba from 0.22.1 to 0.23.1
I have some numpy heavy code what I speeded up with numba (no GPU).

It worked beautifully until I updated numba (and a couple of other python packages), after update the mem usage of the code goes up until the OS kills it.
Downgrading numba to the previous version fixed the issue.

I use conda on Ubuntu 14.04 (64 bit).
Good version:
0.22.1-np110py27_0

Bad version:
0.23.1-np110py27_0



Discussion:
Would you be willing to try our development packages from Anaconda.org?

```
conda update -c numba numba
```

This would let us know whether or not we already fixed the issue since 0.23.1 was released.

I too had the same issue (no GPU), and fixed things by downgrading to
0.22.1, about two weeks ago.

In my case, I was running the latest development version (conda install -c
numba numba, as of two weeks ago), and was still having that trouble.

I'll try and produce a minimal not working example, but...I need a lot of
motivation to do so right now :(

On Wed, Feb 24, 2016 at 9:08 AM, Stan Seibert notifications@github.com
wrote:

> Would you be willing to try our development packages from Anaconda.org?
> 
> conda update -c numba numba
> 
> This would let us know whether or not we already fixed the issue since
> 0.23.1 was released.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/numba/numba/issues/1734#issuecomment-188356289.

OK, good to know.  We have several tests looking for reference leaks, but they obviously don't cover all situations.  Any information you can provide would be appreciated, even if you don't have time for a working example.  :)

@seibert  Would you be interested in a copy of my simulation? It is easy to run, and I can remove all the visualization bits, which require other packages (like cairo), so that only numba and numpy are needed.

I started testing things out right now, but then I realized that if I went further, the existing simulations I am running would crash...so, I might have to wait a couple of days before I can really get to it on my computer.

Otherwise, here's my post from a couple of weeks ago, lamenting the memory leaks: https://groups.google.com/a/continuum.io/forum/#!topic/numba-users/OzeAkz9YuPQ

At the time, I wasn't sure what the issue was (I still hadn't downgraded numba), so I theorized that it might be some other things (such as using 64bit numbers on a machine with only 4GB RAM), but those were not culprits in hindsight. Anyway, the section titled "What memory usage pattern are you seeing when running a simulation?" might be of great interest!

Yes, even a non-minimal case would help us.  Thanks!

I have a minimal test case for you:

``` python
import numba
import numpy
import numpy.random

@numba.njit
def evaluate_split(values, split, targets):
    no_targets = targets.shape[0]
    sum = numpy.zeros((49,2), dtype=numpy.float32)

    for i in range(0, no_targets):
        t = targets[i]
        if numpy.int_(values[i, split[0]]) - numpy.int_(values[i, split[1]]) > split[2]:
            sum += t

for i in xrange(0,2000):
    values = numpy.array(numpy.random.randint(0, 255, (10000, 400)), dtype=numpy.uint8)
    split = numpy.array(numpy.random.randint(0,400,3),dtype=numpy.int32)
    targets = numpy.random.rand(10000,49,2)
    evaluate_split(values, split, targets)
    print i
```

(In case you wondering: I removed a lot of irrelevant stuff from the code, so it does not calculate anything meaningful anymore...)

Affected versions: 0.23.1-np110py27_0, 0.23.1-np110py27_214
Not affected version: 0.22.1-np110py27_0

Hope it helps.


Resolving patch:
diff --git a/numba/lowering.py b/numba/lowering.py
--- a/numba/lowering.py
+++ b/numba/lowering.py
@@ -307,14 +307,7 @@ def lower_inst(self, inst):
             return impl(self.builder, (target, index))
 
         elif isinstance(inst, ir.Del):
-            try:
-                # XXX: incorrect Del injection?
-                val = self.loadvar(inst.value)
-            except KeyError:
-                pass
-            else:
-                self.decref(self.typeof(inst.value), val)
-                self._delete_variable(inst.value)
+            self.delvar(inst.value)
 
         elif isinstance(inst, ir.SetAttr):
             target = self.loadvar(inst.target.name)
@@ -772,23 +765,38 @@ def lower_expr(self, resty, expr):
 
         raise NotImplementedError(expr)
 
+    def _alloca_var(self, name, fetype):
+        """
+        Ensure the given variable has an allocated stack slot.
+        """
+        if name not in self.varmap:
+            # If not already defined, allocate it
+            llty = self.context.get_value_type(fetype)
+            ptr = self.alloca_lltype(name, llty)
+            # Remember the pointer
+            self.varmap[name] = ptr
+
     def getvar(self, name):
+        """
+        Get a pointer to the given variable's slot.
+        """
         return self.varmap[name]
 
     def loadvar(self, name):
+        """
+        Load the given variable's value.
+        """
         ptr = self.getvar(name)
         return self.builder.load(ptr)
 
     def storevar(self, value, name):
+        """
+        Store the value into the given variable.
+        """
         fetype = self.typeof(name)
 
         # Define if not already
-        if name not in self.varmap:
-            # If not already defined, allocate it
-            llty = self.context.get_value_type(fetype)
-            ptr = self.alloca_lltype(name, llty)
-            # Remember the pointer
-            self.varmap[name] = ptr
+        self._alloca_var(name, fetype)
 
         # Clean up existing value stored in the variable
         old = self.loadvar(name)
@@ -804,6 +812,21 @@ def storevar(self, value, name):
 
         self.builder.store(value, ptr)
 
+    def delvar(self, name):
+        """
+        Delete the given variable.
+        """
+        fetype = self.typeof(name)
+
+        # Define if not already (may happen if the variable is deleted
+        # at the beginning of a loop, but only set later in the loop)
+        self._alloca_var(name, fetype)
+
+        ptr = self.getvar(name)
+        self.decref(fetype, self.builder.load(ptr))
+        # Zero-fill variable to avoid double frees on subsequent dels
+        self.builder.store(Constant.null(ptr.type.pointee), ptr)
+
     def alloca(self, name, type):
         lltype = self.context.get_value_type(type)
         return self.alloca_lltype(name, lltype)
@@ -822,10 +845,3 @@ def decref(self, typ, val):
             return
 
         self.context.nrt_decref(self.builder, typ, val)
-
-    def _delete_variable(self, varname):
-        """
-        Zero-fill variable to avoid crashing due to extra ir.Del
-        """
-        storage = self.getvar(varname)
-        self.builder.store(Constant.null(storage.type.pointee), storage)


Test patch:
diff --git a/numba/tests/test_nrt_refct.py b/numba/tests/test_nrt_refct.py
--- a/numba/tests/test_nrt_refct.py
+++ b/numba/tests/test_nrt_refct.py
@@ -77,6 +77,30 @@ def if_with_allocation_and_initialization(arr1, test1):
         self.assertEqual(cur_stats.alloc - init_stats.alloc,
                          cur_stats.free - init_stats.free)
 
+    def test_del_at_beginning_of_loop(self):
+        """
+        Test issue #1734
+        """
+        @njit
+        def f(arr):
+            res = 0
+
+            for i in (0, 1):
+                # `del t` is issued here before defining t.  It must be
+                # correctly handled by the lowering phase.
+                t = arr[i]
+                if t[i] > 1:
+                    res += t[i]
+
+            return res
+
+        arr = np.ones((2, 2))
+        init_stats = rtsys.get_allocation_stats()
+        f(arr)
+        cur_stats = rtsys.get_allocation_stats()
+        self.assertEqual(cur_stats.alloc - init_stats.alloc,
+                         cur_stats.free - init_stats.free)
+
 
 if __name__ == '__main__':
     unittest.main()