Mercurial > public > mercurial-scm > hg
comparison mercurial/config.py @ 34357:c41444a39de2
config: use copy-on-write to improve copy performance
Previously, chg's `verify` call could take 30+ms loading and checking new
config files. With one socket redirection, that adds up to around 70ms,
which is a lot for fast commands (ex. `bookmark --hidden`).
When investigating closer, A lot of time was spent on actually spent on ui
copying, which is mainly about `config.config` and `dict` copying.
This patch makes that 20x faster by adopting copy-on-write. The
copy-on-write is performed at config section level.
Before:
In [1]: %timeit ui.copy()
100 loops, best of 3: 2.32 ms per loop
After:
In [1]: %timeit ui.copy()
10000 loops, best of 3: 128 us per loop
2ms may look not that bad, but it adds up pretty quickly with multiple
calls. A typical chg run may call it 4 times, which is about 10ms.
Differential Revision: https://phab.mercurial-scm.org/D808
author | Jun Wu <quark@fb.com> |
---|---|
date | Wed, 27 Sep 2017 18:07:48 -0700 |
parents | 0fa781320203 |
children | 0efdfb57b05c |
comparison
equal
deleted
inserted
replaced
34350:f975cb7c4dbe | 34357:c41444a39de2 |
---|---|
18 ) | 18 ) |
19 | 19 |
20 class config(object): | 20 class config(object): |
21 def __init__(self, data=None, includepaths=None): | 21 def __init__(self, data=None, includepaths=None): |
22 self._data = {} | 22 self._data = {} |
23 self._source = {} | |
24 self._unset = [] | 23 self._unset = [] |
25 self._includepaths = includepaths or [] | 24 self._includepaths = includepaths or [] |
26 if data: | 25 if data: |
27 for k in data._data: | 26 for k in data._data: |
28 self._data[k] = data[k].copy() | 27 self._data[k] = data[k].copy() |
29 self._source = data._source.copy() | 28 self._source = data._source.copy() |
29 else: | |
30 self._source = util.cowdict() | |
30 def copy(self): | 31 def copy(self): |
31 return config(self) | 32 return config(self) |
32 def __contains__(self, section): | 33 def __contains__(self, section): |
33 return section in self._data | 34 return section in self._data |
34 def hasitem(self, section, item): | 35 def hasitem(self, section, item): |
37 return self._data.get(section, {}) | 38 return self._data.get(section, {}) |
38 def __iter__(self): | 39 def __iter__(self): |
39 for d in self.sections(): | 40 for d in self.sections(): |
40 yield d | 41 yield d |
41 def update(self, src): | 42 def update(self, src): |
43 self._source = self._source.preparewrite() | |
42 for s, n in src._unset: | 44 for s, n in src._unset: |
43 if s in self and n in self._data[s]: | 45 ds = self._data.get(s, None) |
46 if ds is not None and n in ds: | |
47 self._data[s] = ds.preparewrite() | |
44 del self._data[s][n] | 48 del self._data[s][n] |
45 del self._source[(s, n)] | 49 del self._source[(s, n)] |
46 for s in src: | 50 for s in src: |
47 if s not in self: | 51 ds = self._data.get(s, None) |
48 self._data[s] = util.sortdict() | 52 if ds: |
53 self._data[s] = ds.preparewrite() | |
54 else: | |
55 self._data[s] = util.cowsortdict() | |
49 self._data[s].update(src._data[s]) | 56 self._data[s].update(src._data[s]) |
50 self._source.update(src._source) | 57 self._source.update(src._source) |
51 def get(self, section, item, default=None): | 58 def get(self, section, item, default=None): |
52 return self._data.get(section, {}).get(item, default) | 59 return self._data.get(section, {}).get(item, default) |
53 | 60 |
72 def set(self, section, item, value, source=""): | 79 def set(self, section, item, value, source=""): |
73 if pycompat.ispy3: | 80 if pycompat.ispy3: |
74 assert not isinstance(value, str), ( | 81 assert not isinstance(value, str), ( |
75 'config values may not be unicode strings on Python 3') | 82 'config values may not be unicode strings on Python 3') |
76 if section not in self: | 83 if section not in self: |
77 self._data[section] = util.sortdict() | 84 self._data[section] = util.cowsortdict() |
85 else: | |
86 self._data[section] = self._data[section].preparewrite() | |
78 self._data[section][item] = value | 87 self._data[section][item] = value |
79 if source: | 88 if source: |
89 self._source = self._source.preparewrite() | |
80 self._source[(section, item)] = source | 90 self._source[(section, item)] = source |
81 | 91 |
82 def restore(self, data): | 92 def restore(self, data): |
83 """restore data returned by self.backup""" | 93 """restore data returned by self.backup""" |
94 self._source = self._source.preparewrite() | |
84 if len(data) == 4: | 95 if len(data) == 4: |
85 # restore old data | 96 # restore old data |
86 section, item, value, source = data | 97 section, item, value, source = data |
98 self._data[section] = self._data[section].preparewrite() | |
87 self._data[section][item] = value | 99 self._data[section][item] = value |
88 self._source[(section, item)] = source | 100 self._source[(section, item)] = source |
89 else: | 101 else: |
90 # no data before, remove everything | 102 # no data before, remove everything |
91 section, item = data | 103 section, item = data |
147 if m: | 159 if m: |
148 section = m.group(1) | 160 section = m.group(1) |
149 if remap: | 161 if remap: |
150 section = remap.get(section, section) | 162 section = remap.get(section, section) |
151 if section not in self: | 163 if section not in self: |
152 self._data[section] = util.sortdict() | 164 self._data[section] = util.cowsortdict() |
153 continue | 165 continue |
154 m = itemre.match(l) | 166 m = itemre.match(l) |
155 if m: | 167 if m: |
156 item = m.group(1) | 168 item = m.group(1) |
157 cont = True | 169 cont = True |