Skip to content

PERF: Speed up add_patch#32026

Open
scottshambaugh wants to merge 3 commits into
matplotlib:mainfrom
scottshambaugh:add_patch_speedup
Open

PERF: Speed up add_patch#32026
scottshambaugh wants to merge 3 commits into
matplotlib:mainfrom
scottshambaugh:add_patch_speedup

Conversation

@scottshambaugh

@scottshambaugh scottshambaugh commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

PR summary

add_patch is currently very slow since it does bezier checks on all lines, but we can fast-path this for the common straight line case. In the below test script, add_patch dropped from 139 seconds to 50 milliseconds (~2700x improvement).

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch

np.random.seed(0)
n = 100000
verts = np.random.rand(n, 2)
codes = np.full(n, Path.LINETO, dtype=np.uint8)
codes[0] = Path.MOVETO
p = Path(verts, codes)

fig, ax = plt.subplots()
for _ in range(10):
    ax.add_patch(PathPatch(p))
plt.close(fig)

AI Disclosure

Manually identified, claude suggested edits, manually reviewed / edited.

PR checklist

vertices = np.vstack(vertices)
if p.codes is None:
vertices = p.vertices
elif not ((p.codes == p.CURVE3) | (p.codes == p.CURVE4)).any():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be faster with np.isin?

@scottshambaugh scottshambaugh Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had tried that out, and it was ~20x slower than this direct comparison (for two comparisons, I imagine it probably scales better if you add more).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let me roll that into Path.get_extents as well

Comment thread lib/matplotlib/path.py
xys = self.vertices
elif len(np.intersect1d(self.codes, [Path.CURVE3, Path.CURVE4])) == 0:
elif not ((self.codes == Path.CURVE3)
| (self.codes == Path.CURVE4)).any():

@scottshambaugh scottshambaugh Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ~40x faster than the np.intersect1d method, and path.codes is always 1d anyways.

Comment thread lib/matplotlib/path.py
return Bbox([xys.min(axis=0), xys.max(axis=0)])
x = xys[:, 0]
y = xys[:, 1]
return Bbox([[x.min(), y.min()], [x.max(), y.max()]])

@scottshambaugh scottshambaugh Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ended up being a hot path after the other changes, and is ~20x faster for a n=100000 point line. Surprised the numpy reduction is so slow tbh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants