Skip to content

Commit 230325d

Browse files
committed
Make Collection.set_paths even faster
1 parent ef9fc20 commit 230325d

File tree

4 files changed

+45
-41
lines changed

4 files changed

+45
-41
lines changed

lib/matplotlib/cbook/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import os
1717
from pathlib import Path
1818
import re
19+
import gc
1920
import shlex
2021
import subprocess
2122
import sys
@@ -2343,3 +2344,17 @@ def _backend_module_name(name):
23432344
"""
23442345
return (name[9:] if name.startswith("module://")
23452346
else "matplotlib.backends.backend_{}".format(name.lower()))
2347+
2348+
2349+
def _without_gc(f):
2350+
"""A decorator to disable garbage collection in the decorated function."""
2351+
@functools.wraps(f)
2352+
def wrapper(*args, **kwargs):
2353+
was_enabled = gc.isenabled()
2354+
gc.disable()
2355+
try:
2356+
return f(*args, **kwargs)
2357+
finally:
2358+
if was_enabled:
2359+
gc.enable()
2360+
return wrapper

lib/matplotlib/collections.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,10 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs):
10701070
self.set_verts(verts, closed)
10711071
self.stale = True
10721072

1073+
# This function creates a lot of Path object, which is pretty much
1074+
# guaranteed to trigger the GC multiple times. None of them will be garbage
1075+
# just yet, so all those runs are completely unnecessary.
1076+
@cbook._without_gc
10731077
def set_verts(self, verts, closed=True):
10741078
"""
10751079
Set the vertices of the polygons.
@@ -1094,15 +1098,19 @@ def set_verts(self, verts, closed=True):
10941098
return
10951099

10961100
# Fast path for arrays
1097-
if isinstance(verts, np.ndarray):
1098-
verts_pad = np.concatenate((verts, verts[:, :1]), axis=1)
1101+
if isinstance(verts, np.ndarray) and len(verts):
1102+
verts_pad = (np.concatenate((verts, verts[:, :1]), axis=1)
1103+
.astype(mpath.Path.verts_type))
10991104
# Creating the codes once is much faster than having Path do it
11001105
# separately each time by passing closed=True.
1101-
codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type)
1102-
codes[:] = mpath.Path.LINETO
1103-
codes[0] = mpath.Path.MOVETO
1104-
codes[-1] = mpath.Path.CLOSEPOLY
1105-
self._paths = [mpath.Path(xy, codes) for xy in verts_pad]
1106+
example_path = mpath.Path(verts_pad[0], closed=True)
1107+
# Looking up the values once speeds up the iteration a bit
1108+
_make_path = mpath.Path._fast_from_codes_and_verts
1109+
codes = example_path.codes
1110+
self._paths = [_make_path(xy, codes,
1111+
internals_from=example_path,
1112+
unmask_verts=False)
1113+
for xy in verts_pad]
11061114
return
11071115

11081116
self._paths = []

lib/matplotlib/path.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,12 @@ class Path:
7474
made up front in the constructor that will not change when the
7575
data changes.
7676
"""
77+
__slots__ = ('_vertices', '_codes', '_readonly',
78+
'_should_simplify', '_simplify_threshold',
79+
'_interpolation_steps', '__weakref__')
7780

7881
code_type = np.uint8
82+
verts_type = float
7983

8084
# Path codes
8185
STOP = code_type(0) # 1 vertex
@@ -160,7 +164,8 @@ def __init__(self, vertices, codes=None, _interpolation_steps=1,
160164
self._readonly = False
161165

162166
@classmethod
163-
def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None):
167+
def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None,
168+
unmask_verts=True):
164169
"""
165170
Creates a Path instance without the expense of calling the constructor.
166171
@@ -173,9 +178,17 @@ def _fast_from_codes_and_verts(cls, verts, codes, internals_from=None):
173178
``should_simplify``, ``simplify_threshold``, and
174179
``interpolation_steps`` will be copied. Note that ``readonly`` is
175180
never copied, and always set to ``False`` by this constructor.
181+
unmask_verts : bool
182+
If False, vertices should be an unmasked NumPy array with dtype
183+
set to Path.verts_type.
176184
"""
177185
pth = cls.__new__(cls)
178-
pth._vertices = _to_unmasked_float_array(verts)
186+
# NOTE: _to_unmasked_float_array has non-trivial overhead in tight
187+
# loops, so we allow skipping it if the caller wants to
188+
if unmask_verts:
189+
pth._vertices = _to_unmasked_float_array(verts)
190+
else:
191+
pth._vertices = verts
179192
pth._codes = codes
180193
pth._readonly = False
181194
if internals_from is not None:
@@ -269,16 +282,6 @@ def readonly(self):
269282
"""
270283
return self._readonly
271284

272-
def __copy__(self):
273-
"""
274-
Returns a shallow copy of the `Path`, which will share the
275-
vertices and codes with the source `Path`.
276-
"""
277-
import copy
278-
return copy.copy(self)
279-
280-
copy = __copy__
281-
282285
def __deepcopy__(self, memo=None):
283286
"""
284287
Returns a deepcopy of the `Path`. The `Path` will not be

lib/matplotlib/tests/test_transforms.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -621,28 +621,6 @@ def test_invalid_arguments():
621621
t.transform([[1, 2, 3]])
622622

623623

624-
def test_transformed_path():
625-
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
626-
path = Path(points, closed=True)
627-
628-
trans = mtransforms.Affine2D()
629-
trans_path = mtransforms.TransformedPath(path, trans)
630-
assert_allclose(trans_path.get_fully_transformed_path().vertices, points)
631-
632-
# Changing the transform should change the result.
633-
r2 = 1 / np.sqrt(2)
634-
trans.rotate(np.pi / 4)
635-
assert_allclose(trans_path.get_fully_transformed_path().vertices,
636-
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
637-
atol=1e-15)
638-
639-
# Changing the path does not change the result (it's cached).
640-
path.points = [(0, 0)] * 4
641-
assert_allclose(trans_path.get_fully_transformed_path().vertices,
642-
[(0, 0), (r2, r2), (0, 2 * r2), (-r2, r2)],
643-
atol=1e-15)
644-
645-
646624
def test_transformed_patch_path():
647625
trans = mtransforms.Affine2D()
648626
patch = mpatches.Wedge((0, 0), 1, 45, 135, transform=trans)

0 commit comments

Comments
 (0)