[PATCH 5 of 6 frozen-repos] localrepo: evaluate revsets against frozen repos

Gregory Szorc gregory.szorc at gmail.com
Sat Nov 21 19:14:02 CST 2015


# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1448146482 28800
#      Sat Nov 21 14:54:42 2015 -0800
# Node ID ad891b362564b30ec69bc844a3fd98e73b6e032e
# Parent  888c2171adffa8340406b50aae02375f7bef50f4
localrepo: evaluate revsets against frozen repos

Previously, revsets were evaluated against a repository/changelog that
could change. This felt wrong. And, changectx lookups during revset
evaluation would result in repoview constantly performing changelog
correctness checks, adding overhead.

This patch results in some significant performance wins, especially
when changectx are involved. There are some minor regressions, but
the absolute time increase is so small that they can arguably be
ignored. A detailed analysis follows.

Running the revset benchmarks in default mode of returning integers,
we see some interesting changes:

revset #1: draft()
   plain
0) 0.000040
1) 0.000053 132%

   plain
0) 0.000233
1) 0.000236

revset #7: author(lmoscovicz)
   plain
0) 0.994968
1) 0.702156  70%

revset #8: author(mpm)
   plain
0) 0.982039
1) 0.696124  70%

revset #9: author(lmoscovicz) or author(mpm)
   plain
0) 1.944505
1) 1.372315  70%

revset #10: author(mpm) or author(lmoscovicz)
   plain
0) 1.970464
1) 1.393157  70%

revset #13: roots((tip~100::) - (tip~100::tip))
   plain
0) 0.000636
1) 0.000603  94%

revset #15: 42:68 and roots(42:tip)
   plain
0) 0.000226
1) 0.000178  78%

revset #19: draft()
   plain
0) 0.000040
1) 0.000056 140%

revset #22: (not public() - obsolete())
   plain
0) 0.000088
1) 0.000111 126%

revset #23: (_intlist('20000\x0020001')) and merge()
   plain
0) 0.000066
1) 0.000086 130%

First, the improvements. revsets with author() improved significantly.
The reason is that unlike most changesets, author() needs to obtain a
changectx to inspect the author field. And since repo.changelog lookups
are faster, that revset function became faster.

Now, the regressions. The percentages here are concerning. However, when
you look at the absolute values, we're going from e.g. 40us/call to
53us/call. Across the board, very fast revsets regressed by 10-20us.
Profiling reveals this regression is because of the creation and
instantiation of the new frozen repo class. The very act of dynamically
defining and then instantiating *any* proxy class appears to add this
overhead: even if the class's __init__ does practically nothing. While
there is a regression here, the wall values are so small that I don't
think it is concerning. It's also worth noting that if I modify
`hg perfrevset` to re-use the same frozen repo instance (instead of
instantiating a new one every benchmark loop), benchmark times improve
across the board! So, as more internal consumers start using frozen
repo consumers, there will be fewer frozen repo classes instantiated
and less of a performance penalty. Of course, a penalty of 20us is
probably not worrying about.

When we benchmark revsets obtaining changectxs, we see a more drastic
improvement:

revset #0: all()
   plain
0) 0.164450
1) 0.057012  34%

revset #1: draft()
   plain
0) 0.000131
1) 0.000091  69%

revset #2: ::tip
   plain
0) 0.202160
1) 0.094987  46%

revset #4: ::tip and draft()
   plain
0) 0.000269
1) 0.000251  93%

revset #5: 0::tip
   plain
0) 0.165030
1) 0.056363  34%

revset #7: author(lmoscovicz)
   plain
0) 0.997949
1) 0.727015  72%

revset #8: author(mpm)
   plain
0) 1.015544
1) 0.743669  73%

revset #9: author(lmoscovicz) or author(mpm)
   plain
0) 2.087182
1) 1.483129  71%

revset #10: author(mpm) or author(lmoscovicz)
   plain
0) 2.087200
1) 1.482509  71%

revset #11: tip:0
   plain
0) 0.169505
1) 0.056132  33%

revset #12: 0::
   plain
0) 0.210607
1) 0.089506  42%

revset #13: roots((tip~100::) - (tip~100::tip))
   plain
0) 0.000701
1) 0.000594  84%

revset #15: 42:68 and roots(42:tip)
   plain
0) 0.000234
1) 0.000177  75%

revset #16: ::p1(p1(tip))::
   plain
0) 0.266980
1) 0.144333  54%

revset #17: public()
   plain
0) 0.190038
1) 0.068690  36%

revset #18: :10000 and public()
   plain
0) 0.070399
1) 0.026308  37%

revset #19: draft()
   plain
0) 0.000131
1) 0.000090  68%

revset #22: (not public() - obsolete())
   plain
0) 0.000187
1) 0.000146  78%

revset #23: (_intlist('20000\x0020001')) and merge()
   plain
0) 0.000067
1) 0.000083 123%

revset #25: (20000::) - (20000)
   plain
0) 0.073760
1) 0.044524  60%

revset #26: (children(ancestor(tip~5, tip)) and ::(tip~5))::
   plain
0) 0.041947
1) 0.039813  94%

The one regression in revset #23 is likely probe overhead and normal
variation due to the extremely small times involved.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -530,26 +530,27 @@ class localrepository(object):
         The revset is specified as a string ``expr`` that may contain
         %-formatting to escape certain types. See ``revset.formatspec``.
 
         Return a revset.abstractsmartset, which is a list-like interface
         that contains integer revisions.
         '''
         expr = revset.formatspec(expr, *args)
         m = revset.match(None, expr)
-        return m(self)
+        return m(self.frozen())
 
     def set(self, expr, *args):
         '''Find revisions matching a revset and emit changectx instances.
 
         This is a convenience wrapper around ``revs()`` that iterates the
         result and is a generator of changectx instances.
         '''
-        for r in self.revs(expr, *args):
-            yield self[r]
+        frozen = self.frozen()
+        for r in frozen.revs(expr, *args):
+            yield frozen[r]
 
     def url(self):
         return 'file:' + self.root
 
     def hook(self, name, throw=False, **args):
         """Call a hook, passing this repo instance.
 
         This a convenience method to aid invoking hooks. Extensions likely


More information about the Mercurial-devel mailing list