PERF: Cache ticks and ticklabel bboxes within each draw cycle#31012
PERF: Cache ticks and ticklabel bboxes within each draw cycle#31012scottshambaugh wants to merge 8 commits intomatplotlib:mainfrom
Conversation
e117b04 to
2ed48b9
Compare
|
Is this safe? Are you sure they are always identical? For example constrained layout draws twice and the Axes size and limits may have changed in the second draw. Or asking the other way round: if the updates are redundant, why are we doing them in the first place? |
|
As to why we're doing them multiple times in the first place - in 3D we need to access these values in the tick drawing and the grid drawing paths, which might not both be called so both needed to potentially refresh the calcs. In 2D, I don't see a reason - my hunch it was done without realizing the performance hit of recalculating that state each time. # axis.draw()
self._clear_ticks_cache()
ticks_to_draw = self._update_ticks(_use_cache=True)
tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer, _use_cache=True)
for tick in ticks_to_draw:
tick.draw(renderer)
self._update_label_position(renderer, _use_cache=True)
self.label.draw(renderer)
self._update_offset_text_position(tlb1, tlb2)
self.offsetText.set_text(self.major.formatter.get_offset())
self.offsetText.draw(renderer)
renderer.close_group(__name__)
self._clear_ticks_cache() |
9479900 to
7175a61
Compare
7fca827 to
975fb24
Compare
975fb24 to
32af92e
Compare
|
Rebased to main |
Fix test
32af92e to
7991b9b
Compare
|
This has fallen well off the PRs front page so is probably getting lost, @timhoffm any thoughts? |
|
I'm uneasy about introducing complex caches. Caching a pure function is fine, but here we have to jump substantial hoops with conditional use of caching and cache invalidation. Touching ~20 code locations for that seems brittle and not something I would want to understand or maintain. I suspect this need for distributed caching is the result of our bad/fragmented tick handling (and possibly it's even the root cause for the tick inefficienty). I still think something like #5665 (comment) is the prerequisite to get out of the tick mess. |
|
Agree that this is pretty complex state & fragile with respect to future refactors / maintainability. Offsetting that is that ticks are covered by pretty much every single image comparison test so I'm not too worried about regressions. Will leave open for now if anyone else wants to weigh in. I think the speedup here is significant enough to warrant it, but we did get a good boost on overall ticks performance from #30995 so I'm less gung-ho about pushing this one. The 3D tick handling is actually a good bit simpler than 2D, so I'll ping you on looking at caching for those when I finish up #30994 |
|
I'm afraid that the complex cache makes future refactoring like #5665 (comment), that could substantially improve the tick architecture and performance much harder. How much faster does the test suite get by this? |


PR summary
Towards #5665
Currently we are calling
_update_ticks()3 times per axis every draw call, and_get_ticklabel_bboxes2 times per axis. These calls take up about 35% of total draw time for an empty plot.We can eliminate 25% of total draw time by caching the results of these calculations every draw cycle. This introduces some state, but I think it's decently well guarded and the performance boost is definitely worth it.
Before & After
The circled red areas show the before & after runtime of
axis._update_label_positionwithinAxis.draw(). It runs after the cache values have been set by other functions, so we nearly completely eliminate its runtime.Before:

After:

Profiling script:
PR checklist