# HG changeset patch # User Jun Wu # Date 2017-09-28 01:07:48 # Node ID c41444a39de25167cb965b8c3591a4e4910c4300 # Parent f975cb7c4dbe946799f0371380cd4d9abd64311a 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 diff --git a/mercurial/config.py b/mercurial/config.py --- a/mercurial/config.py +++ b/mercurial/config.py @@ -20,13 +20,14 @@ from . import ( class config(object): def __init__(self, data=None, includepaths=None): self._data = {} - self._source = {} self._unset = [] self._includepaths = includepaths or [] if data: for k in data._data: self._data[k] = data[k].copy() self._source = data._source.copy() + else: + self._source = util.cowdict() def copy(self): return config(self) def __contains__(self, section): @@ -39,13 +40,19 @@ class config(object): for d in self.sections(): yield d def update(self, src): + self._source = self._source.preparewrite() for s, n in src._unset: - if s in self and n in self._data[s]: + ds = self._data.get(s, None) + if ds is not None and n in ds: + self._data[s] = ds.preparewrite() del self._data[s][n] del self._source[(s, n)] for s in src: - if s not in self: - self._data[s] = util.sortdict() + ds = self._data.get(s, None) + if ds: + self._data[s] = ds.preparewrite() + else: + self._data[s] = util.cowsortdict() self._data[s].update(src._data[s]) self._source.update(src._source) def get(self, section, item, default=None): @@ -74,16 +81,21 @@ class config(object): assert not isinstance(value, str), ( 'config values may not be unicode strings on Python 3') if section not in self: - self._data[section] = util.sortdict() + self._data[section] = util.cowsortdict() + else: + self._data[section] = self._data[section].preparewrite() self._data[section][item] = value if source: + self._source = self._source.preparewrite() self._source[(section, item)] = source def restore(self, data): """restore data returned by self.backup""" + self._source = self._source.preparewrite() if len(data) == 4: # restore old data section, item, value, source = data + self._data[section] = self._data[section].preparewrite() self._data[section][item] = value self._source[(section, item)] = source else: @@ -149,7 +161,7 @@ class config(object): if remap: section = remap.get(section, section) if section not in self: - self._data[section] = util.sortdict() + self._data[section] = util.cowsortdict() continue m = itemre.match(l) if m: diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -588,6 +588,24 @@ def cachefunc(func): return f +class cow(object): + """helper class to make copy-on-write easier + + Call preparewrite before doing any writes. + """ + + def preparewrite(self): + """call this before writes, return self or a copied new object""" + if getattr(self, '_copied', 0): + self._copied -= 1 + return self.__class__(self) + return self + + def copy(self): + """always do a cheap copy""" + self._copied = getattr(self, '_copied', 0) + 1 + return self + class sortdict(collections.OrderedDict): '''a simple sorted dictionary @@ -613,6 +631,38 @@ class sortdict(collections.OrderedDict): for k, v in src: self[k] = v +class cowdict(cow, dict): + """copy-on-write dict + + Be sure to call d = d.preparewrite() before writing to d. + + >>> a = cowdict() + >>> a is a.preparewrite() + True + >>> b = a.copy() + >>> b is a + True + >>> c = b.copy() + >>> c is a + True + >>> a = a.preparewrite() + >>> b is a + False + >>> a is a.preparewrite() + True + >>> c = c.preparewrite() + >>> b is c + False + >>> b is b.preparewrite() + True + """ + +class cowsortdict(cow, sortdict): + """copy-on-write sortdict + + Be sure to call d = d.preparewrite() before writing to d. + """ + class transactional(object): """Base class for making a transactional type into a context manager.""" __metaclass__ = abc.ABCMeta