Patchwork repoview: improve compute staticblockers perf

login
register
mail settings
Submitter Durham Goode
Date April 1, 2015, 8:57 p.m.
Message ID <7efd440569bacde8f596.1427921858@dev2000.prn2.facebook.com>
Download mbox | patch
Permalink /patch/8425/
State Accepted
Headers show

Comments

Durham Goode - April 1, 2015, 8:57 p.m.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1427917810 25200
#      Wed Apr 01 12:50:10 2015 -0700
# Node ID 7efd440569bacde8f596f7e11813a40f49a9d2f9
# Parent  1b97cc5d2272c272961cc3e1d738e521af012a40
repoview: improve compute staticblockers perf

Previously we would compute the repoview's static blockers by finding all the
children of hidden commits that were not hidden.  This was O(number of commits
since first hidden change) since 'children' requires walking every commit from
tip until the first hidden change.

The new algorithm walks all heads down until it sees a public commit. This makes
the computation O(number of draft) commits, which is much faster in large
repositories with a large number of commits and a low number of drafts.

On a large repo with 1000+ obsolete markers and the earliest draft commit around
tip~200000, this improves computehidden perf by 200x (2s to 0.01s).
Matt Mackall - April 1, 2015, 9:59 p.m.
On Wed, 2015-04-01 at 13:57 -0700, Durham Goode wrote:
> # HG changeset patch
> # User Durham Goode <durham@fb.com>
> # Date 1427917810 25200
> #      Wed Apr 01 12:50:10 2015 -0700
> # Node ID 7efd440569bacde8f596f7e11813a40f49a9d2f9
> # Parent  1b97cc5d2272c272961cc3e1d738e521af012a40
> repoview: improve compute staticblockers perf

Excellent, queued for default.

Patch

diff --git a/mercurial/repoview.py b/mercurial/repoview.py
--- a/mercurial/repoview.py
+++ b/mercurial/repoview.py
@@ -6,6 +6,7 @@ 
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import collections
 import copy
 import error
 import phases
@@ -13,6 +14,7 @@  import util
 import obsolete
 import struct
 import tags as tagsmod
+from node import nullrev
 
 def hideablerevs(repo):
     """Revisions candidates to be hidden
@@ -20,23 +22,37 @@  def hideablerevs(repo):
     This is a standalone function to help extensions to wrap it."""
     return obsolete.getrevs(repo, 'obsolete')
 
-def _getstaticblockers(repo):
+def _getstatichidden(repo):
     """Cacheable revisions blocking hidden changesets from being filtered.
 
     Additional non-cached hidden blockers are computed in _getdynamicblockers.
     This is a standalone function to help extensions to wrap it."""
     assert not repo.changelog.filteredrevs
     hideable = hideablerevs(repo)
-    blockers = set()
     if hideable:
-        # We use cl to avoid recursive lookup from repo[xxx]
-        cl = repo.changelog
-        firsthideable = min(hideable)
-        revs = cl.revs(start=firsthideable)
-        tofilter = repo.revs(
-            '(%ld) and children(%ld)', list(revs), list(hideable))
-        blockers.update([r for r in tofilter if r not in hideable])
-    return blockers
+        actuallyhidden = {}
+        getphase = repo._phasecache.phase
+        getparentrevs = repo.changelog.parentrevs
+        queue = collections.deque((r, False) for r in repo.changelog.headrevs())
+        while queue:
+            rev, blocked = queue.popleft()
+            phase = getphase(repo, rev)
+            # Skip nodes which are public (guaranteed to not be hidden) and
+            # nodes which have already been processed and won't be blocked by
+            # the previous node.
+            if phase == 0 or (not blocked and rev in actuallyhidden):
+                continue
+            if rev in hideable:
+                if blocked:
+                    actuallyhidden[rev] = False
+                else:
+                    actuallyhidden.setdefault(rev, True)
+            else:
+                blocked = True
+
+            for parent in (p for p in getparentrevs(rev) if p != nullrev):
+                queue.append((parent, blocked))
+    return set(rev for rev, hidden in actuallyhidden.iteritems() if hidden)
 
 def _getdynamicblockers(repo):
     """Non-cacheable revisions blocking hidden changesets from being filtered.
@@ -137,8 +153,7 @@  def computehidden(repo):
         cl = repo.changelog
         hidden = tryreadcache(repo, hideable)
         if hidden is None:
-            blocked = cl.ancestors(_getstaticblockers(repo), inclusive=True)
-            hidden = frozenset(r for r in hideable if r not in blocked)
+            hidden = frozenset(_getstatichidden(repo))
             trywritehiddencache(repo, hideable, hidden)
 
         # check if we have wd parents, bookmarks or tags pointing to hidden