Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Optimize imshow #26335

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

Merged
merged 8 commits into from
Jul 28, 2023
Merged

Optimize imshow #26335

merged 8 commits into from
Jul 28, 2023

Conversation

eendebakpt
Copy link
Contributor

@eendebakpt eendebakpt commented Jul 17, 2023

PR summary

In this PR we apply some micro optimizations to improve the imshow performance. Benchmark:

Mean +- std dev: [main] 7.34 ms +- 0.07 ms -> [pr] 6.67 ms +- 0.11 ms: 1.10x faster

Notes:

  • There is a fast path for the MaskedArray in case the mask is False (e.g. images without a NaN or Inf value)
  • The np.ma.masked_invalid undoes the shrink_mask from np.ma.masked_where, but matplotlib applies it again. So it is faster to call np.ma.masked_where directly
  • Another possible optimization is in Normalize.autoscale_None. There both the setting of vmin and vmax triggers _changed. It is faster to first set vmin and vmax (without triggering _changed) and then calling _changed. This would require a new method set_vmin_vmax or some other mechanism to avoid triggering _changed twice.

Benchmark script:

import matplotlib
# print(matplotlib)
import pyperf

setup = """
import matplotlib.pyplot as plt
import numpy as np

n=10
im=np.random.rand(100,100)

def go():
    for ii in range(n):
        plt.figure(1)
        plt.imshow(im)
"""

runner = pyperf.Runner()
runner.timeit(name="mpl", stmt="go()", setup=setup)

PR checklist

@@ -735,5 +735,5 @@ def _ensure_cmap(cmap):
cmap_name = cmap if cmap is not None else mpl.rcParams["image.cmap"]
# use check_in_list to ensure type stability of the exception raised by
# the internal usage of this (ValueError vs KeyError)
_api.check_in_list(sorted(_colormaps), cmap=cmap_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matters for the printed error message when the colormap is invalid. (We could instead ensure that _colormaps is always sorted by sorting it whenever a new item is added, though.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. We could ensure the ordering of _colormaps by adding c._cmaps = {k: c._cmaps[k] for k in sorted(c._cmaps)} at the end of ColormapRegistry.register. Under the assumption that number of colormaps added is reasonable, this will not have any other negative performance impact.

Another option is to replace the code with

if not cmap_name in _colormaps:
    _api.check_in_list(sorted(_colormaps), cmap=cmap_name)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making sure that __iter__ on ColormapRegistry is always sorted is probably the best option (rather than mucking with dictionary order we can keep a self._sorted_keys = sorted(self._cmap) attribute and iterate over that in __iter__.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the if not in... is also fine (as I see you already pushed that).

Comment on lines +1396 to +1397
if A.mask is False or not A.mask.shape:
A = A.data
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of testing:

Suggested change
if A.mask is False or not A.mask.shape:
A = A.data
try:
no_mask = not bool(A.mask)
except ValueError:
no_mask = False
if no_mask:
A = A.data

Numpy generated a ValueError when testing an array for truth value.

@eendebakpt
Copy link
Contributor Author

Coverage was complaining because the number of lines of code was reduced. I added a few simple tests for strip_math to make ci happy.

@tacaswell
Copy link
Member

Is stip_math otherwise implicated is this change and does the new test test something that we do not already test?

Some of the codecov failures sort them selves out when all of the CI jobs upload their results and are processed (as some of the tests are platform specific)

@eendebakpt
Copy link
Contributor Author

Is stip_math otherwise implicated is this change and does the new test test something that we do not already test?

Some of the codecov failures sort them selves out when all of the CI jobs upload their results and are processed (as some of the tests are platform specific)

The strip_math is completely unrelated to this PR. Coverage showed the method was not in the unit testing (at least not explicit), so I picked it as an easy way to increase coverage. If you want I can remove the tests again and see what happens to the CI.

@jklymak
Copy link
Member

jklymak commented Jul 18, 2023

Maybe directly testing strip_math is OK, but it points to whatever code calls strip_math as not being tested either. It gets used in LogFormatter, which I'd have thought was thoroughly tested, but maybe not?

EDIT: overall I think it's OK for codecov to decrease percentage in a PR if its due to code removal. It seems confusing to add an unrelated test just to make our tool happy. Maybe consider moving the test improvements to a separate PR?

@eendebakpt eendebakpt changed the title Draft: Optimize imshow Optimize imshow Jul 18, 2023
@jklymak jklymak requested a review from anntzer July 27, 2023 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants