-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Major speed regression introduced in "plt.bar" definition clipping between 3.0.3 and 3.3.0. #17974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It'll be a lot more work for us to reproduce accurately if you don't have a minimal example. |
I will workout a self-contained example tonight, but the problem is identified, it is usage of |
It was actually a bit harder to reproduce than expected because it is actually a combination of two things:
If both that argument and call are present, computation time explodes. Here is a reproducible snippet that should be self-contained: import matplotlib.pyplot as plt
import numpy as np
import timeit
from matplotlib.patches import Polygon
def tstack(a):
return np.concatenate([x[..., np.newaxis] for x in a], axis=-1)
def plot_slow_bar():
figure = plt.figure(figsize=(10.24, 7.68))
axes = figure.gca()
x_min, x_max = 360, 780
wl = np.arange(x_min, x_max, 1)
wl_c = len(wl)
values = np.sin(wl / 50) * 125 + 125
colours = np.random.random([wl_c, 3])
polygon = Polygon(
np.vstack([
(x_min, 0),
tstack([wl, values]),
(x_max, 0),
]),
facecolor='none',
edgecolor='none')
axes.add_patch(polygon)
padding = 0.1
axes.bar(
x=wl - padding,
height=max(values),
width=1 + padding,
color=colours,
align='edge',
# Comment this line and everything is fast.
clip_path=polygon)
axes.plot(wl, values)
axes.set_xlim(x_min, x_max)
axes.set_ylim(0, 250)
# Comment this line and everything is fast.
figure.tight_layout()
plt.savefig('test.png')
print(timeit.timeit(plot_slow_bar, number=1)) Without
With
With
Call graph for my real function which confirms that most of the time is spent in |
Thanks for the example and profile! Could you try and narrow down a bit more which version this changed by testing if the regression is present in the |
I suspect what changed was #10682 which added all artists to One workaround, in addition to the two PRs @anntzer has put in is to take the artist out of the layout: b = ax.bar(); b.set_in_layout(False)`. |
Hmmm, so I get for So this was not #10682. bisects to 95a530c #16832 @brunobeltran On master this now returns 0.74 seconds, so I guess short circuiting the changes in #16832. Not sure though if that points to further optimizations, or if this should just be closed. |
EDIT: misread @jklymak's comment, didn't realize we're now short-circuiting correctly again. Feel free to weigh in on whether the below is still worth it....not sure why we weren't short-circuiting in the first place. tl;dr: this code is definitely much slower, but it's supposed to only be slower when you need it to be (because previously you would have gotten incorrect results). So I'm happy to add some simple optimizations to work towards that goal if they're actually useful. There are definitely some simple optimization options to do here. The reason that most of the code didn't get a huge speed hit is that we only ever hit this code path in the first place first place if we're not clipping to the axis box anway. I haven't had a free minute to deep dive this this test case, but I assume that adding a manual clip path is what's triggering the One (no so elegant) solution would be keep augmenting this clipping logic, since obviously in this case the curves being checked are still inside of the axis bbox, so there should be some way to short-circuit the whole looping-over-each-curve in Python for its extents. On the other hand, the slowdown basically results from calling Python code to iterate over the vertices of each Path instead of C++ (because the C++ code only works for straight lines, so not for markers, etc). So, for cases like this where we are working with straight lines, falling back to the C++ would in principle recover all of the speed lost. A simple if not self.codes or not np.isin(self.codes, [CURVE3, CURVE4]):
return _path.get_extents(...)
# otherwise use new code In principle, this would add one Of course I could always just port the new code back into C++, but I think that only makes sense if there's not an easy solution like the above. |
The above two PRs are now backported, so this should be fixed for 3.3.1. |
Thanks @QuLogic ! 👍 |
Checked just to be sure:
|
Bug report
Bug summary
As I updated Matplolib from version 3.0.3 to version 3.3.0 I noticed that Colour unit test time skyrocketed: our plotting sub-package test suite went from 120 seconds to around 3000 seconds!
I quickly noticed that it was related to definitions using the spectrum plot:
This definition is using
plt.bar
and as I suspected that polygon clipping might be the culprit, I ran some tests on one of the offending definitions while keeping and removing the polygon clipping:With Polygon Clipping
Without Polygon Clipping
Code for reproduction
I haven't created a reproducible case but my assumption is that the clipping code is orders of magnitude slower than before, the definition I used is available here: https://github.com/colour-science/colour/blob/d5f68005f62fc86ba59745bd4c8bb8a01bb5dcb4/colour/plotting/colorimetry.py#L183 and the meat is roughly as follows:
There are roughly 450 wavelengths and each one of them generates a coloured bar.
Matplotlib version
print(matplotlib.get_backend())
): MacOSXMatplotlib was installed with Pip.
The text was updated successfully, but these errors were encountered: