Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
e34a333 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 #3044 and
#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
  • Loading branch information
tacaswell committed Aug 23, 2022
commit 9f3861f407006593d5a5f07d24b03e5017f34a1a
9 changes: 0 additions & 9 deletions lib/matplotlib/_pylab_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import atexit
from collections import OrderedDict
import gc


class Gcf:
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down