Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.1k
gh-103193: cache calls toinspect._shadowed_dict
ininspect.getattr_static
#104267
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
The
So I don't think this is safe. |
AlexWaygood commentedMay 7, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Well, good that we're discovering holes in What about something like this, where # add @lru_cache() to _shadowed_dict, like in this PR currentlydefgetattr_static(obj,attr,default=_sentinel):try:# existing getattr_static implementation herefinally:_shadowed_dict.cache_clear() |
How does the performance comparison look if you make I think if you did that, then it would be safe to put an LRU cache on I think it should be a bounded cache, though, not unbounded. The pathological scenario would be someone creating lots of transient classes inside a function, and calling |
AlexWaygood commentedMay 7, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
>>> functools.lru_cache(lambda:1).cache_info()CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) |
It's not as fast as this PR, but it's alot faster than |
def _shadowed_dict(klass): | ||
return _shadowed_dict_from_mro_tuple(_static_getmro(klass)) |
AlexWaygoodMay 7, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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 considered inlining the calls to_static_getmro
, but I thought this made it more readable. It also preservesgit blame
better, and having to pass through this function doesn't seem to have much impact on performance
AlexWaygood commentedMay 7, 2023 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Here's the new performance numbers using the
The same benchmark on
With this PRnow, On |
(Skipping news because, if this patch is correct, it shouldn't change behaviour at all other than improving performance. And the news entry inhttps://github.com/python/cpython/pull/103195/files should already cover the performance boost.) |
inspect._shadowed_dict
ininspect.getattr_static
Thanks@carljm :D I listed you as a co-author on the commit; I don't think I would have thought of using the mro tuple as a cache key on my own :) |
…getattr_static` (python#104267)Co-authored-by: Carl Meyer <carl@oddbird.net>
Uh oh!
There was an error while loading.Please reload this page.
EDIT: These performance numbers are out of date with the current PR; read further down the thread for the up-to-date numbers.
This dramatically speeds up calls to
inspect.getattr_static
.Here are benchmark results on
main
, using@sobolevn's benchmark script from#103193 (comment):And here are results with this PR:
With this PR,
inspect.getattr_static
is fast enough that evenisinstance()
calls like this, with "pathological" runtime-checkable protocols, are faster than they were on Python 3.11:Pathological protocol
This approach makes me a little nervous. There are two plausible reasons I thought of why adding a cache here might not be a good idea:
klass
argument to be held by the cache even after they've been deleted elsewhere, which could be unexpected behaviour for a low-level function likegetattr_static
__dict__
attribute is shadowed could change at some point during the lifetime of a class.However, Ithink we're okay on both points.
For objection (1):
_shadowed_dict
is only ever called on type objects. The vast majority of classes are defined once in the global namespace and never deleted, so it shouldn't be an issue that the cache is holding strong references to the type objects. (If wedo think this is an issue, I also experimented with a version of this PR that uses aweakref.WeakKeyDictionary
as a cache. It also sped things up, but not by nearly as much.)For objection (2): It doesn't seem to be possible, once a class has been created, to change the class's
__dict__
attribute, at least from Python code. So for any given classklass
,_shadowed_dict(klass)
shouldalways return the same result.@carljm, what do you think?
inspect.getattr_static
#103193