diff -r 7236facefd4f -r e4f514627514 contrib/perf.py --- a/contrib/perf.py Sun May 28 10:56:28 2017 -0700 +++ b/contrib/perf.py Sun May 28 11:13:10 2017 -0700 @@ -23,6 +23,7 @@ import gc import os import random +import struct import sys import time from mercurial import ( @@ -34,6 +35,7 @@ extensions, mdiff, merge, + revlog, util, ) @@ -857,6 +859,122 @@ timer(d, title) fm.end() +@command('perfrevlogindex', revlogopts + formatteropts, + '-c|-m|FILE') +def perfrevlogindex(ui, repo, file_=None, **opts): + """Benchmark operations against a revlog index. + + This tests constructing a revlog instance, reading index data, + parsing index data, and performing various operations related to + index data. + """ + + rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts) + + opener = getattr(rl, 'opener') # trick linter + indexfile = rl.indexfile + data = opener.read(indexfile) + + header = struct.unpack('>I', data[0:4])[0] + version = header & 0xFFFF + if version == 1: + revlogio = revlog.revlogio() + inline = header & (1 << 16) + else: + raise error.Abort(('unsupported revlog version: %d') % version) + + rllen = len(rl) + + node0 = rl.node(0) + node25 = rl.node(rllen // 4) + node50 = rl.node(rllen // 2) + node75 = rl.node(rllen // 4 * 3) + node100 = rl.node(rllen - 1) + + allrevs = range(rllen) + allrevsrev = list(reversed(allrevs)) + allnodes = [rl.node(rev) for rev in range(rllen)] + allnodesrev = list(reversed(allnodes)) + + def constructor(): + revlog.revlog(opener, indexfile) + + def read(): + with opener(indexfile) as fh: + fh.read() + + def parseindex(): + revlogio.parseindex(data, inline) + + def getentry(revornode): + index = revlogio.parseindex(data, inline)[0] + index[revornode] + + def getentries(revs, count=1): + index = revlogio.parseindex(data, inline)[0] + + for i in range(count): + for rev in revs: + index[rev] + + def resolvenode(node): + nodemap = revlogio.parseindex(data, inline)[1] + # This only works for the C code. + if nodemap is None: + return + + try: + nodemap[node] + except error.RevlogError: + pass + + def resolvenodes(nodes, count=1): + nodemap = revlogio.parseindex(data, inline)[1] + if nodemap is None: + return + + for i in range(count): + for node in nodes: + try: + nodemap[node] + except error.RevlogError: + pass + + benches = [ + (constructor, 'revlog constructor'), + (read, 'read'), + (parseindex, 'create index object'), + (lambda: getentry(0), 'retrieve index entry for rev 0'), + (lambda: resolvenode('a' * 20), 'look up missing node'), + (lambda: resolvenode(node0), 'look up node at rev 0'), + (lambda: resolvenode(node25), 'look up node at 1/4 len'), + (lambda: resolvenode(node50), 'look up node at 1/2 len'), + (lambda: resolvenode(node75), 'look up node at 3/4 len'), + (lambda: resolvenode(node100), 'look up node at tip'), + # 2x variation is to measure caching impact. + (lambda: resolvenodes(allnodes), + 'look up all nodes (forward)'), + (lambda: resolvenodes(allnodes, 2), + 'look up all nodes 2x (forward)'), + (lambda: resolvenodes(allnodesrev), + 'look up all nodes (reverse)'), + (lambda: resolvenodes(allnodesrev, 2), + 'look up all nodes 2x (reverse)'), + (lambda: getentries(allrevs), + 'retrieve all index entries (forward)'), + (lambda: getentries(allrevs, 2), + 'retrieve all index entries 2x (forward)'), + (lambda: getentries(allrevsrev), + 'retrieve all index entries (reverse)'), + (lambda: getentries(allrevsrev, 2), + 'retrieve all index entries 2x (reverse)'), + ] + + for fn, title in benches: + timer, fm = gettimer(ui, opts) + timer(fn, title=title) + fm.end() + @command('perfrevlogrevisions', revlogopts + formatteropts + [('d', 'dist', 100, 'distance between the revisions'), ('s', 'startrev', 0, 'revision to start reading at'),