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
--- a/mercurial/config.py Sat Sep 30 18:19:14 2017 +0530
+++ b/mercurial/config.py Wed Sep 27 18:07:48 2017 -0700
@@ -20,13 +20,14 @@
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 @@
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 @@
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 @@
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:
--- a/mercurial/util.py Sat Sep 30 18:19:14 2017 +0530
+++ b/mercurial/util.py Wed Sep 27 18:07:48 2017 -0700
@@ -588,6 +588,24 @@
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 @@
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