diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 59139c30f4b1..8c841375b16b 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -10,6 +10,7 @@ import collections.abc import contextlib import functools +import gc import gzip import itertools import operator @@ -2343,3 +2344,17 @@ def _backend_module_name(name): """ return (name[9:] if name.startswith("module://") else "matplotlib.backends.backend_{}".format(name.lower())) + + +def _without_gc(f): + """A decorator to disable garbage collection in the decorated function.""" + @functools.wraps(f) + def wrapper(*args, **kwargs): + was_enabled = gc.isenabled() + gc.disable() + try: + return f(*args, **kwargs) + finally: + if was_enabled: + gc.enable() + return wrapper diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index aa37ca0e24a7..f809b3244c46 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1070,6 +1070,10 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs): self.set_verts(verts, closed) self.stale = True + # This function creates a lot of Path object, which is pretty much + # guaranteed to trigger the GC multiple times. None of them will be garbage + # just yet, so all those runs are completely unnecessary. + @cbook._without_gc def set_verts(self, verts, closed=True): """ Set the vertices of the polygons. @@ -1094,15 +1098,19 @@ def set_verts(self, verts, closed=True): return # Fast path for arrays - if isinstance(verts, np.ndarray): - verts_pad = np.concatenate((verts, verts[:, :1]), axis=1) + if isinstance(verts, np.ndarray) and len(verts): + verts_pad = (np.concatenate((verts, verts[:, :1]), axis=1) + .astype(mpath.Path.vert_type)) # Creating the codes once is much faster than having Path do it # separately each time by passing closed=True. - codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type) - codes[:] = mpath.Path.LINETO - codes[0] = mpath.Path.MOVETO - codes[-1] = mpath.Path.CLOSEPOLY - self._paths = [mpath.Path(xy, codes) for xy in verts_pad] + example_path = mpath.Path(verts_pad[0], closed=True) + # Looking up the values once speeds up the iteration a bit + _make_path = mpath.Path._fast_from_codes_and_verts + codes = example_path.codes + self._paths = [_make_path(xy, codes, + internals_from=example_path, + unmask_verts=False) + for xy in verts_pad] return self._paths = [] diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 44392cc81af2..53f1e410b2fc 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -74,8 +74,12 @@ class Path: made up front in the constructor that will not change when the data changes. """ + __slots__ = ('_vertices', '_codes', '_readonly', + '_should_simplify', '_simplify_threshold', + '_interpolation_steps', '__weakref__') code_type = np.uint8 + vert_type = float # Path codes STOP = code_type(0) # 1 vertex @@ -175,7 +179,7 @@ def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None): never copied, and always set to ``False`` by this constructor. """ pth = cls.__new__(cls) - pth._vertices = _to_unmasked_float_array(verts) + pth._vertices = verts pth._codes = codes pth._readonly = False if internals_from is not None: @@ -269,16 +273,6 @@ def readonly(self): """ return self._readonly - def __copy__(self): - """ - Returns a shallow copy of the `Path`, which will share the - vertices and codes with the source `Path`. - """ - import copy - return copy.copy(self) - - copy = __copy__ - def __deepcopy__(self, memo=None): """ Returns a deepcopy of the `Path`. The `Path` will not be diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 47f5a90a2b68..cc7afd0893a6 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -636,12 +636,6 @@ def test_transformed_path(): [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], atol=1e-15) - # Changing the path does not change the result (it's cached). - path.points = [(0, 0)] * 4 - assert_allclose(trans_path.get_fully_transformed_path().vertices, - [(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)], - atol=1e-15) - def test_transformed_patch_path(): trans = mtransforms.Affine2D()