Repository: numba/numba

Issue:
Memory leak in np.linalg.inv
Numba implementation of np.linalg.inv is leaking memory. The following script demonstrates it (and check that np.dot does not).

I am willing to help fix this, and I would appreciate any pointers in where to look at and what to look for.

``` python
# -*- coding: utf-8 -*-
import gc
import tracemalloc
import numpy as np
from numba import njit


@njit
def dot():
    a = np.ones((100, 100))
    c = np.dot(a, a)
    return c

dot()


@njit
def inv():
    a = np.diag(np.linspace(1, 2, 100))
    c = np.linalg.inv(a)
    return c

inv()

tracemalloc.start()

print('Dot')
for i in range(100):
    d = dot()
    print(tracemalloc.get_traced_memory())

print('Inv')
for i in range(100):
    d = inv()
    print(tracemalloc.get_traced_memory())

gc.collect()
print(tracemalloc.get_traced_memory())

```

Leads to : 

```
Dot
(80416, 160416)
(80448, 240624)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
(80448, 240656)
Inv
(132944, 293152)
(265368, 345472)
(397792, 477896)
(530216, 610320)
(662640, 742744)
(795064, 875168)
(927488, 1007592)
(1059912, 1140016)
(1192336, 1272440)
(1324760, 1404864)
(1457184, 1537288)
(1589608, 1669712)
(1722032, 1802136)
(1854456, 1934560)
(1986880, 2066984)
(2119304, 2199408)
(2251728, 2331832)
(2384152, 2464256)
(2516576, 2596680)
(2649000, 2729104)
(2781424, 2861528)
(2913848, 2993952)
(3046272, 3126376)
(3178696, 3258800)
(3311120, 3391224)
(3443544, 3523648)
(3575968, 3656072)
(3708392, 3788496)
(3840816, 3920920)
(3973240, 4053344)
(4105664, 4185768)
(4238088, 4318192)
(4370512, 4450616)
(4502936, 4583040)
(4635360, 4715464)
(4767784, 4847888)
(4900208, 4980312)
(5032632, 5112736)
(5165056, 5245160)
(5297480, 5377584)
(5429904, 5510008)
(5562328, 5642432)
(5694752, 5774856)
(5827176, 5907280)
(5959600, 6039704)
(6092024, 6172128)
(6224448, 6304552)
(6356872, 6436976)
(6489296, 6569400)
(6621720, 6701824)
(6754144, 6834248)
(6886568, 6966672)
(7018992, 7099096)
(7151416, 7231520)
(7283840, 7363944)
(7416264, 7496368)
(7548688, 7628792)
(7681112, 7761216)
(7813536, 7893640)
(7945960, 8026064)
(8078384, 8158488)
(8210808, 8290912)
(8343232, 8423336)
(8475656, 8555760)
(8608080, 8688184)
(8740504, 8820608)
(8872928, 8953032)
(9005352, 9085456)
(9137776, 9217880)
(9270200, 9350304)
(9402624, 9482728)
(9535048, 9615152)
(9667472, 9747576)
(9799896, 9880000)
(9932320, 10012424)
(10064744, 10144848)
(10197168, 10277272)
(10329592, 10409696)
(10462016, 10542120)
(10594440, 10674544)
(10726864, 10806968)
(10859288, 10939392)
(10991712, 11071816)
(11124136, 11204240)
(11256560, 11336664)
(11388984, 11469088)
(11521408, 11601512)
(11653832, 11733936)
(11786256, 11866360)
(11918680, 11998784)
(12051104, 12131208)
(12183528, 12263632)
(12315952, 12396056)
(12448376, 12528480)
(12580800, 12660904)
(12713224, 12793328)
(12845648, 12925752)
(12978072, 13058176)
(13110496, 13190600)
(13242920, 13323024)
(13242888, 13323024)
```



Discussion:
I think that one incorrect thing is the use of impl_ret_borrowed instead of impl_ret_new_ref at https://github.com/numba/numba/blob/master/numba/targets/linalg.py#L641 but this alone does not fix the issue.

It seems the `i` matrix (holding intermediate results) is never deallocated? Numba doesn't have a GC, we have to manage reference counts explicitly.

The same holds for work. How do I deallocate the memory ? 

Calling `context.nrt_decref` should be sufficient (I'm assuming `empty_nd_impl` initialized the refcount to 1...).

Should I do the same for values allocated through cgutils.alloca_once ?

Ah no, alloca_once() just allocates stack slot (it's a compile-time instruction really). Those ones are totally ok.


Resolving patch:
diff --git a/numba/targets/linalg.py b/numba/targets/linalg.py
--- a/numba/targets/linalg.py
+++ b/numba/targets/linalg.py
@@ -558,6 +558,7 @@ def create_out(a):
     # Allocate the array in which the pivot indices are stored.
     ipiv_t = types.Array(types.intc, 1, 'C')
     i = _empty_nd_impl(context, builder, ipiv_t, (m,))
+    ipiv = i._getvalue()
 
     info = cgutils.alloca_once(builder, ll_intc)
 
@@ -574,10 +575,12 @@ def create_out(a):
         with builder.if_else(invalid_arg) as (then, otherwise):
             raise_err = context.call_conv.return_user_exc
             with then:
+                context.nrt_decref(builder, ipiv_t, ipiv)
                 raise_err(builder, ValueError,
                           ('One argument passed to getrf is invalid',)
                           )
             with otherwise:
+                context.nrt_decref(builder, ipiv_t, ipiv)
                 raise_err(builder, ValueError,
                           ('Matrix is singular and cannot be inverted',)
                           )
@@ -592,6 +595,7 @@ def create_out(a):
     lapack_error = builder.icmp_signed('!=', info_val, zero)
 
     with builder.if_then(lapack_error, False):
+        context.nrt_decref(builder, ipiv_t, ipiv)
         raise_err = context.call_conv.return_user_exc
         raise_err(builder, ValueError,
                   ('One argument passed to getri is invalid',)
@@ -626,6 +630,9 @@ def allocate_work(x, size):
     lapack_error = builder.icmp_signed('!=', info_val, zero)
     invalid_arg = builder.icmp_signed('<', info_val, zero)
 
+    context.nrt_decref(builder, wty, work)
+    context.nrt_decref(builder, ipiv_t, ipiv)
+
     with builder.if_then(lapack_error, False):
         with builder.if_else(invalid_arg) as (then, otherwise):
             raise_err = context.call_conv.return_user_exc
@@ -638,7 +645,7 @@ def allocate_work(x, size):
                           ('Matrix is singular and cannot be inverted',)
                           )
 
-    return impl_ret_borrowed(context, builder, sig.return_type, out)
+    return impl_ret_new_ref(context, builder, sig.return_type, out)
 
 
 @lower_builtin(numpy.linalg.inv, types.Array)


Test patch: