This function is called frequently with similar arguments, so cache as
much of the partial results as possible. It seems that "match_from_list"
must return a list containing actual entries from the "candidate_list" argument,
so we store frozensets in "_match_from_list_cache" and test items from
"candidate_list" for membership in these sets. The filtering performed
by the mydep.unevaluated_atom.use and mydep.repo checks towards the end
of the function is also not cached, since this causes some test failures.

This results in a reduction of "emerge -uDvpU --with-bdeps=y @world"
runtime from 43.53 -> 40.15 sec -- an 8.4% speedup.
---
 lib/portage/dep/__init__.py | 359 +++++++++++++++++++-----------------
 1 file changed, 189 insertions(+), 170 deletions(-)

diff --git a/lib/portage/dep/__init__.py b/lib/portage/dep/__init__.py
index df296dd81..dbd23bb23 100644
--- a/lib/portage/dep/__init__.py
+++ b/lib/portage/dep/__init__.py
@@ -2174,6 +2174,8 @@ def best_match_to_list(mypkg, mylist):
 
        return bestm
 
+_match_from_list_cache = {}
+
 def match_from_list(mydep, candidate_list):
        """
        Searches list for entries that matches the package.
@@ -2197,209 +2199,226 @@ def match_from_list(mydep, candidate_list):
        if not isinstance(mydep, Atom):
                mydep = Atom(mydep, allow_wildcard=True, allow_repo=True)
 
-       mycpv     = mydep.cpv
-       mycpv_cps = catpkgsplit(mycpv) # Can be None if not specific
-       build_id  = mydep.build_id
+       cache_key = (mydep, tuple(candidate_list))
+       key_has_hash = True
+       cache_entry = None
+       if mydep.build_id is None and key_has_hash:
+               try:
+                       cache_entry = _match_from_list_cache.get(cache_key)
+               except TypeError:
+                       key_has_hash = False
 
-       if not mycpv_cps:
-               ver = None
-               rev = None
-       else:
-               cat, pkg, ver, rev = mycpv_cps
-               if mydep == mycpv:
-                       raise KeyError(_("Specific key requires an operator"
-                               " (%s) (try adding an '=')") % (mydep))
-
-       if ver and rev:
-               operator = mydep.operator
-               if not operator:
-                       writemsg(_("!!! Invalid atom: %s\n") % mydep, 
noiselevel=-1)
-                       return []
+       if cache_entry is not None:
+               # Note: the list returned by this function must contain actual 
entries
+               # from "candidate_list", so store frozensets in 
"_match_from_list_cache"
+               # and test items from "candidate_list" for membership in these 
sets.
+               mylist = [x for x in candidate_list if x in cache_entry]
        else:
-               operator = None
-
-       mylist = []
+               mycpv     = mydep.cpv
+               mycpv_cps = catpkgsplit(mycpv) # Can be None if not specific
+               build_id  = mydep.build_id
 
-       if mydep.extended_syntax:
+               if not mycpv_cps:
+                       ver = None
+                       rev = None
+               else:
+                       cat, pkg, ver, rev = mycpv_cps
+                       if mydep == mycpv:
+                               raise KeyError(_("Specific key requires an 
operator"
+                                       " (%s) (try adding an '=')") % (mydep))
 
-               for x in candidate_list:
-                       cp = getattr(x, "cp", None)
-                       if cp is None:
-                               mysplit = catpkgsplit(remove_slot(x))
-                               if mysplit is not None:
-                                       cp = mysplit[0] + '/' + mysplit[1]
+               if ver and rev:
+                       operator = mydep.operator
+                       if not operator:
+                               writemsg(_("!!! Invalid atom: %s\n") % mydep, 
noiselevel=-1)
+                               return []
+               else:
+                       operator = None
 
-                       if cp is None:
-                               continue
+               mylist = []
 
-                       if cp == mycpv or extended_cp_match(mydep.cp, cp):
-                               mylist.append(x)
+               if mydep.extended_syntax:
 
-               if mylist and mydep.operator == "=*":
+                       for x in candidate_list:
+                               cp = getattr(x, "cp", None)
+                               if cp is None:
+                                       mysplit = catpkgsplit(remove_slot(x))
+                                       if mysplit is not None:
+                                               cp = mysplit[0] + '/' + 
mysplit[1]
 
-                       candidate_list = mylist
-                       mylist = []
-                       # Currently, only \*\w+\* is supported.
-                       ver = mydep.version[1:-1]
+                               if cp is None:
+                                       continue
 
-                       for x in candidate_list:
-                               x_ver = getattr(x, "version", None)
-                               if x_ver is None:
-                                       xs = catpkgsplit(remove_slot(x))
-                                       if xs is None:
-                                               continue
-                                       x_ver = "-".join(xs[-2:])
-                               if ver in x_ver:
+                               if cp == mycpv or extended_cp_match(mydep.cp, 
cp):
                                        mylist.append(x)
 
-       elif operator is None:
-               for x in candidate_list:
-                       cp = getattr(x, "cp", None)
-                       if cp is None:
-                               mysplit = catpkgsplit(remove_slot(x))
-                               if mysplit is not None:
-                                       cp = mysplit[0] + '/' + mysplit[1]
+                       if mylist and mydep.operator == "=*":
 
-                       if cp is None:
-                               continue
+                               candidate_list = mylist
+                               mylist = []
+                               # Currently, only \*\w+\* is supported.
+                               ver = mydep.version[1:-1]
 
-                       if cp == mydep.cp:
-                               mylist.append(x)
+                               for x in candidate_list:
+                                       x_ver = getattr(x, "version", None)
+                                       if x_ver is None:
+                                               xs = catpkgsplit(remove_slot(x))
+                                               if xs is None:
+                                                       continue
+                                               x_ver = "-".join(xs[-2:])
+                                       if ver in x_ver:
+                                               mylist.append(x)
 
-       elif operator == "=": # Exact match
-               for x in candidate_list:
-                       xcpv = getattr(x, "cpv", None)
-                       if xcpv is None:
-                               xcpv = remove_slot(x)
-                       if not cpvequal(xcpv, mycpv):
-                               continue
-                       if (build_id is not None and
-                               getattr(xcpv, "build_id", None) != build_id):
-                               continue
-                       mylist.append(x)
+               elif operator is None:
+                       for x in candidate_list:
+                               cp = getattr(x, "cp", None)
+                               if cp is None:
+                                       mysplit = catpkgsplit(remove_slot(x))
+                                       if mysplit is not None:
+                                               cp = mysplit[0] + '/' + 
mysplit[1]
 
-       elif operator == "=*": # glob match
-               # XXX: Nasty special casing for leading zeros
-               # Required as =* is a literal prefix match, so can't 
-               # use vercmp
-               myver = mycpv_cps[2].lstrip("0")
-               if not myver or not myver[0].isdigit():
-                       myver = "0"+myver
-               if myver == mycpv_cps[2]:
-                       mycpv_cmp = mycpv
-               else:
-                       # Use replace to preserve the revision part if it exists
-                       # (mycpv_cps[3] can't be trusted because in contains r0
-                       # even when the input has no revision part).
-                       mycpv_cmp = mycpv.replace(
-                               mydep.cp + "-" + mycpv_cps[2],
-                               mydep.cp + "-" + myver, 1)
-               for x in candidate_list:
-                       try:
-                               x.cp
-                       except AttributeError:
-                               try:
-                                       pkg = _pkg_str(remove_slot(x))
-                               except InvalidData:
+                               if cp is None:
                                        continue
-                       else:
-                               pkg = x
 
-                       xs = pkg.cpv_split
-                       myver = xs[2].lstrip("0")
-                       if not myver or not myver[0].isdigit():
-                               myver = "0"+myver
-                       if myver == xs[2]:
-                               xcpv = pkg.cpv
-                       else:
-                               # Use replace to preserve the revision part if 
it exists.
-                               xcpv = pkg.cpv.replace(
-                                       pkg.cp + "-" + xs[2],
-                                       pkg.cp + "-" + myver, 1)
-                       if xcpv.startswith(mycpv_cmp):
-                               # =* glob matches only on boundaries between 
version parts,
-                               # so 1* does not match 10 (bug 560466).
-                               next_char = 
xcpv[len(mycpv_cmp):len(mycpv_cmp)+1]
-                               if (not next_char or next_char in '._-' or
-                                       mycpv_cmp[-1].isdigit() != 
next_char.isdigit()):
+                               if cp == mydep.cp:
                                        mylist.append(x)
 
-       elif operator == "~": # version, any revision, match
-               for x in candidate_list:
-                       xs = getattr(x, "cpv_split", None)
-                       if xs is None:
-                               xs = catpkgsplit(remove_slot(x))
-                       if xs is None:
-                               raise InvalidData(x)
-                       if not cpvequal(xs[0]+"/"+xs[1]+"-"+xs[2], 
mycpv_cps[0]+"/"+mycpv_cps[1]+"-"+mycpv_cps[2]):
-                               continue
-                       if xs[2] != ver:
-                               continue
-                       mylist.append(x)
+               elif operator == "=": # Exact match
+                       for x in candidate_list:
+                               xcpv = getattr(x, "cpv", None)
+                               if xcpv is None:
+                                       xcpv = remove_slot(x)
+                               if not cpvequal(xcpv, mycpv):
+                                       continue
+                               if (build_id is not None and
+                                       getattr(xcpv, "build_id", None) != 
build_id):
+                                       continue
+                               mylist.append(x)
 
-       elif operator in (">", ">=", "<", "<="):
-               for x in candidate_list:
-                       if hasattr(x, 'cp'):
-                               pkg = x
+               elif operator == "=*": # glob match
+                       # XXX: Nasty special casing for leading zeros
+                       # Required as =* is a literal prefix match, so can't 
+                       # use vercmp
+                       myver = mycpv_cps[2].lstrip("0")
+                       if not myver or not myver[0].isdigit():
+                               myver = "0"+myver
+                       if myver == mycpv_cps[2]:
+                               mycpv_cmp = mycpv
                        else:
+                               # Use replace to preserve the revision part if 
it exists
+                               # (mycpv_cps[3] can't be trusted because in 
contains r0
+                               # even when the input has no revision part).
+                               mycpv_cmp = mycpv.replace(
+                                       mydep.cp + "-" + mycpv_cps[2],
+                                       mydep.cp + "-" + myver, 1)
+                       for x in candidate_list:
                                try:
-                                       pkg = _pkg_str(remove_slot(x))
-                               except InvalidData:
-                                       continue
+                                       x.cp
+                               except AttributeError:
+                                       try:
+                                               pkg = _pkg_str(remove_slot(x))
+                                       except InvalidData:
+                                               continue
+                               else:
+                                       pkg = x
+
+                               xs = pkg.cpv_split
+                               myver = xs[2].lstrip("0")
+                               if not myver or not myver[0].isdigit():
+                                       myver = "0"+myver
+                               if myver == xs[2]:
+                                       xcpv = pkg.cpv
+                               else:
+                                       # Use replace to preserve the revision 
part if it exists.
+                                       xcpv = pkg.cpv.replace(
+                                               pkg.cp + "-" + xs[2],
+                                               pkg.cp + "-" + myver, 1)
+                               if xcpv.startswith(mycpv_cmp):
+                                       # =* glob matches only on boundaries 
between version parts,
+                                       # so 1* does not match 10 (bug 560466).
+                                       next_char = 
xcpv[len(mycpv_cmp):len(mycpv_cmp)+1]
+                                       if (not next_char or next_char in '._-' 
or
+                                               mycpv_cmp[-1].isdigit() != 
next_char.isdigit()):
+                                               mylist.append(x)
 
-                       if pkg.cp != mydep.cp:
-                               continue
-                       try:
-                               result = vercmp(pkg.version, mydep.version)
-                       except ValueError: # pkgcmp may return ValueError 
during int() conversion
-                               writemsg(_("\nInvalid package name: %s\n") % x, 
noiselevel=-1)
-                               raise
-                       if result is None:
-                               continue
-                       elif operator == ">":
-                               if result > 0:
-                                       mylist.append(x)
-                       elif operator == ">=":
-                               if result >= 0:
-                                       mylist.append(x)
-                       elif operator == "<":
-                               if result < 0:
-                                       mylist.append(x)
-                       elif operator == "<=":
-                               if result <= 0:
-                                       mylist.append(x)
-                       else:
-                               raise KeyError(_("Unknown operator: %s") % 
mydep)
-       else:
-               raise KeyError(_("Unknown operator: %s") % mydep)
+               elif operator == "~": # version, any revision, match
+                       for x in candidate_list:
+                               xs = getattr(x, "cpv_split", None)
+                               if xs is None:
+                                       xs = catpkgsplit(remove_slot(x))
+                               if xs is None:
+                                       raise InvalidData(x)
+                               if not cpvequal(xs[0]+"/"+xs[1]+"-"+xs[2], 
mycpv_cps[0]+"/"+mycpv_cps[1]+"-"+mycpv_cps[2]):
+                                       continue
+                               if xs[2] != ver:
+                                       continue
+                               mylist.append(x)
 
-       if mydep.slot is not None:
-               candidate_list = mylist
-               mylist = []
-               for x in candidate_list:
-                       x_pkg = None
-                       try:
-                               x.cpv
-                       except AttributeError:
-                               xslot = dep_getslot(x)
-                               if xslot is not None:
+               elif operator in (">", ">=", "<", "<="):
+                       for x in candidate_list:
+                               if hasattr(x, 'cp'):
+                                       pkg = x
+                               else:
                                        try:
-                                               x_pkg = 
_pkg_str(remove_slot(x), slot=xslot)
+                                               pkg = _pkg_str(remove_slot(x))
                                        except InvalidData:
                                                continue
-                       else:
-                               x_pkg = x
 
-                       if x_pkg is None:
-                               mylist.append(x)
-                       else:
+                               if pkg.cp != mydep.cp:
+                                       continue
                                try:
-                                       x_pkg.slot
+                                       result = vercmp(pkg.version, 
mydep.version)
+                               except ValueError: # pkgcmp may return 
ValueError during int() conversion
+                                       writemsg(_("\nInvalid package name: 
%s\n") % x, noiselevel=-1)
+                                       raise
+                               if result is None:
+                                       continue
+                               elif operator == ">":
+                                       if result > 0:
+                                               mylist.append(x)
+                               elif operator == ">=":
+                                       if result >= 0:
+                                               mylist.append(x)
+                               elif operator == "<":
+                                       if result < 0:
+                                               mylist.append(x)
+                               elif operator == "<=":
+                                       if result <= 0:
+                                               mylist.append(x)
+                               else:
+                                       raise KeyError(_("Unknown operator: 
%s") % mydep)
+               else:
+                       raise KeyError(_("Unknown operator: %s") % mydep)
+
+               if mydep.slot is not None:
+                       candidate_list = mylist
+                       mylist = []
+                       for x in candidate_list:
+                               x_pkg = None
+                               try:
+                                       x.cpv
                                except AttributeError:
+                                       xslot = dep_getslot(x)
+                                       if xslot is not None:
+                                               try:
+                                                       x_pkg = 
_pkg_str(remove_slot(x), slot=xslot)
+                                               except InvalidData:
+                                                       continue
+                               else:
+                                       x_pkg = x
+
+                               if x_pkg is None:
                                        mylist.append(x)
                                else:
-                                       if _match_slot(mydep, x_pkg):
+                                       try:
+                                               x_pkg.slot
+                                       except AttributeError:
                                                mylist.append(x)
+                                       else:
+                                               if _match_slot(mydep, x_pkg):
+                                                       mylist.append(x)
+               if key_has_hash:
+                       _match_from_list_cache[cache_key] = frozenset(mylist)
 
        if mydep.unevaluated_atom.use:
                candidate_list = mylist
-- 
2.27.0.212.ge8ba1cc988-goog


Reply via email to