diff --git a/news/10621.feature.rst b/news/10621.feature.rst new file mode 100644 index 00000000000..4f7d29e4584 --- /dev/null +++ b/news/10621.feature.rst @@ -0,0 +1 @@ +Optimize performance of conflict cause resolution while the dependency resolver backtracks. diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index e6ec9594f62..603e0ccf83f 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -7,6 +7,8 @@ Iterator, Mapping, Sequence, + Set, + Tuple, TypeVar, Union, ) @@ -100,6 +102,7 @@ def __init__( self._upgrade_strategy = upgrade_strategy self._user_requested = user_requested self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf) + self._backtrack_cause_name_collections: Dict[Tuple[int, ...], Set[str]] = {} def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str: return requirement_or_candidate.name @@ -236,13 +239,28 @@ def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: with_requires = not self._ignore_dependencies return [r for r in candidate.iter_dependencies(with_requires) if r is not None] - @staticmethod def is_backtrack_cause( - identifier: str, backtrack_causes: Sequence["PreferenceInformation"] + self, identifier: str, backtrack_causes: Sequence["PreferenceInformation"] ) -> bool: - for backtrack_cause in backtrack_causes: - if identifier == backtrack_cause.requirement.name: - return True - if backtrack_cause.parent and identifier == backtrack_cause.parent.name: - return True - return False + return identifier in self._get_causes_set(backtrack_causes) + + def _get_causes_set( + self, + backtrack_causes: Sequence["PreferenceInformation"], + ) -> Set[str]: + backtrack_causes_collection_key = tuple(id(c) for c in backtrack_causes) + cached_names = self._backtrack_cause_name_collections.get( + backtrack_causes_collection_key + ) + if cached_names is None: + self._backtrack_cause_name_collections.clear() + cached_names = set() + for backtrack_cause in backtrack_causes: + cached_names.add(backtrack_cause.requirement.name) + parent = backtrack_cause.parent + if parent: + cached_names.add(parent.name) + self._backtrack_cause_name_collections[ + backtrack_causes_collection_key + ] = cached_names + return cached_names