changeset 27470:d394a1a3708a

perf: add perfrevlogrevision As part of investigating performance improvements to revlog reading, I needed a mechanism to measure every part of revlog reading so I knew where time was spent and how effective optimizations were. This patch implements a perf command for benchmarking the various stages of reading a single revlog revision. When executed against a manifest revision at the end of a 30,000+ long delta chain in mozilla-central, the command demonstrates that ~80% of time is spent in zlib decompression.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 20 Dec 2015 18:38:21 -0800
parents 8914296e74a2
children fe79f86099ae
files contrib/perf.py tests/test-contrib-perf.t
diffstat 2 files changed, 96 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/perf.py	Sun Dec 20 19:02:02 2015 -0800
+++ b/contrib/perf.py	Sun Dec 20 18:38:21 2015 -0800
@@ -2,12 +2,14 @@
 '''helper extension to measure performance'''
 
 from mercurial import cmdutil, scmutil, util, commands, obsolete
-from mercurial import repoview, branchmap, merge, copies, error
+from mercurial import repoview, branchmap, merge, copies, error, revlog
+from mercurial import mdiff
 import time, os, sys
 import random
 import functools
 
 formatteropts = commands.formatteropts
+revlogopts = commands.debugrevlogopts
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -488,6 +490,96 @@
     timer(d)
     fm.end()
 
+@command('perfrevlogrevision', revlogopts + formatteropts +
+         [('', 'cache', False, 'use caches instead of clearing')],
+         '-c|-m|FILE REV')
+def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
+    """Benchmark obtaining a revlog revision.
+
+    Obtaining a revlog revision consists of roughly the following steps:
+
+    1. Compute the delta chain
+    2. Obtain the raw chunks for that delta chain
+    3. Decompress each raw chunk
+    4. Apply binary patches to obtain fulltext
+    5. Verify hash of fulltext
+
+    This command measures the time spent in each of these phases.
+    """
+    if opts.get('changelog') or opts.get('manifest'):
+        file_, rev = None, file_
+    elif rev is None:
+        raise error.CommandError('perfrevlogrevision', 'invalid arguments')
+
+    r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
+    node = r.lookup(rev)
+    rev = r.rev(node)
+
+    def dodeltachain(rev):
+        if not cache:
+            r.clearcaches()
+        r._deltachain(rev)
+
+    def doread(chain):
+        if not cache:
+            r.clearcaches()
+        r._chunkraw(chain[0], chain[-1])
+
+    def dodecompress(data, chain):
+        if not cache:
+            r.clearcaches()
+
+        start = r.start
+        length = r.length
+        inline = r._inline
+        iosize = r._io.size
+        buffer = util.buffer
+        offset = start(chain[0])
+
+        for rev in chain:
+            chunkstart = start(rev)
+            if inline:
+                chunkstart += (rev + 1) * iosize
+            chunklength = length(rev)
+            b = buffer(data, chunkstart - offset, chunklength)
+            revlog.decompress(b)
+
+    def dopatch(text, bins):
+        if not cache:
+            r.clearcaches()
+        mdiff.patches(text, bins)
+
+    def dohash(text):
+        if not cache:
+            r.clearcaches()
+        r._checkhash(text, node, rev)
+
+    def dorevision():
+        if not cache:
+            r.clearcaches()
+        r.revision(node)
+
+    chain = r._deltachain(rev)[0]
+    data = r._chunkraw(chain[0], chain[-1])
+    bins = r._chunks(chain)
+    text = str(bins[0])
+    bins = bins[1:]
+    text = mdiff.patches(text, bins)
+
+    benches = [
+        (lambda: dorevision(), 'full'),
+        (lambda: dodeltachain(rev), 'deltachain'),
+        (lambda: doread(chain), 'read'),
+        (lambda: dodecompress(data, chain), 'decompress'),
+        (lambda: dopatch(text, bins), 'patch'),
+        (lambda: dohash(text), 'hash'),
+    ]
+
+    for fn, title in benches:
+        timer, fm = gettimer(ui, opts)
+        timer(fn, title=title)
+        fm.end()
+
 @command('perfrevset',
          [('C', 'clear', False, 'clear volatile cache between each call.'),
           ('', 'contexts', False, 'obtain changectx for each revision')]
--- a/tests/test-contrib-perf.t	Sun Dec 20 19:02:02 2015 -0800
+++ b/tests/test-contrib-perf.t	Sun Dec 20 18:38:21 2015 -0800
@@ -91,6 +91,8 @@
                  (no help text available)
    perfrawfiles  (no help text available)
    perfrevlog    (no help text available)
+   perfrevlogrevision
+                 Benchmark obtaining a revlog revision.
    perfrevrange  (no help text available)
    perfrevset    benchmark the execution time of a revset
    perfstartup   (no help text available)
@@ -136,6 +138,7 @@
   $ hg perfpathcopies 1 2
   $ hg perfrawfiles 2
   $ hg perfrevlog .hg/store/data/a.i
+  $ hg perfrevlogrevision -m 0
   $ hg perfrevrange
   $ hg perfrevset 'all()'
   $ hg perfstartup