diff mercurial/config.py @ 34358: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
line wrap: on
line diff
--- 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: