Repository navigation
Tick rendering speedups - #29394
Tick rendering speedups#29394scottshambaugh wants to merge 1 commit into
Conversation
|
How much of the speedup is due to the lru_cache()? That is something that
would impact performance all across mpl.
…On Tue, Dec 31, 2024 at 9:27 PM Scott Shambaugh ***@***.***> wrote:
PR summary
I'm starting an effort to speed up 3D rendering, spinning up a profiler on
some test cases. For this MR, I'm focusing on draw speed for an empty plot.
Based on py-spy <https://github.com/benfred/py-spy> profiling, the
largest proportion of render time is dedicated to handling tick marks. This
PR implements several optimizations to speed up tick drawing. Based on
averaging 6x runs of the below script that executes 100x draw calls, the
average render time for the basic empty plot decreased from 57 ms to 22 ms,
which is a 2.6x improvement! Qualitatively, rotating the plot definitely
feels snappier on my machine.
import matplotlib.pyplot as pltimport time
fig = plt.figure()ax = fig.add_subplot(111, projection='3d')ax.view_init(0, 0, 0)
plt.show(block=False)plt.pause(1)
# Start timingstart_time = time.perf_counter()
n = 100for i in range(n):
ax.view_init(0, 0, i*10)
plt.draw()
plt.pause(0.1)
# End timingend_time = time.perf_counter()
# Calculate actual computation time by subtracting pausestotal_pause_time = n * 0.1 # 100 iterations * 0.1 seconds per pausecomputation_time = (end_time - start_time) - total_pause_timeprint(f"Total time: {end_time - start_time:.4f} seconds")print(f"Computation time (excluding pauses): {computation_time:.4f} seconds")print(f"Average time per loop: {computation_time / n * 1000:.2f} ms")
Attached are representative before and after profiler runs, which can be
uploaded to https://www.speedscope.app/ to view
profile_ticks_speedups.speedscope.json
<https://github.com/user-attachments/files/18284234/profile_ticks_speedups.speedscope.json>
profile_ticks_base.speedscope.json
<https://github.com/user-attachments/files/18284235/profile_ticks_base.speedscope.json>
PR checklist
- "closes #0000" is in the body of the PR description to link the
related issue
<https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue>
- new and changed code is tested
<https://matplotlib.org/devdocs/devel/testing.html>
- *Plotting related* features are demonstrated in an example
<https://matplotlib.org/devdocs/devel/document.html#write-examples-and-tutorials>
- *New Features* and *API Changes* are noted with a directive and
release note
<https://matplotlib.org/devdocs/devel/api_changes.html#announce-changes-deprecations-and-new-features>
- Documentation complies with general
<https://matplotlib.org/devdocs/devel/document.html#write-rest-pages>
and docstring
<https://matplotlib.org/devdocs/devel/document.html#write-docstrings>
guidelines
------------------------------
You can view, comment on, or merge this pull request online at:
#29394
Commit Summary
- abf083b
<abf083b>
Tick rendering speedups
File Changes
(6 files <https://github.com/matplotlib/matplotlib/pull/29394/files>)
- *M* lib/matplotlib/axis.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-04227e6d4900298b309bddab2e848da8cc638da2913c64b5dcf0d800ba2a0c16>
(22)
- *M* lib/matplotlib/backends/backend_agg.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-0a415dbb618fcfb73e6191c735f6e5a91f530d4a29b8886afdfd56604892de61>
(2)
- *M* lib/matplotlib/spines.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-4e07c249abaaf29d0bb2bef9c5b12a86ccaa4895fcae37004bacf9d406043f8b>
(2)
- *M* lib/matplotlib/tests/test_axes.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-ef5144bd27a700deb760ef56ccf51dc6fcfe86723f9ffa84df7f024985bf3ee7>
(2)
- *M* lib/mpl_toolkits/mplot3d/axes3d.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-a67656224bfb26dc6de0a0f89e398cd6999377d1482aca3e867ee550ff05be57>
(3)
- *M* lib/mpl_toolkits/mplot3d/axis3d.py
<https://github.com/matplotlib/matplotlib/pull/29394/files#diff-ea1dd4bc7c3b7c0078e69e41e916d25354864e4f2fe31fd71210477f6f9f9660>
(36)
Patch Links:
- https://github.com/matplotlib/matplotlib/pull/29394.patch
- https://github.com/matplotlib/matplotlib/pull/29394.diff
—
Reply to this email directly, view it on GitHub
<#29394>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACHF6A7PCUXALQNDHIHBRL2ING75AVCNFSM6AAAAABUOEZ4CCVHI2DSMVQWIX3LMV43ASLTON2WKOZSG43DINZZGE4DMNQ>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
abf083b to
059e8a5
Compare
|
Redid the profiling in a more stable environment and the improvement wasn't as dramatic, about 48% speedup. The |
| tick.label1.set_text(label) | ||
| tick.label2.set_text(label) | ||
| ticks = [*major_ticks, *minor_ticks] | ||
| self._ticks = [*major_ticks, *minor_ticks] |
There was a problem hiding this comment.
I‘m somewhat skeptical this is a good idea. It’s essentially a caching mechanism for the expensive (?) lookup of the current ticks. This adds more state and makes tick handling more complicated.
On a general note. The fundamental problem is the tick design:
- Ticks consist of three Line2D objects and two Text objects. This is heavy.
- Ticks are often created / modified.
This combination makes Ticks expensive. One mitigation strategy we use is that ticks are repurposed instead of being recreated - this is the update_ticks mechanism. The second trick is the lazy behavior of tick lists. Ticks are only created when you need them first. Overall these are still only bandaids on the fundamentally expensive tick design. And the change here goes into the same category.
I believe to substantially improve performance, we have to move away from heavy Tick instances and loops over then. See #5665 (comment). Alternatively/complementary, ticks could get more lightweight with a borg-like pattern.
There was a problem hiding this comment.
I agree with your assessment on Ticks being heavy, and a refactor being the real solution here. The highlighted branch of the call tree here shows the ticks taking up well over half the time associated with the figure & 3d axes creation and drawing.
Think the extra state isn't worth the ~20% speedup? I don't feel too strongly either way.
There was a problem hiding this comment.
I‘d like to put this on hold. I plan to pick up the refactoring plan soon, and this additional state would complicate that. We might put the caching then behind the TickCollection abstraction.
There was a problem hiding this comment.
Sounds good to me!

PR summary
I'm starting an effort to speed up 3D rendering, spinning up a profiler on some test cases. For this MR, I'm focusing on draw speed for an empty plot.
Based on
py-spyprofiling, the largest proportion of render time is dedicated to handling tick marks. This PR implements several optimizations to speed up tick drawing. Based on averaging 6x runs of the below script that executes 100x draw calls, the average render time for the basic empty plot decreased from 23.1 ms to 19.1 ms, which is a 21% improvement. Qualitatively, rotating the plot definitely feels snappier on my machine.Attached are representative before and after profiler runs, which can be uploaded to https://www.speedscope.app/ to view
profile_ticks_speedups.speedscope.json
profile_ticks_base.speedscope.json
PR checklist