Repository navigation
Description
Bug report
Bug summary
The plot function of the matplotlib 2 and 3 versions is much slower than the plot function of matplotlib 1.5.3 if one plots many lines (and other objects) in an Axes.
Code for reproduction
I have tested it on an Ubuntu server (CLI only), version 16.04 with python 3.5.2 (through virtualenv), matplotlib 1.5.3 with backend agg set by matplotlibrc.
setup="""import matplotlib.pyplot as plt
a=[1,2]"""
test="plt.plot(a,a,'-')"
import timeit
timeit.timeit(test,setup,number=1000)
# output: 0.6324263589922339
timeit.timeit(test,setup,number=1000)
# output: 0.6100882450118661
timeit.timeit(test,setup,number=1000)
# output: 0.6113728249911219=> around 0.6-0.7 sec, it is stable for a few dozen times.
On the same machine & operating system, python 3.5.2 (through virtualenv), matplotlib 2.2.2 with backend agg.
setup="""import matplotlib.pyplot as plt
a=[1,2]"""
test="plt.plot(a,a,'-')"
import timeit
timeit.timeit(test,setup,number=1000)
# output: 0.9981246230890974
timeit.timeit(test,setup,number=1000)
# output: 1.3041282850317657
timeit.timeit(test,setup,number=1000)
# output: 1.6257964670658112
timeit.timeit(test,setup,number=1000)
# output: 1.9830634299432859=> getting slower and slower...
Note that matplotlib 2.0.0, 2.0.1, 2.1.0, 2.1.2, 2.2.2, or 3.0.0 has the same slowdown behavior. If I set svg as backend, the result is the same as agg (I confirmed backend="svg" only for matplotlib 2.0.0 on the Ubuntu server).
This behavior is also confirmed on a Windows distribution: WinPython-64bit-3.4.4.6Qt5 with matplotlib 2.0.0 and default backend TkAgg, but not for WinPython-64bit-3.4.4.5Qt5 with matplotlib version 1.5.3 and TkAgg, though the situation is much more complex. In addition, WinPython-64bit-3.5.1.1 and matplotlib 2.1.0 (packaged by Christoph Gohlke) causes the same slowdown behavior.
Another windows distribution anaconda 3 version 4.4.0(64bit) also has a similar behavior. For the anaconda 3 environment, all the packages are default of the 4.4.0 distribution (python 3.6 and matplotlib 2.0.2 with backend qt5agg), and it runs on windows7-x64.
If I close the figure by pyplot.close(), the slowed plot() is returned to the initial state.
Expected outcome
The expected outcome is "no slowdown for the plot function of matplotlib 3" as the matplotlib version 1.5.3.
Matplotlib version
- Operating system: Ubuntu 1604 (CLI only)
- Matplotlib version: matplotlib 2.2.2
- Matplotlib backend (
print(matplotlib.get_backend())): agg - Python version: 3.5.2
- Jupyter version (if applicable):
- Other libraries:
Other note
I have used a script to write huge maps with overlayed many lines drawn by plot(). When I'm with matplotlib 1.5.3, it takes several minutes to output a bunch of png files, but no fatal problem is there. However, with 2.0.0-3.0.0 it takes several hours or more, and I cannot use the script at all.
I believe this slowdown behavior can occur for most of the matplotlib environments. I think this is not an OS/system dependent bug/specification.
Activity
jklymak commentedon Oct 17, 2018
I agree your script slows down for me.
OTOH, initializing a figure in your setup doesn't exhibit this slow down. I'm not sure why this happens, but I'm not sure what your use case is.
In general, I'd suggest not trying to use the state-based interface for the best performance...
QuLogic commentedon Oct 17, 2018
But I think that's the point, since:
On the other hand, drawing 4000 lines vs 1000 lines seems like it should be slower to me, but is it too much slower per line? I'm not sure, possibly, but I think a more interesting test is this:
which does not seem to scale so well in recent versions:

