Patchwork D808: config: use copy-on-write to improve copy performance

login
register
mail settings
Submitter phabricator
Date Sept. 28, 2017, 1:11 a.m.
Message ID <a2855bc63e4e1ebe93f4cc0145785e03@localhost.localdomain>
Download mbox | patch
Permalink /patch/24193/
State Not Applicable
Headers show

Comments

phabricator - Sept. 28, 2017, 1:11 a.m.
quark updated this revision to Diff 2135.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D808?vs=2073&id=2135

REVISION DETAIL
  https://phab.mercurial-scm.org/D808

AFFECTED FILES
  mercurial/config.py
  mercurial/util.py

CHANGE DETAILS




To: quark, #hg-reviewers, mbthomas, durin42
Cc: durin42, mbthomas, mercurial-devel

Patch

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -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
diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -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: