From 9f3861f407006593d5a5f07d24b03e5017f34a1a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 22 Aug 2022 21:07:29 -0400 Subject: [PATCH] FIX: do not try to help CPython with garbage collection Matplotlib has a large number of circular references (between figure and manager, between axes and figure, axes and artist, figure and canvas, and ...) so when the user drops their last reference to a `Figure` (and clears it from pyplot's state), the objects will not immediately deleted. To account for this we have long (goes back to e34a333d00814124d3e19d462b9d78ac35e7a49a the "reorganize code" commit in 2004 which is the end of history for much of the code) had a `gc.collect()` in the close logic in order to promptly clean up after our selves. However, unconditionally calling `gc.collect` and be a major performance issue (see https://github.com/matplotlib/matplotlib/issues/3044 and https://github.com/matplotlib/matplotlib/pull/3045) because if there are a large number of long-lived user objects Python will spend a lot of time checking objects that are not going away are never going away. Instead of doing a full collection we switched to clearing out the lowest two generations. However this both not doing what we want (as most of our objects will actually survive) and due to clearing out the first generation opened us up to having unbounded memory usage. In cases with a very tight loop between creating the figure and destroying it (e.g. `plt.figure(); plt.close()`) the first generation will never grow large enough for Python to consider running the collection on the higher generations. This will lead to un-bounded memory usage as the long-lived objects are never re-considered to look for reference cycles and hence are never deleted because their reference counts will never go to zero. closes #23701 --- lib/matplotlib/_pylab_helpers.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 24fcb81fc9b5..d32a69d4ff99 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -4,7 +4,6 @@ import atexit from collections import OrderedDict -import gc class Gcf: @@ -66,10 +65,6 @@ def destroy(cls, num): manager.canvas.mpl_disconnect(manager._cidgcf) manager.destroy() del manager, num - # Full cyclic garbage collection may be too expensive to do on every - # figure destruction, so we collect only the youngest two generations. - # see: https://github.com/matplotlib/matplotlib/pull/3045 - gc.collect(1) @classmethod def destroy_fig(cls, fig): @@ -82,14 +77,10 @@ def destroy_fig(cls, fig): @classmethod def destroy_all(cls): """Destroy all figures.""" - # Reimport gc in case the module globals have already been removed - # during interpreter shutdown. - import gc for manager in list(cls.figs.values()): manager.canvas.mpl_disconnect(manager._cidgcf) manager.destroy() cls.figs.clear() - gc.collect(1) @classmethod def has_fignum(cls, num):