Skip to content

Conversation

BobTheBuidler
Copy link

@BobTheBuidler BobTheBuidler commented Sep 28, 2025

We can reduce the number of attribute lookups requried here.

I also think we can speed it up a little bit more if we make methods arg a packed tuple instead of an unpacked one since we can bypass the additional tuple creation at the time of each call

Seeking input on that before I implement the same, I'm not 100% certain this internal function is safe to change.

We can reduce the number of attribute lookups requried here. 

I also think we can speed it up a little bit more if we make `methods` arg a packed tuple instead of an unpacked one since we can bypass the additional tuple creation at the time of each call

Seeking input on that before I implement the same
@python-cla-bot
Copy link

python-cla-bot bot commented Sep 28, 2025

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Sep 28, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@AA-Turner
Copy link
Member

Please provide a pyperf benchmark. For micro-optimisations, we generally look for a minimum of 10% performance improvement.

A

@picnixz
Copy link
Member

picnixz commented Sep 28, 2025

Also, please create an issue where you can report the benchmarks as well and the script you used to deduce them. I would suggest creating test cases with deeply nested classes.

@StanFromIreland StanFromIreland added the pending The issue will be closed if no feedback is provided label Sep 28, 2025

def _check_methods(C, *methods):
mro = C.__mro__
mro_dicts = [B.__dict__ for B in C.__mro__]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should use a tuple instead of a list to reduce memory usage, since this collection isn’t modified:

Suggested change
mro_dicts = [B.__dict__ for B in C.__mro__]
mro_dicts = tuple(B.__dict__ for B in C.__mro__)

Copy link
Author

Choose a reason for hiding this comment

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

would it be reasonable to cache this tuple for re-use?

Copy link
Contributor

Choose a reason for hiding this comment

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

But the caching here would need to be more sophisticated, since when the class is updated, the result must also change. Standard caching only takes the input into account, so in this case the input would be cached, but the output should actually differ depending on the class state.

Copy link
Author

Choose a reason for hiding this comment

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

can a class mro change after the fact?

I know the contents of dict could change but if the cache is just holding references to each class' dict itself, then any modifications of any dict should already be reflected in the cache

If the mro can change this becomes invalid

Copy link
Author

Choose a reason for hiding this comment

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

to be clear I'm not suggesting to cache the result of this function, only the mro dict tuple

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, got it. I think caching the MRO dict tuple should be safe.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here you also need to decide what exactly we want to optimize, since a tuple gives about ~10% better memory efficiency, while a list is about ~10% faster when iterating in loops.

Copy link
Author

Choose a reason for hiding this comment

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

interesting, I didn't know the lists are faster to iterate thru, even with the length check at each iteration. TIL, thanks

In that case I'd opt for the list for top speed in the subclass check but I'm not sure how that fits into the best practices here or what the people want

Copy link
Member

Choose a reason for hiding this comment

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

It's not worth debating tiny differences like this until we've verified that the change itself is worth pursing.

Comment on lines +111 to 115
for base_dict in mro_dicts:
if method in base_dict:
if base_dict[method] is None:
return NotImplemented
break

Choose a reason for hiding this comment

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

Suggested change
for base_dict in mro_dicts:
if method in base_dict:
if base_dict[method] is None:
return NotImplemented
break
for base_dict in mro_dicts:
method_impl = base_dict.get(method, sentinel)
if method_impl is None:
return NotImplemented
if method_impl is not sentinel:
break

This avoids calculating hash and looking for key in dict twice. sentinel object can be created at the top of the method.

Copy link
Author

Choose a reason for hiding this comment

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

That looks like a clear win, good eye! Might make sense to do the check optimistically, ie: consider the sentinel more likely than the None

if method_impl is sentinel:
    continue
if method_impl is None:
    return NotImplemented
break

Copy link
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

If you want to apply any of the given suggestions, please first open an issue and provide micro-benchmarks (use a PGO+LTO+non-debug build for that, and pyperf as well).

@bedevere-app
Copy link

bedevere-app bot commented Sep 29, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@BobTheBuidler
Copy link
Author

Oh wow, who knew the most active PR conversation in my github history would be on a PR microoptimizing a helper function within the collections module! Thank you for the feedback everybody!

Clearly this will be more involved than I originally planned, its going to take me some time to put together a benchmark so bear with me.

But I'll get that done soonish and test out these suggestions.

@BobTheBuidler
Copy link
Author

please first open an issue and provide micro-benchmarks (use a PGO+LTO+non-debug build for that, and pyperf as well).

This will be my first time using this tool, do you have any suggestions or best practices for its use within this repo?

@picnixz
Copy link
Member

picnixz commented Sep 29, 2025

Yes, here are the steps you can do:

  1. Create a benchmark script. Examples of such scripts can be found in various issues, but in your case, I think the one in gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ #131914 be a good starting point.
  2. To use pyperf, you can take inspiration from the following script gh-74598: add fnmatch.filterfalse for excluding names #121185 (comment) (it's hidden in some "details" box).
  3. The idea is to just benchmark the function and not a full-fledge program. I suggest you have a look at https://github.com/psf/pyperf as well.

I don't have time now to shepherd this but you can take your time for that. However, I'd appreciate we create an issue first as well to give more visibility (possibly, this feature could be rejected if the benchmarks are not satisfactory enough; we usually strive for >= 10% improvements).

@StanFromIreland StanFromIreland changed the title feat: microoptimize _collections_abc._check_methods gh-139424: microoptimize _collections_abc._check_methods Sep 29, 2025
@picnixz picnixz removed the pending The issue will be closed if no feedback is provided label Sep 29, 2025
@AA-Turner
Copy link
Member

I'm going to close this PR for now, let's reopen this if the benchmarks in the linked issue demonstrate it's worth it. Ben's advice above is the best to follow in terms of demonstrating the worth of the change.

A

@AA-Turner AA-Turner closed this Sep 29, 2025
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.

6 participants