-
-
Notifications
You must be signed in to change notification settings - Fork 415
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
PERF: Event emissions and perf regression. #5307
Conversation
Working on napari#5263, I came across this weird things where a lot of time is spent checking the non-equality of two empty dict, that actually are the same instance. It does create a 20% improvement in TextManager.__init__, which apply directly trigger the event mechanism: def apply(self, features: Any): self.string._apply(features) # this line is a call to __call__ and represent ~99% of time # spent in this method regardless of before/after patch. self.events.values() self.color._apply(features) I'm not sure why the interpreter does not check for identity first. I'm adding the After patch: ``` (HEAD) $ time python -m kernprof -l -v bench.py Wrote profile results to bench.py.lprof Timer unit: 1e-06 s Total time: 0.001366 s File: /Users/bussonniermatthias/dev/napari/napari/layers/utils/text_manager.py Function: __init__ at line 89 Line # Hits Time Per Hit % Time Line Contents ============================================================== class TextManager(EventedModel) 89 @Profile 90 def __init__( 91 self, text=None, properties=None, n_text=None, features=None, **kwargs 92 ): 93 1 1.0 1.0 0.1 if ...: 94 ... 95 1 0.0 0.0 0.0 if ...: 96 ... 97 ... 98 else: 99 1 114.0 114.0 8.3 features = _validate_features(features) 100 1 1.0 1.0 0.1 if ...: 101 ... 102 ... 103 ... 104 ... 105 1 0.0 0.0 0.0 if ...: 106 _warn_about_deprecated_text_parameter() 107 kwargs['string'] = text 108 1 1224.0 1224.0 89.6 super().__init__(**kwargs) 109 1 11.0 11.0 0.8 self.events.add(values=Event) 110 1 15.0 15.0 1.1 self.apply(features) and in __call__: 766 5 2.0 0.4 2.9 self._emitting = False 767 5 6.0 1.2 8.6 ps = event._pop_source() 768 5 5.0 1.0 7.1 if ps is not self.source : 769 if ps != self.source: 770 raise RuntimeError( 771 trans._( 772 "Event source-stack mismatch.", 773 deferred=True, 774 ) 775 ) ``` And master baseline for info: ``` (|nap6) napari[main ✗] $ time python -m kernprof -l -v bench.py Wrote profile results to bench.py.lprof Timer unit: 1e-06 s Total time: 0.001803 s File: /Users/bussonniermatthias/dev/napari/napari/layers/utils/text_manager.py Function: __init__ at line 89 Line # Hits Time Per Hit % Time Line Contents ============================================================== 89 @Profile 90 def __init__( 91 self, text=None, properties=None, n_text=None, features=None, **kwargs 92 ): 93 1 1.0 1.0 0.1 if ...: 94 ... 95 1 0.0 0.0 0.0 if ...: 96 ... 97 ... 98 else: 99 1 112.0 112.0 6.2 features = _validate_features(features) 100 1 0.0 0.0 0.0 if ...: 101 ... 102 ... 103 if 'string' not in kwargs: 104 ... 105 1 1.0 1.0 0.1 if ...: 106 ... 107 ... 108 1 1268.0 1268.0 70.3 super().__init__(**kwargs) 109 1 11.0 11.0 0.6 self.events.add(values=Event) 110 1 410.0 410.0 22.7 self.apply(features) and in __call__: 767 5 441.0 88.2 87.3 if event._pop_source() != self.source: 768 raise RuntimeError( 769 trans._( 770 "Event source-stack mismatch.", 771 deferred=True, 772 ) 773 ) python -m kernprof -l -v bench.py 1.35s user 0.61s system 113% cpu 1.721 total ```
Codecov Report
@@ Coverage Diff @@
## main #5307 +/- ##
==========================================
+ Coverage 89.08% 89.09% +0.01%
==========================================
Files 581 581
Lines 49233 49236 +3
==========================================
+ Hits 43857 43869 +12
+ Misses 5376 5367 -9
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
but the comparison is of two instances of |
Sure, but I still came across this while it was comparing empty dicts due to register_theme. This is also why I added the profile of |
I do not understand. Where is a problem with comparing empty dicts? I do not see it in the profiling output.
Because it is how napari/napari/utils/events/evented_model.py Lines 325 to 345 in 1cc90a7
I never expected that python would use |
I'm (was) pretty sure that is an optimisation at CPython level for |
Ah, Yes, there is a shortcut for |
napari/utils/events/event.py
Outdated
if ps is not self.source: | ||
if ps != self.source: |
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 ps is not self.source: | |
if ps != self.source: | |
if ps is not self.source and ps != self.source: |
maybe single if?
Should we then simply add the identity check at the top of our Yeah it seems like a useful optimization to have... On the other hand, this would break stuff like: np.nan != np.nan |
Problem is that there are many sources, and you would need to add the identity check everywhere. In the case of |
Not if the problem is the slowness of
Sorry, I wasn't clear. I meant that if the interpreter used this shortcut by default, it would not allow things like
Actually, I think you're right. Either way, I think the addition to |
Ok, I've move the instance test to the EventedModel I've also asserted that sources that don't test equal still are instances of EventedModel |
@Carreau you have an approval on this, just bringing it to attention in case you forgot about it :) |
I try to always avoid self-merge, and try to follow the policy that only @napari/core-devs should merged – at least I was reminded some time ago I generally should not be the one merging PRs, so I'm not merging. |
Working on #5263, I came across this weird things where a lot of time is spent checking the non-equality of two empty dict, that actually are the same instance.
It does create a 20% improvement in
TextManager.__init__
, which apply directly trigger the event mechanism:I'm not sure why the interpreter does not check for identity first. I'm adding the
After patch:
And master baseline for info:
I'd love for someone to confirm I"m not crazy and Python does not text for identity.
And here is my bench script, from the slow benchmarks: