Skip to content

Commit 7a1bae7

Browse files
committed
Make Collection.set_paths even faster
1 parent 76ddc2d commit 7a1bae7

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

lib/matplotlib/cbook/__init__.py

Lines changed: 14 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
@@ -2337,3 +2338,16 @@ def _backend_module_name(name):
23372338
"""
23382339
return (name[9:] if name.startswith("module://")
23392340
else "matplotlib.backends.backend_{}".format(name.lower()))
2341+
2342+
2343+
def _without_gc(f):
2344+
@functools.wraps(f)
2345+
def wrapper(*args, **kwargs):
2346+
was_enabled = gc.isenabled()
2347+
gc.disable()
2348+
try:
2349+
return f(*args, **kwargs)
2350+
finally:
2351+
if was_enabled:
2352+
gc.enable()
2353+
return wrapper

lib/matplotlib/collections.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,10 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs):
10761076
self.set_verts(verts, closed)
10771077
self.stale = True
10781078

1079+
# This function creates a lot of Path object, which is pretty much
1080+
# guaranteed to trigger the GC multiple times. None of them will be garbage
1081+
# just yet, so all those runs are completely necessary.
1082+
@cbook._without_gc
10791083
def set_verts(self, verts, closed=True):
10801084
"""
10811085
Set the vertices of the polygons.
@@ -1101,14 +1105,21 @@ def set_verts(self, verts, closed=True):
11011105

11021106
# Fast path for arrays
11031107
if isinstance(verts, np.ndarray):
1104-
verts_pad = np.concatenate((verts, verts[:, :1]), axis=1)
1108+
verts_pad = (np.concatenate((verts, verts[:, :1]), axis=1)
1109+
.astype(mpath.Path.verts_type))
11051110
# Creating the codes once is much faster than having Path do it
11061111
# separately each time by passing closed=True.
11071112
codes = np.empty(verts_pad.shape[1], dtype=mpath.Path.code_type)
11081113
codes[:] = mpath.Path.LINETO
11091114
codes[0] = mpath.Path.MOVETO
11101115
codes[-1] = mpath.Path.CLOSEPOLY
1111-
self._paths = [mpath.Path(xy, codes) for xy in verts_pad]
1116+
example_path = mpath.Path(verts_pad[0], codes)
1117+
# Looking up the function once before the iteration gives a nice speedup
1118+
_make_path = mpath.Path._fast_from_codes_and_verts
1119+
self._paths = [_make_path(xy, codes,
1120+
internals_from=example_path,
1121+
unmask_verts=False)
1122+
for xy in verts_pad]
11121123
return
11131124

11141125
self._paths = []

lib/matplotlib/path.py

Lines changed: 14 additions & 5 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')
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,15 @@ 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+
if unmask_verts:
187+
pth._vertices = _to_unmasked_float_array(verts)
188+
else:
189+
pth._vertices = verts
179190
pth._codes = codes
180191
pth._readonly = False
181192
if internals_from is not None:
@@ -269,16 +280,14 @@ def readonly(self):
269280
"""
270281
return self._readonly
271282

272-
def __copy__(self):
283+
def copy(self):
273284
"""
274285
Returns a shallow copy of the `Path`, which will share the
275286
vertices and codes with the source `Path`.
276287
"""
277288
import copy
278289
return copy.copy(self)
279290

280-
copy = __copy__
281-
282291
def __deepcopy__(self, memo=None):
283292
"""
284293
Returns a deepcopy of the `Path`. The `Path` will not be

0 commit comments

Comments
 (0)