-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
PERF: Text handling speedups #31001
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
base: main
Are you sure you want to change the base?
PERF: Text handling speedups #31001
Conversation
267a86f to
6a7f558
Compare
6a7f558 to
fe70db4
Compare
Fix tests
More robust BBox creation
fe70db4 to
f35c71d
Compare
|
Ready for review. @anntzer FYI - you're probably the most familiar with the text sections here |
| def get_window_extent(self, renderer=None): | ||
| x0, x1, y0, y1 = self._extent | ||
| bbox = Bbox.from_extents([x0, y0, x1, y1]) | ||
| bbox = Bbox.from_extents(x0, y0, x1, y1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess Bbox([(x0, y0), (x1, y1)]) would be even better.
|
|
||
| self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float) | ||
| self._x, self._y = self._xy.T # views | ||
| # Fast path for common case where x and y have same shape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably both cases can be kept together (by relying on broadcasting in the assignments):
self._xy = np.empty((max(len(x), len(y)), 2))
self._xy[:, 0] = x
self._xy[:, 1] = y
?
| set. This is useful when dealing with logarithmic scales and other | ||
| scales where negative bounds result in floating point errors. | ||
| """ | ||
| bbox = Bbox(np.reshape(args, (2, 2))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe just np.asarray(args, float).reshape((2, 2)) should get you most or all the speed benefits?
|
|
||
| # now rotate the positions around the first (x, y) position | ||
| xys = M.transform(offset_layout) - (offsetx, offsety) | ||
| if rotation != 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're going for this kind of microoptimizations, I would strongly suspect that it may be faster to convert everything to python scalars (not numpy scalars) and completely avoid the use of numpy here (writing out explicitly the rotation operations and so on), because we're dealing in general with very few characters ("no one" is writing thousands of characters with matplotlib) and numpy's overhead is actually large in this case.
| _text_metrics_cache = weakref.WeakKeyDictionary() | ||
|
|
||
|
|
||
| def _get_text_metrics_with_cache_impl( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This caching was last touched by @tacaswell IIRC, perhaps he can check it.
PR summary
I've been having a lot of fun profiling the past two days. This PR is the result of optimizing slow bits of the text rendering code paths that are called downstream of
axis3d._draw_ticks(). None of these changes are 3D specific, so they should speed up 2D draw times as well. The non-agg-rendering code in this part of the stack is sped up by a cumulative 2.2x, which is an 8% reduction in the total draw time for my test script of an empty 3D plot.The commits are all self-contained, so I can break them apart if that's easier to review.
Summary of the changes:
text.py:lru_cache, and use a tuple-based cache key. This avoids expensive copying of the wholeFontPropertiesobject on every cache lookup.@lru_cachefor rotation transforms via a_rotate(theta)helper function (common case is only a few angles)font_manager.py:__copy__method onFontPropertiesthat bypasses__init__validationlines.pybroadcast_arrays.Tunpacking with column slicing for viewspath.py_api.check_shapetransforms.pyBbox.from_extentsovernp.reshapeBefore:

After (less time on things that aren't

draw_text):Test script:
PR checklist