diff --git a/.gitignore b/.gitignore index dc6244855..da9a31ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ tests/data/common_wheels/ # Mac .DS_Store + +# Profiling related artifacts +*.prof diff --git a/news/9748.feature.rst b/news/9748.feature.rst new file mode 100644 index 000000000..cb4a1cded --- /dev/null +++ b/news/9748.feature.rst @@ -0,0 +1 @@ +Improve performance when picking the best file from indexes during `pip install`. diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 4cc9ffd4d..a6423cce1 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -434,6 +434,12 @@ class CandidateEvaluator: self._project_name = project_name self._specifier = specifier self._supported_tags = supported_tags + # Since the index of the tag in the _supported_tags list is used + # as a priority, precompute a map from tag to index/priority to be + # used in wheel.find_most_preferred_tag. + self._wheel_tag_preferences = { + tag: idx for idx, tag in enumerate(supported_tags) + } def get_applicable_candidates( self, @@ -512,14 +518,17 @@ class CandidateEvaluator: if link.is_wheel: # can raise InvalidWheelFilename wheel = Wheel(link.filename) - if not wheel.supported(valid_tags): + try: + pri = -(wheel.find_most_preferred_tag( + valid_tags, self._wheel_tag_preferences + )) + except ValueError: raise UnsupportedWheel( "{} is not a supported wheel for this platform. It " "can't be sorted.".format(wheel.filename) ) if self._prefer_binary: binary_preference = 1 - pri = -(wheel.support_index_min(valid_tags)) if wheel.build_tag is not None: match = re.match(r'^(\d+)(.*)$', wheel.build_tag) build_tag_groups = match.groups() diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py index 708bff330..c206d13cb 100644 --- a/src/pip/_internal/models/wheel.py +++ b/src/pip/_internal/models/wheel.py @@ -2,7 +2,7 @@ name that have meaning. """ import re -from typing import List +from typing import Dict, List from pip._vendor.packaging.tags import Tag @@ -66,8 +66,26 @@ class Wheel: """ return min(tags.index(tag) for tag in self.file_tags if tag in tags) + def find_most_preferred_tag(self, tags, tag_to_priority): + # type: (List[Tag], Dict[Tag, int]) -> int + """Return the priority of the most preferred tag that one of the wheel's file + tag combinations acheives in the given list of supported tags using the given + tag_to_priority mapping, where lower priorities are more-preferred. + + This is used in place of support_index_min in some cases in order to avoid + an expensive linear scan of a large list of tags. + + :param tags: the PEP 425 tags to check the wheel against. + :param tag_to_priority: a mapping from tag to priority of that tag, where + lower is more preferred. + + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + return min(tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority) + def supported(self, tags): - # type: (List[Tag]) -> bool + # type: (Iterable[Tag]) -> bool """Return whether the wheel is compatible with one of the given tags. :param tags: the PEP 425 tags to check the wheel against.