Extrapolating says it should take 400s for count=100000 on 3.0.0, but I'm still waiting ~60 minutes later...
QuLogic commentedon Oct 17, 2018
Oh, actually, 100000 count finished, so (this is loglog):


Looking at time per line (just time / number) (this is only semilogy because the count shouldn't matter):
Ignoring 1 and 10 which are pretty noisy, 10000 lines is an order of magnitude slower, but at 100000 lines, it's two orders of magnitude slower per line. I don't know what's going on there.
QuLogic commentedon Oct 17, 2018
More straightforward test, I think?
with results:
The base time went up slightly, but the main point is that the ratio is much higher now for some reason.
QuLogic commentedon Oct 17, 2018
Ran an automated bisect with ratio=13 as good/bad threshold and it points to 9617dfe as the first bad commit
which I guess might make sense as automated margins would require iterating through all artists.
anntzer commentedon Oct 17, 2018
Thanks for figuring this out.
This points once again to #7413, namely @efiring's first point:
We're hitting quadratic behavior here :/
dstansby commentedon Oct 17, 2018
If anyone wants a fun project, this looks like a good excuse to dig up #2188 / https://github.com/airspeed-velocity/asv - there are some older results at https://matplotlib.org/mpl-bench/
QuLogic commentedon Oct 17, 2018
I'm still waiting for them to accept the PR to enable it on NumPy's bench system...
QuLogic commentedon Oct 17, 2018
A pretty generous speed up can be had by disabling autoscale and sticky edges before plotting, and then re-enabling them after:
$ time python ../mpl-tests/issue12542a.py real 0m39.860s user 0m38.035s sys 0m0.186svs.
anntzer commentedon Oct 17, 2018
See #12546 for a small improvement.
PRF: Disable sticky edge search if not autoscaling.
Disable sticky edge accumulation if no autoscaling.
QuLogic commentedon Oct 17, 2018
#12547 is a related change insomuch as it helps if you've already disable autoscale, but it's orthogonal to @anntzer's change.
jklymak commentedon Oct 17, 2018
Ooops, I see what the difference is now in the tests. Sorry for the noise.
I think a todo here, asside from makig the autolim algorithm faster, is to have a Performance Tweaks page where we list potential performance blocks that will slow things down for folks who want to have thousands of artists, or a very responsive GUI. Off the top of my head:
No doubt there are more.
ImportanceOfBeingErnest commentedon Oct 17, 2018
Adding to the list (especially with respect to the actual topic here)
LineCollectioninstead of manyplots, aPathCollectionorPolyCollectionor evenPath.make_compound_path_from_polysinstead of abarwith many bars (like here), etc.QuLogic commentedon Oct 18, 2018
OK, interestingly, though we definitely would want to avoid autoscale being called too much, looking at a profiler, it's actually accumulating sticky edges that takes the most time (even with @anntzer's optimization there):
anntzer commentedon Oct 18, 2018
One can squeeze out another ~30%(!) or so by replacing the property access (artist.sticky_edges, sticky.x, sticky.y) by direct attribute/tuple access (artist._sticky_edges, sticky[0], sticky[1] (sticky is a namedtuple)), which I guess is a reasonable patch (with a comment explaining the performance implications) but really just a band-aid over a problematic design.
materia9 commentedon Oct 18, 2018
Thank you everyone. I now realize that the root cause of the problem at least for my case. Now I'm trying to use the options:
autoscale(False)anduse_sticky_edges = False. I'm a lazy programmer and thus I found this behavior (maybe a good programmer can solve the speed limitation completely and never realizes the problem). I think a performance tweak page will be a very good start point even for me.jklymak commentedon Oct 18, 2018
So is ti possible to delay
autoscale_viewto draw time? Seems a straightforward solution.If that is not possible, could we accumulate
x_stickiesandy_stickiesinadd_artistand remove them inartist.remove? Unless I'm misunderstanding, the problem is that making these lists from scratch every time a new artist is added is inefficient.WeatherGod commentedon Oct 18, 2018
tacaswell commentedon Oct 19, 2018
If we cache the sticky-ness, we need to track if it is updated on the artists.