Thanks to visit codestin.com
Credit goes to github.com

Skip to content

PERF: Bezier root finding speedup#31005

Merged
scottshambaugh merged 5 commits intomatplotlib:mainfrom
scottshambaugh:bezier_speedups
Mar 8, 2026
Merged

PERF: Bezier root finding speedup#31005
scottshambaugh merged 5 commits intomatplotlib:mainfrom
scottshambaugh:bezier_speedups

Conversation

@scottshambaugh
Copy link
Copy Markdown
Contributor

@scottshambaugh scottshambaugh commented Jan 20, 2026

PR summary

This speeds up Path.get_extents which calls these bezier operations.

Instead of np.roots which does expensive eigenvalue decomposition, we can take advantage of the fact that we are only looking for roots on the limited [0, 1] interval and just do bisection for degree >= 3. Or we can calculate exactly for degree == 2. These both end up being much faster.

This code section is circled below in the profiler runs.

Before:
image

After:
image

Profiling test script:

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

n = 1000
rng = np.random.default_rng(42)
x = rng.random(n)
y = rng.random(n)
for i in range(n):
    ax.scatter(x[i], y[i])

fig.canvas.draw()
plt.close(fig)

PR checklist

@timhoffm
Copy link
Copy Markdown
Member

timhoffm commented Feb 2, 2026

Can you please split the two changes? I would like to review and discuss them separately.

For the coefficient matrices, I'd like to have a built-in guarantee that the matrix and the formula yield the same result. This can be achieved in one of two ways:

  • use caching instead of hard-coding; i.e. calculate them the first time they occur.
  • or factor out the calculation into a separate private function and write a test that the hard-coded matices and calculated matrices for n<=4 match.

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

scottshambaugh commented Feb 2, 2026

Sure thing @timhoffm, I split the coefficient calcs into #31059 and refactored that to cache the poly coefficient matrices rather than hardcoding them. The root-finding changes remain in this PR, and the description is updated with some screenshots of the profiling changes.

@scottshambaugh scottshambaugh changed the title PERF: Bezier speedups PERF: Bezier root finding speedup Feb 2, 2026
Comment thread lib/matplotlib/bezier.py Outdated
@scottshambaugh scottshambaugh force-pushed the bezier_speedups branch 2 times, most recently from a9548a7 to 4206313 Compare February 3, 2026 15:30
Comment thread lib/matplotlib/bezier.py
@timhoffm
Copy link
Copy Markdown
Member

timhoffm commented Feb 3, 2026

Please fix the stubtest

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

rebased on main

Comment thread lib/matplotlib/bezier.py Outdated
Comment thread lib/matplotlib/bezier.py Outdated
coeffs in ascending order: c0 + c1*x + c2*x**2 + ...
"""
deg = len(coeffs) - 1
n_samples = max(8, deg * 2)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this guarantees you will find all roots, does it? (your initial sieve could be too wide and leave two roots in the same bin, leading to no sign change seen.)
I would think you need something like https://en.wikipedia.org/wiki/Sturm%27s_theorem#Root_isolation

In practice you could also say that we don't care about the degree>=4 case (because Paths can only represent up to cubic beziers), error out for those, and implement the explicit formula for cubics, which is long but reasonably doable (the trig formula https://en.wikipedia.org/wiki/Cubic_equation#Trigonometric_and_hyperbolic_solutions is easier to handle in my experience).

Copy link
Copy Markdown
Contributor Author

@scottshambaugh scottshambaugh Mar 6, 2026

Choose a reason for hiding this comment

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

That's a really good point which I overlooked.

I didn't realize that Paths only go up to cubic though, which really simplifies things. axis_aligned_extrema does root finding on the derivative of the curve's coefficients, so we are ever actually only root finding for degree <= 2. I took out the bisection altogether and swapped in np.roots as a fallback for higher orders. This makes the code a good bit simpler with no speed impact in practice.

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.

image Profiling results still look good

@scottshambaugh scottshambaugh force-pushed the bezier_speedups branch 2 times, most recently from 7e3b70f to 7f4eb0b Compare March 6, 2026 03:40
Comment thread lib/matplotlib/tests/test_bezier.py Outdated
@anntzer
Copy link
Copy Markdown
Contributor

anntzer commented Mar 8, 2026

@scottshambaugh Do you want to squash?

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

Squash merge works for me!

@scottshambaugh scottshambaugh merged commit 9d83ca6 into matplotlib:main Mar 8, 2026
40 checks passed
@QuLogic QuLogic added this to the v3.11.0 milestone Mar 9, 2026
andreas16700 added a commit to andreas16700/matplotlib that referenced this pull request Mar 16, 2026
andreas16700 added a commit to andreas16700/matplotlib that referenced this pull request Mar 16, 2026
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.

4 participants