[PATCH 3 of 3 rfc] mpath: add a cffi version of fold, patches and patchedsize

Maciej Fijalkowski fijall at gmail.com
Mon Mar 7 10:43:17 EST 2016


# HG changeset patch
# User fijal
# Date 1456477676 -3600
#      Fri Feb 26 10:07:56 2016 +0100
# Node ID 50f9e5969e282b318b4f5b7f916c6eb21bdedb95
# Parent  e04ac5d0fefe1418e1906ec209c0a3657ad8b022
mpath: add a cffi version of fold, patches and patchedsize

This patch adds cffi-based mpatch and makes hg blame about 30%
faster on PyPy. The cffi process is as follows:

* build cffi extension for mpatch (mercurial/build_mpatch_cffi is a bad
  place, not sure where to put it)
* use cffi extension from mercurial/pure/mpatch via mpatch_cffi,
  we can merge those two files (the second depending on cffi
  the first one only conditionally importing stuff from the second)

This patch is not intended for merging, only for RFC

diff -r e04ac5d0fefe -r 50f9e5969e28 mercurial/build_mpatch_cffi.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/build_mpatch_cffi.py	Fri Feb 26 10:07:56 2016 +0100
@@ -0,0 +1,20 @@
+import os
+from cffi import FFI
+
+ffi = FFI()
+mpatch_c = os.path.join(os.path.dirname(__file__), 'mpatch.c')
+ffi.set_source("_mpatch_cffi", open(mpatch_c).read(), include_dirs=["mercurial"])
+ffi.cdef("""
+struct flist {
+    ...;
+};
+
+struct flist *decode(const char *bin, size_t len);
+struct flist *combine(struct flist *a, struct flist *b);
+size_t calcsize(size_t len, struct flist *l);
+void lfree(struct flist *a);
+static int apply(char *buf, const char *orig, size_t len, struct flist *l);
+size_t _patchedsize(long orig, char* bin, size_t patchlen);
+""")
+if __name__ == '__main__':
+    ffi.compile()
diff -r e04ac5d0fefe -r 50f9e5969e28 mercurial/mpatch_cffi.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/mpatch_cffi.py	Fri Feb 26 10:07:56 2016 +0100
@@ -0,0 +1,33 @@
+
+from _mpatch_cffi import lib, ffi
+
+def fold(bins, start, end, all):
+    if start + 1 == end:
+        to_pass = ffi.new("char[]", str(bins[start]))
+        all.append(to_pass)
+        return lib.decode(to_pass, len(to_pass) - 1)
+    lgt = (end - start) / 2
+    return lib.combine(fold(bins, start, start + lgt, all),
+                  fold(bins, start + lgt, end, all))
+
+def patches(text, bins):
+    all = []
+    lgt = len(bins)
+    if not lgt:
+        return text
+    patch = fold(bins, 0, lgt, all)
+    if not patch:
+        raise Exception("failed")
+    outlen = lib.calcsize(len(text), patch)
+    if outlen < 0:
+        lib.lfree(patch)
+        raise Exception("failed")
+    buf = ffi.new("char[]", outlen)
+    if not lib.apply(buf, text, len(text), patch):
+        lib.lfree(patch)
+        raise Exception("fail")
+    res = ffi.buffer(buf, outlen)[:]
+    return res
+
+def patchedsize(orig, bin):
+    return lib._patchedsize(orig, bin, len(bin))
diff -r e04ac5d0fefe -r 50f9e5969e28 mercurial/pure/mpatch.py
--- a/mercurial/pure/mpatch.py	Thu Feb 25 15:00:07 2016 +0100
+++ b/mercurial/pure/mpatch.py	Fri Feb 26 10:07:56 2016 +0100
@@ -10,6 +10,8 @@
 import cStringIO
 import struct
 
+from .. import modulepolicy, policynocffi
+
 StringIO = cStringIO.StringIO
 
 # This attempts to apply a series of patches in time proportional to
@@ -117,3 +119,10 @@
 
     outlen += orig - last
     return outlen
+
+if modulepolicy not in policynocffi:
+    try:
+        from mercurial.mpatch_cffi import patches, patchedsize
+    except ImportError:
+        if modulepolicy == 'cffi': # strict cffi import
+            raise
diff -r e04ac5d0fefe -r 50f9e5969e28 setup.py
--- a/setup.py	Thu Feb 25 15:00:07 2016 +0100
+++ b/setup.py	Fri Feb 26 10:07:56 2016 +0100
@@ -277,7 +277,8 @@
 
 
 class hgdist(Distribution):
-    pure = ispypy
+    pure = False
+    cffi = ispypy
 
     global_options = Distribution.global_options + \
                      [('pure', None, "use pure (slow) Python "
@@ -337,6 +338,9 @@
 
         if self.distribution.pure:
             self.distribution.ext_modules = []
+        elif self.distribution.cffi:
+            from mercurial import build_mpatch_cffi
+            self.distribution.ext_modules = [build_mpatch_cffi.ffi.distutils_extension()]
         else:
             h = os.path.join(get_python_inc(), 'Python.h')
             if not os.path.exists(h):
@@ -347,6 +351,8 @@
         dst, copied = build_py.copy_file(self, *args, **kwargs)
 
         if copied and dst.endswith('__init__.py'):
+            if self.distribution.cffi:
+                modulepolicy = 'cffi'
             if self.distribution.pure:
                 modulepolicy = 'py'
             else:


More information about the Mercurial-devel mailing list