Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leaks in libsingular polynomial evaluation and substitution #27261

Open
simon-king-jena opened this issue Feb 12, 2019 · 90 comments
Open

Comments

@simon-king-jena
Copy link
Member

The following two examples leak memory (in sagemath from 8.9 to 9.3.rc5)

sage: R = PolynomialRing(ZZ, 'x', 50)
sage: d = {str(g): g for g in R.gens()}
sage: p = sum(d.values())
sage: import gc
sage: mem0 = get_memory_usage()
sage: for i in range(20):
....:     for _ in range(50):
....:         _ = p.subs(**d)
....:     _ = gc.collect()
....:     mem1 = get_memory_usage()
....:     if mem1 > mem0:
....:         print(i, mem1-mem0)
....:         mem0 = mem1

and

sage: R.<x, y> = ZZ[]
sage: p = (x + y)**100
sage: mem0 = get_memory_usage()
sage: for i in range(20):
....:     _ = p(x + y, y)
....:     _ = gc.collect()
....:     mem1 = get_memory_usage()
....:     if mem1 > mem0:
....:         print(i, mem1-mem0)

This was reported on sage-devel and ask.sagemath.org.

We fix .subs (the first example above) by modifying the appropriate singular call. We identified two possibly related memory leaks in singular

In the mean time, we use the non-satisfactory solution of going via naive substitution in Python. To do so, we moved the generic implementation on the base class MPolynomial.

See also #13447 also related to memory handling in singular.

Upstream: Reported upstream. No feedback yet.

CC: @nbruin @malb

Component: memleak

Keywords: libsingular polynomial memleak

Author: Vincent Delecroix, ​Dima Pasechnik

Branch/Commit: u/vdelecroix/27261 @ edf8847

Reviewer: Dima Pasechnik

Issue created by migration from https://trac.sagemath.org/ticket/27261

@simon-king-jena
Copy link
Member Author

comment:1

For testing, I changed the code so that the id() of any newly created MPolynomial_libsingular instance is stored in some set, and is removed from the set as soon as it becomes garbage collected.

Result: In the above examples, the MPolynomial_libsingular instances are correctly garbage collected.

Conclusion: The memleak occurs in Sage's usage of libsingular. Apparently the underlying data of a polynomial are not correctly freed when it is deallocated.

@simon-king-jena
Copy link
Member Author

comment:2

Or, another possibility: Some temporary libsingular data created during the computation of Q is not freed.

To detect this, I will try to make the example more fine-grained: Test if the leak occurs if the only arithmetic operation involved is _add_, _mul_ or __pow__, respectively. (X+Y)<sup>120*Y</sup>100 mixes these three operations.

@simon-king-jena
Copy link
Member Author

comment:3

Interestingly, creating a copy of P from scratch does not leak:

sage: import gc
sage: R.<X,Y> = ZZ[]
sage: P = (X+Y)^120*Y^100
sage: mem0 = get_memory_usage()
sage: for i in range(500):
....:     Q = (X+Y)^120*Y^100
....:     del Q
....:     _ = gc.collect()
....:     mem1 = get_memory_usage()
....:     if mem1>mem0:
....:         print(i, mem1-mem0)
....:         mem0 = mem1
....:         
(0, 0.1484375)
sage: 

So, it seems that calling the polynomial (i.e., P(X,Y)) is what is leaking!

@simon-king-jena
Copy link
Member Author

comment:4

Note that there previously was a leak in polynomial evaluation, as by comment in sage.libs.singular.polynomial.singular_polynomial_call - see #16958. So, perhaps it didn't get completely fixed?

@simon-king-jena
Copy link
Member Author

comment:5

singular_polynomial_call copies the input data before doing the actual call. This, I think, is a waste of time. I tested that taking the copy is not causing the leak, but while we are at it, it could as well be improved, too.

@simon-king-jena
Copy link
Member Author

comment:6

Also, singular_polynomial_call has an argument poly *(*get_element)(object) that is (at least in the Sage library) always assigned with the function MPolynomial_libsingular_get_element, which does nothing more than return the ._poly of an MPolynomial_libsingular.

So, basically the get_element argument is void. Should it be removed?

@simon-king-jena simon-king-jena changed the title Fix a memory leak in libsingular polynomial arithmetic Fix a memory leak in libsingular polynomial evaluation Feb 12, 2019
@embray
Copy link
Contributor

embray commented Mar 25, 2019

comment:8

Ticket retargeted after milestone closed (if you don't believe this ticket is appropriate for the Sage 8.8 release please retarget manually)

@embray embray modified the milestones: sage-8.7, sage-8.8 Mar 25, 2019
@embray
Copy link
Contributor

embray commented Jun 14, 2019

comment:9

As the Sage-8.8 release milestone is pending, we should delete the sage-8.8 milestone for tickets that are not actively being worked on or that still require significant work to move forward. If you feel that this ticket should be included in the next Sage release at the soonest please set its milestone to the next release milestone (sage-8.9).

@embray embray removed this from the sage-8.8 milestone Jun 14, 2019
@videlec
Copy link
Contributor

videlec commented May 6, 2021

comment:10

Stumble upon a similar problem using .subs()

sage: R.<X,Y> = ZZ[] 
....: import gc 
....: mem0 = get_memory_usage() 
....: for i in range(1000): 
....:     for _ in range(100): 
....:         _ = (X + Y).subs(X=X, Y=Y) 
....:     _ = gc.collect() 
....:     mem1 = get_memory_usage() 
....:     if mem1>mem0: 
....:         print(i, mem1-mem0) 
....:         mem0 = mem1                                                                                                                                                                      
78 0.1328125
100 0.1328125
121 0.12890625
142 0.12890625
164 0.1328125
172 2.0
185 0.12890625
206 0.12890625
228 0.1328125
...

See also https://ask.sagemath.org/question/29444/high-memory-usage-when-substituting-variables/

@videlec
Copy link
Contributor

videlec commented May 6, 2021

comment:11

Since __call__ calls subs() I gues the latter is the culprit.

@videlec
Copy link
Contributor

videlec commented May 7, 2021

Commit: ee9a54a

@videlec
Copy link
Contributor

videlec commented May 7, 2021

New commits:

ee9a54a27261: fix memory leak in polynomial substitution

@videlec
Copy link
Contributor

videlec commented May 7, 2021

Changed dependencies from #13447 to none

@videlec
Copy link
Contributor

videlec commented May 7, 2021

Branch: u/vdelecroix/27261

@videlec videlec added this to the sage-9.4 milestone May 7, 2021
@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 7, 2021

Changed commit from ee9a54a to 00b85c8

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 7, 2021

Branch pushed to git repo; I updated commit sha1. New commits:

00b85c827261: doctest subs leak

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 7, 2021

Branch pushed to git repo; I updated commit sha1. This was a forced push. New commits:

bd4d41f27261: doctest subs and `__call__` leak

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 23, 2021

Changed commit from 394d173 to 58ba726

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 23, 2021

Branch pushed to git repo; I updated commit sha1. New commits:

58ba72627261: more robust leak test

@videlec
Copy link
Contributor

videlec commented May 23, 2021

comment:58

Replying to @dimpase:

I've opened Singular/Singular#1090

Apparently, partially fixed in b983a8a.

@mwageringel
Copy link

comment:59

This ask-sage question reports that there is also a performance problem with polynomial evaluation. Could this be related to the issue of this ticket? The example from the linked question is about 10 times slower than the naive Python substitution.

@videlec
Copy link
Contributor

videlec commented May 27, 2021

comment:60

Replying to @mwageringel:

This ask-sage question reports that there is also a performance problem with polynomial evaluation. Could this be related to the issue of this ticket? The example from the linked question is about 10 times slower than the naive Python substitution.

This ticket is not about performance issue. But as I mentioned in the ask question switching to naive Python evaluation as done in my branch actually speeds up the evaluation.

@videlec
Copy link
Contributor

videlec commented May 28, 2021

comment:61

ping?

@dimpase
Copy link
Member

dimpase commented May 28, 2021

comment:62

testing...

@dimpase
Copy link
Member

dimpase commented May 28, 2021

comment:63

lgtm

@dimpase
Copy link
Member

dimpase commented May 28, 2021

Reviewer: Dima Pasechnik

@videlec
Copy link
Contributor

videlec commented May 28, 2021

comment:64

thx

@dimpase
Copy link
Member

dimpase commented May 28, 2021

comment:65

one test broken on macOS:

File "src/sage/rings/polynomial/multi_polynomial_libsingular.pyx", line 153, in sage.rings.polynomial.multi_polynomial_libsingular
Failed example:
    for i in range(30):
        n = leak_subs(20)
        print("Leaked {} bytes".format(n))
        if n == 0:
            zeros += 1
            if zeros >= 6:
                print("done")
                break
        else:
            zeros = 0
Expected:
    Leaked ...
    ...
    Leaked 0 bytes
    done
Got:
    Leaked 184549376 bytes
    Leaked 12582912 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 8388608 bytes
    Leaked 0 bytes
    Leaked 37748736 bytes
    Leaked 0 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 37748736 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 37748736 bytes
    Leaked 4194304 bytes
    Leaked 0 bytes
    Leaked 0 bytes
    Leaked 4194304 bytes
**********************************************************************
1 item had failures:
   1 of  55 in sage.rings.polynomial.multi_polynomial_libsingular

(tested together with #30801, but I guess it doesn't matter.

@videlec
Copy link
Contributor

videlec commented May 28, 2021

comment:66

The sagemath-singular interface is worse than what I thought. Switching to a generic subs function also leak!

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 28, 2021

Branch pushed to git repo; I updated commit sha1. New commits:

ddc9a6b27261: use generic subs
edf884727261: move tests in an independent file

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented May 28, 2021

Changed commit from 58ba726 to edf8847

@dimpase
Copy link
Member

dimpase commented May 29, 2021

comment:68

how about adding latest upstream fixes as patches?

@mkoeppe mkoeppe modified the milestones: sage-9.4, sage-9.5 Aug 22, 2021
@mkoeppe mkoeppe modified the milestones: sage-9.5, sage-9.6 Dec 18, 2021
@mkoeppe mkoeppe modified the milestones: sage-9.6, sage-9.7 May 3, 2022
@mkoeppe mkoeppe modified the milestones: sage-9.7, sage-9.8 Sep 19, 2022
@mkoeppe mkoeppe removed this from the sage-9.8 milestone Jan 29, 2023
@dimpase
Copy link
Member

dimpase commented Jul 17, 2023

still leaks with Sage 10.1.beta6 (Singular 4.3.2)
As get_memory_usage is gone, I'm running

sage: R = PolynomialRing(ZZ, 'x', 50)
sage: d = {str(g): g for g in R.gens()}
sage: p = sum(d.values())
sage: import gcsage: while (1):
....:     for _ in range(50):
....:         _ = p.subs(**d)
....:     _ = gc.collect()

and observing memory consumption growing in another ternimal running top

@maxale
Copy link
Contributor

maxale commented Sep 22, 2023

If someone wants to monitor memory leak directly in Sage, it can be done via psutil module. Here is a modified example:

import os
import gc
import psutil
import time

def _mem(pid=os.getpid()):
    gc.collect()
    process = psutil.Process(pid)
    return process.memory_full_info()

def test_leak():
    R = PolynomialRing(ZZ, 'x', 50)
    d = {str(g): g for g in R.gens()}
    p = sum(d.values())
    mytime = time.time()
    while True:
        _ = p.subs(**d)
        if time.time() - mytime > 10:   # every 10 seconds
            print(_mem())
            mytime = time.time()

Here test_leak() prints memory usage (after garbage collection) every 10 seconds, which goes like this:

pfullmem(rss=808443904, vms=2267947008, shared=79335424, text=2822144, lib=0, data=790425600, dirty=0, uss=788611072, pss=794329088, swap=0)

pfullmem(rss=1369354240, vms=2828382208, shared=79335424, text=2822144, lib=0, data=1350860800, dirty=0, uss=1349472256, pss=1355190272, swap=0)

pfullmem(rss=1940168704, vms=3398918144, shared=79335424, text=2822144, lib=0, data=1921396736, dirty=0, uss=1920376832, pss=1926094848, swap=0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants