From patchwork Thu Aug 7 18:08:46 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [2,of,3] repoview: cache hidden changesets From: David Soria Parra X-Patchwork-Id: 5312 Message-Id: <3da6820d38112cddfc21.1407434926@dev544.prn1.facebook.com> To: Date: Thu, 7 Aug 2014 11:08:46 -0700 # HG changeset patch # User David Soria Parra # Date 1407356774 25200 # Wed Aug 06 13:26:14 2014 -0700 # Node ID 3da6820d38112cddfc21e2b99ab02957f93d80cf # Parent 4ac33b2438c3d50d5f1f21423f0a51ed32d12994 repoview: cache hidden changesets Add a cache for hidden changesets. We cache all hidden changesets computed by _gethiddenblockers and check if a bookmark, tag or wd parent moved to a hidden changesets in which case we recompute parts. This significantly speeds up status, log, etc: without caching: $ time hg status hg status 0.72s user 0.20s system 100% cpu 0.917 total with caching $ time hg status hg status 0.49s user 0.15s system 100% cpu 0.645 total We invalidate the cache if the append only obsstore size changed from what we have observed before. In that case we recompute everything again. diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1013,6 +1013,7 @@ def invalidatevolatilesets(self): self.filteredrevcache.clear() obsolete.clearobscaches(self) + repoview.invalidatecache(self) def invalidatedirstate(self): '''Invalidates the dirstate, causing the next call to dirstate diff --git a/mercurial/repoview.py b/mercurial/repoview.py --- a/mercurial/repoview.py +++ b/mercurial/repoview.py @@ -10,6 +10,7 @@ import phases import util import obsolete +import struct import tags as tagsmod @@ -56,17 +57,61 @@ blockers.extend([cl.rev(t[0]) for t in tags.values()]) return frozenset(blockers) +cachefile = 'cache/hidden' +cachemeta = 'cache/hidden.len' +def invalidatecache(repo): + for filename in [cachefile, cachemeta]: + if repo.vfs.exists(filename): + repo.vfs.unlink(filename) + def computehidden(repo): """compute the set of hidden revision to filter During most operation hidden should be filtered.""" assert not repo.changelog.filteredrevs + + def iscachevalid(repo, hideable): + """The cache is invalid if the append-only obsstore has more markers + then we observed before.""" + if repo.vfs.exists(cachefile): + oldlen = repo.vfs.tryread(cachemeta) + if oldlen and int(oldlen) == len(hideable): + return True + return False + + hidden = frozenset() hideable = hideablerevs(repo) if hideable: cl = repo.changelog - blocked = cl.ancestors(_gethiddenblockers(repo), inclusive=True) - return frozenset(r for r in hideable if r not in blocked) - return frozenset() + if iscachevalid(repo, hideable): + data = repo.vfs.read(cachefile) + hidden = frozenset(struct.unpack('%sI' % (len(data) / 4), data)) + else: + # recompute cache + blocked = cl.ancestors(_gethiddenblockers(repo), inclusive=True) + hidden = frozenset(r for r in hideable if r not in blocked) + + # write cache to file + try: + data = struct.pack('%sI' % len(hidden), *hidden) + fh = repo.vfs(cachefile, 'w+b', atomictemp=True) + fh.write(data) + fh.close() + + # our observed size of the obsstore. used for cache invalidation. + fh = repo.vfs(cachemeta, 'w+b', atomictemp=True) + fh.write('%d' % len(hideable)) + fh.close() + except IOError: + ui.debug(_('error writing hidden changesets cache')) + + # check if we have wd parents, bookmarks or tags pointing to hidden + # changesets and remove those. + dynamic = hidden & _getdynamicblockers(repo) + if dynamic: + blocked = cl.ancestors(dynamic, inclusive=True) + hidden &= frozenset(r for r in hideable if r not in blocked) + return hidden def computeunserved(repo): """compute the set of revision that should be filtered when used a server