Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/context.py @ 29667:2f6d5c60f6fc stable
annotate: pre-calculate the "needed" dictionary (issue5360)
The "needed" dict is used as a reference counter to free items in the giant
"hist" dict. However, currently it is not very accurate and can lead to
dropping "hist" items unnecessarily, for example, with the following DAG,
-3-
/ \
0--1--2--4--
The current algorithm will visit and calculate rev 1 twice, undesired. And
it tries to be smart by clearing rev 1's parents: "pcache[1] = []" at the
time hist[1] being accessed (note: hist[1] needs to be used twice, by rev 2
and rev 3). It can result in incorrect results if p1 of rev 4 deletes chunks
belonging to rev 0.
However, simply removing "needed" is not okay, because it will consume 10x
memory:
# without any change
% HGRCPATH= lrun ./hg annotate mercurial/commands.py -r d130a38 3>&2 [1]
MEMORY 49074176
CPUTIME 9.213
REALTIME 9.270
# with "needed" removed
MEMORY 637673472
CPUTIME 8.164
REALTIME 8.249
This patch moves "needed" (and "pcache") calculation to a separate DFS to
address the issue. It improves perf and fixes issue5360 by correctly reusing
hist, while maintaining low memory usage. Some additional attempt has been
made to further reduce memory usage, like changing "pcache[f] = []" to "del
pcache[f]". Therefore the result can be both faster and lower memory usage:
# with this patch applied
MEMORY 47575040
CPUTIME 7.870
REALTIME 7.926
[1]: lrun is a lightweight sandbox built on Linux cgroup and namespace. It's
used to measure CPU and memory usage here. Source code is available at
github.com/quark-zju/lrun.
author | Jun Wu <quark@fb.com> |
---|---|
date | Fri, 02 Sep 2016 15:20:59 +0100 |
parents | 576ff900fcc7 |
children | 2c302c654451 |
comparison
equal
deleted
inserted
replaced
29666:d3b2da20a9c5 | 29667:2f6d5c60f6fc |
---|---|
988 | 988 |
989 # This algorithm would prefer to be recursive, but Python is a | 989 # This algorithm would prefer to be recursive, but Python is a |
990 # bit recursion-hostile. Instead we do an iterative | 990 # bit recursion-hostile. Instead we do an iterative |
991 # depth-first search. | 991 # depth-first search. |
992 | 992 |
993 # 1st DFS pre-calculates pcache and needed | |
993 visit = [base] | 994 visit = [base] |
994 hist = {} | |
995 pcache = {} | 995 pcache = {} |
996 needed = {base: 1} | 996 needed = {base: 1} |
997 while visit: | 997 while visit: |
998 f = visit.pop() | |
999 if f in pcache: | |
1000 continue | |
1001 pl = parents(f) | |
1002 pcache[f] = pl | |
1003 for p in pl: | |
1004 needed[p] = needed.get(p, 0) + 1 | |
1005 if p not in pcache: | |
1006 visit.append(p) | |
1007 | |
1008 # 2nd DFS does the actual annotate | |
1009 visit[:] = [base] | |
1010 hist = {} | |
1011 while visit: | |
998 f = visit[-1] | 1012 f = visit[-1] |
999 pcached = f in pcache | 1013 if f in hist: |
1000 if not pcached: | 1014 visit.pop() |
1001 pcache[f] = parents(f) | 1015 continue |
1002 | 1016 |
1003 ready = True | 1017 ready = True |
1004 pl = pcache[f] | 1018 pl = pcache[f] |
1005 for p in pl: | 1019 for p in pl: |
1006 if p not in hist: | 1020 if p not in hist: |
1007 ready = False | 1021 ready = False |
1008 visit.append(p) | 1022 visit.append(p) |
1009 if not pcached: | |
1010 needed[p] = needed.get(p, 0) + 1 | |
1011 if ready: | 1023 if ready: |
1012 visit.pop() | 1024 visit.pop() |
1013 reusable = f in hist | 1025 curr = decorate(f.data(), f) |
1014 if reusable: | |
1015 curr = hist[f] | |
1016 else: | |
1017 curr = decorate(f.data(), f) | |
1018 for p in pl: | 1026 for p in pl: |
1019 if not reusable: | 1027 curr = pair(hist[p], curr) |
1020 curr = pair(hist[p], curr) | |
1021 if needed[p] == 1: | 1028 if needed[p] == 1: |
1022 del hist[p] | 1029 del hist[p] |
1023 del needed[p] | 1030 del needed[p] |
1024 else: | 1031 else: |
1025 needed[p] -= 1 | 1032 needed[p] -= 1 |
1026 | 1033 |
1027 hist[f] = curr | 1034 hist[f] = curr |
1028 pcache[f] = [] | 1035 del pcache[f] |
1029 | 1036 |
1030 return zip(hist[base][0], hist[base][1].splitlines(True)) | 1037 return zip(hist[base][0], hist[base][1].splitlines(True)) |
1031 | 1038 |
1032 def ancestors(self, followfirst=False): | 1039 def ancestors(self, followfirst=False): |
1033 visit = {} | 1040 visit = {} |