Mercurial > hg4j
changeset 324:283b294d1079
Explore alternatives to access file-changelog combined history
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Mon, 03 Oct 2011 06:47:20 +0200 |
parents | 4c7e3ba67213 |
children | f05c8b1f08c4 |
files | src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/Revlog.java test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java |
diffstat | 3 files changed, 155 insertions(+), 42 deletions(-) [+] |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgManifest.java Fri Sep 30 08:44:48 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Mon Oct 03 06:47:20 2011 +0200 @@ -411,6 +411,7 @@ return revisionNumber; } + // XXX likely can be replaced with Revlog.RevisionInspector public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { if (changelog2manifest != null) { // next assertion is not an error, rather assumption check, which is too development-related to be explicit exception -
--- a/src/org/tmatesoft/hg/repo/Revlog.java Fri Sep 30 08:44:48 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Mon Oct 03 06:47:20 2011 +0200 @@ -33,7 +33,9 @@ import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ArrayHelper; import org.tmatesoft.hg.internal.DataAccess; +import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.RevlogStream; +import org.tmatesoft.hg.util.Adaptable; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelSupport; import org.tmatesoft.hg.util.CancelledException; @@ -215,6 +217,62 @@ } } + @Experimental + public void walk(int start, int end, final Revlog.Inspector inspector) { + int lastRev = getLastRevision(); + if (start == TIP) { + start = lastRev; + } + if (end == TIP) { + end = lastRev; + } + final RevisionInspector revisionInsp = getAdapter(inspector, RevisionInspector.class); + final ParentInspector parentInsp = getAdapter(inspector, ParentInspector.class); + final Nodeid[] allRevisions = parentInsp == null ? null : new Nodeid[end - start + 1]; + + content.iterate(start, end, false, new RevlogStream.Inspector() { + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { + Nodeid nid = Nodeid.fromBinary(nodeid, 0); + if (revisionInsp != null) { + revisionInsp.next(revisionNumber, nid, linkRevision); + } + if (parentInsp != null) { + Nodeid p1 = parent1Revision == -1 ? Nodeid.NULL : allRevisions[parent1Revision]; + Nodeid p2 = parent2Revision == -1 ? Nodeid.NULL : allRevisions[parent2Revision]; + allRevisions[revisionNumber] = nid; + parentInsp.next(revisionNumber, nid, parent1Revision, parent2Revision, p1, p2); + } + } + }); + } + private static <T> T getAdapter(Object o, Class<T> adapterClass) { + if (adapterClass.isInstance(o)) { + return adapterClass.cast(o); + } + if (o instanceof Adaptable) { + return ((Adaptable) o).getAdapter(adapterClass); + } + return null; + } + + /** + * MARKER + */ + @Experimental + public interface Inspector { + } + + @Experimental + public interface RevisionInspector extends Inspector { + void next(int localRevision, Nodeid revision, int linkedRevision); + } + + @Experimental + public interface ParentInspector extends Inspector { + void next(int localRevision, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2); + } + /* * XXX think over if it's better to do either: * pw = getChangelog().new ParentWalker(); pw.init() and pass pw instance around as needed @@ -223,7 +281,7 @@ * * and yes, walker is not a proper name */ - public final class ParentWalker { + public final class ParentWalker implements ParentInspector { private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering @@ -241,43 +299,31 @@ return Revlog.this.getRepo(); } + public void next(int revisionNumber, Nodeid revision, int parent1Revision, int parent2Revision, Nodeid nidParent1, Nodeid nidParent2) { + if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) { + throw new IllegalStateException(); // sanity, revisions are sequential + } + int ix = revisionNumber; + sequential[ix] = sorted[ix] = revision; + if (parent1Revision != -1) { + firstParent[ix] = sequential[parent1Revision]; + } + if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1 + secondParent[ix] = sequential[parent2Revision]; + } + } + public void init() { - final RevlogStream stream = Revlog.this.content; - final int revisionCount = stream.revisionCount(); + final int revisionCount = Revlog.this.getRevisionCount(); firstParent = new Nodeid[revisionCount]; // although branches/merges are less frequent, and most of secondParent would be -1/null, some sort of // SparseOrderedList might be handy, provided its inner structures do not overweight simplicity of an array + // FIXME IntMap is right candidate? secondParent = new Nodeid[revisionCount]; // sequential = new Nodeid[revisionCount]; sorted = new Nodeid[revisionCount]; - - RevlogStream.Inspector insp = new RevlogStream.Inspector() { - - int ix = 0; - public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) { - if (ix != revisionNumber) { - // XXX temp code, just to make sure I understand what's going on here - // FIXME check against cpython or another tool-mangled repository - throw new IllegalStateException(); - } - if (parent1Revision >= revisionNumber || parent2Revision >= revisionNumber) { - throw new IllegalStateException(); // sanity, revisions are sequential - } - final Nodeid nid = new Nodeid(nodeid, true); - sequential[ix] = sorted[ix] = nid; - if (parent1Revision != -1) { - assert parent1Revision < ix; - firstParent[ix] = sequential[parent1Revision]; - } - if (parent2Revision != -1) { // revlog of DataAccess.java has p2 set when p1 is -1 - assert parent2Revision < ix; - secondParent[ix] = sequential[parent2Revision]; - } - ix++; - } - }; - stream.iterate(0, TIP, false, insp); + Revlog.this.walk(0, TIP, this); Arrays.sort(sorted); sorted2natural = new int[revisionCount]; for (int i = 0; i < revisionCount; i++) { @@ -424,7 +470,7 @@ * getLocalRevision(Nodeid) with straightforward lookup approach performs O(n/2) * #localRevision() is log(n), plus initialization is O(n) */ - public final class RevisionMap { + public final class RevisionMap implements RevisionInspector { /* * in fact, initialization is much slower as it instantiates Nodeids, while #getLocalRevision * compares directly against byte buffer. Measuring cpython with 70k+ gives 3 times difference (47 vs 171) @@ -432,7 +478,9 @@ */ /* - * XXX 3 * (x * 4) bytes. Can I do better? + * XXX 3 * (x * 4) bytes. Can I do better? + * It seems, yes. Don't need to keep sorted, always can emulate it with indirect access to sequential through sorted2natural. + * i.e. instead sorted[mid].compareTo(toFind), do sequential[sorted2natural[mid]].compareTo(toFind) */ private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering private Nodeid[] sorted; // for binary search @@ -444,23 +492,20 @@ public HgRepository getRepo() { return Revlog.this.getRepo(); } + + public void next(int localRevision, Nodeid revision, int linkedRevision) { + sequential[localRevision] = sorted[localRevision] = revision; + } /** * @return <code>this</code> for convenience. */ public RevisionMap init(/*XXX Pool<Nodeid> to reuse nodeids, if possible. */) { // XXX HgRepository.register((RepoChangeListener) this); // listen to changes in repo, re-init if needed? - final int revisionCount = Revlog.this.content.revisionCount(); + final int revisionCount = Revlog.this.getRevisionCount(); sequential = new Nodeid[revisionCount]; sorted = new Nodeid[revisionCount]; - RevlogStream.Inspector insp = new RevlogStream.Inspector() { - - public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { - final Nodeid nid = new Nodeid(nodeid, true); - sequential[revisionNumber] = sorted[revisionNumber] = nid; - } - }; - Revlog.this.content.iterate(0, TIP, false, insp); + Revlog.this.walk(0, TIP, this); // next is alternative to Arrays.sort(sorted), and build sorted2natural looking up each element of sequential in sorted. // the way sorted2natural was build is O(n*log n). final ArrayHelper ah = new ArrayHelper();
--- a/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Fri Sep 30 08:44:48 2011 +0200 +++ b/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Mon Oct 03 06:47:20 2011 +0200 @@ -1,5 +1,7 @@ package org.tmatesoft.hg.test; +import static org.tmatesoft.hg.repo.HgRepository.TIP; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -8,6 +10,7 @@ import java.util.List; import java.util.Map; +import org.junit.Assert; import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgChangeset; import org.tmatesoft.hg.core.HgChangesetHandler; @@ -40,11 +43,75 @@ // m.collectTagsPerFile(); // m.manifestWalk(); // m.changelogWalk(); - m.revisionMap(); +// m.revisionMap(); + m.buildFile2ChangelogRevisionMap(); m = null; System.gc(); System.out.printf("Free mem: %,d\n", Runtime.getRuntime().freeMemory()); } + + /* + * .hgtags, 261 revisions + * Approach 1: total 83, init: 0, iteration: 82 + * Approach 2: total 225, init: 206, iteration: 19 + * README, 465 revisions + * Approach 1: total 162, init: 0, iteration: 161 + * Approach 2: total 231, init: 198, iteration: 32 + * configure.in, 1109 revisions + * Approach 1: total 409, init: 1, iteration: 407 + * Approach 2: total 277, init: 203, iteration: 74 + */ + private void buildFile2ChangelogRevisionMap() throws Exception { + final HgRepository repository = new HgLookup().detect(new File("/temp/hg/cpython")); + final HgChangelog clog = repository.getChangelog(); + final HgDataFile fileNode = repository.getFileNode("configure.in"); + // warm-up + HgChangelog.RevisionMap clogMap = clog.new RevisionMap().init(); + HgDataFile.RevisionMap fileMap = fileNode.new RevisionMap().init(); + // + final int latestRevision = fileNode.getLastRevision(); + // + final long start_1 = System.nanoTime(); + fileMap = fileNode.new RevisionMap().init(); + final long start_1a = System.nanoTime(); + final Map<Nodeid, Nodeid> changesetToNodeid_1 = new HashMap<Nodeid, Nodeid>(); + for (int revision = 0; revision <= latestRevision; revision++) { + final Nodeid nodeId = fileMap.revision(revision); +// final Nodeid changesetId = fileNode.getChangesetRevision(nodeId); + int localCset = fileNode.getChangesetLocalRevision(revision); + final Nodeid changesetId = clog.getRevision(localCset); + changesetToNodeid_1.put(changesetId, nodeId); + } + final long end_1 = System.nanoTime(); + // + final long start_2 = System.nanoTime(); + clogMap = clog.new RevisionMap().init(); + fileMap = fileNode.new RevisionMap().init(); + final Map<Nodeid, Nodeid> changesetToNodeid_2 = new HashMap<Nodeid, Nodeid>(); + final long start_2a = System.nanoTime(); + for (int revision = 0; revision <= latestRevision; revision++) { + Nodeid nidFile = fileMap.revision(revision); + int localCset = fileNode.getChangesetLocalRevision(revision); + Nodeid nidCset = clogMap.revision(localCset); + changesetToNodeid_2.put(nidCset, nidFile); + } + final long end_2 = System.nanoTime(); + Assert.assertEquals(changesetToNodeid_1, changesetToNodeid_2); + // + final long start_3 = System.nanoTime(); + final Map<Nodeid, Nodeid> changesetToNodeid_3 = new HashMap<Nodeid, Nodeid>(); + fileNode.walk(0, TIP, new HgDataFile.RevisionInspector() { + + public void next(int localRevision, Nodeid revision, int linkedRevision) { + changesetToNodeid_3.put(clog.getRevision(linkedRevision), revision); + } + }); + final long end_3 = System.nanoTime(); + Assert.assertEquals(changesetToNodeid_1, changesetToNodeid_3); + System.out.printf("Approach 1: total %d, init: %d, iteration: %d\n", (end_1 - start_1)/1000000, (start_1a - start_1)/1000000, (end_1 - start_1a)/1000000); + System.out.printf("Approach 2: total %d, init: %d, iteration: %d\n", (end_2 - start_2)/1000000, (start_2a - start_2)/1000000, (end_2 - start_2a)/1000000); + System.out.printf("Approach 3: total %d\n", (end_3 - start_3)/1000000); + } /* * Each 5000 revisions from cpython, total 15 revisions