]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Optimise dumping int to text
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 7 Jan 2021 17:51:31 +0000 (18:51 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 8 Jan 2021 01:32:29 +0000 (02:32 +0100)
Code lifted from Postgres sources. However there is no config phase here
so it will require tweaking to work with other compilers or architectures)

psycopg3_c/psycopg3_c/types/numeric.pyx
psycopg3_c/psycopg3_c/types/numutils.c [new file with mode: 0644]
psycopg3_c/setup.py

index 0bca8ac03365d89b47f529f0e95e545ab7ca6504..41761b976fc9c291479742ea3ce2907b7f69dfef 100644 (file)
@@ -27,6 +27,16 @@ cdef extern from "Python.h":
     int Py_DTSF_ADD_DOT_0
 
 
+# defined in numutils.c
+cdef extern from *:
+    """
+#define MAXINT8LEN 20
+int pg_lltoa(int64_t value, char *a);
+    """
+    int MAXINT8LEN
+    int pg_lltoa(int64_t value, char *a)
+
+
 # @cython.final  # TODO? causes compile warnings
 cdef class IntDumper(CDumper):
 
@@ -36,10 +46,9 @@ cdef class IntDumper(CDumper):
         self.oid = oids.INT8_OID
 
     cdef Py_ssize_t cdump(self, obj, bytearray rv, Py_ssize_t offset) except -1:
-        cdef int size = 22  # max int as string
-        cdef char *buf = CDumper.ensure_size(rv, offset, size)
+        cdef char *buf = CDumper.ensure_size(rv, offset, MAXINT8LEN + 1)
         cdef long long val = PyLong_AsLongLong(obj)
-        cdef int written = PyOS_snprintf(buf, size, "%lld", val)
+        cdef int written = pg_lltoa(val, buf)
         return written
 
     def quote(self, obj) -> bytearray:
diff --git a/psycopg3_c/psycopg3_c/types/numutils.c b/psycopg3_c/psycopg3_c/types/numutils.c
new file mode 100644 (file)
index 0000000..6237df0
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Utilities to deal with numbers.
+ *
+ * Copyright (C) 2020 The Psycopg Team
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+/* configuration */
+
+#ifdef __GNUC__
+#define SIZEOF_VOID_P __SIZEOF_POINTER__
+#define HAVE__BUILTIN_CLZ 1
+#else
+#error 'you need to define this for your compiler'
+#endif
+
+/* TODO: this is a guess, it is on my Ubuntu 20.04 */
+#define HAVE_LONG_INT_64 1
+
+
+/*
+ * 64-bit integers
+ */
+#ifdef HAVE_LONG_INT_64
+/* Plain "long int" fits, use it */
+
+# ifndef HAVE_INT64
+typedef long int int64;
+# endif
+# ifndef HAVE_UINT64
+typedef unsigned long int uint64;
+# endif
+# define INT64CONST(x)  (x##L)
+# define UINT64CONST(x) (x##UL)
+#elif defined(HAVE_LONG_LONG_INT_64)
+/* We have working support for "long long int", use that */
+
+# ifndef HAVE_INT64
+typedef long long int int64;
+# endif
+# ifndef HAVE_UINT64
+typedef unsigned long long int uint64;
+# endif
+# define INT64CONST(x)  (x##LL)
+# define UINT64CONST(x) (x##ULL)
+#else
+/* neither HAVE_LONG_INT_64 nor HAVE_LONG_LONG_INT_64 */
+# error must have a working 64-bit integer datatype
+#endif
+
+
+#ifndef HAVE__BUILTIN_CLZ
+static const uint8_t pg_leftmost_one_pos[256] = {
+       0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+       4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+       6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+       6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+       6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+       6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+       7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+};
+#endif
+
+static const char DIGIT_TABLE[200] = {
+    '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0',
+    '7', '0', '8', '0', '9', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4',
+    '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '2', '0', '2', '1', '2',
+    '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9',
+    '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3',
+    '7', '3', '8', '3', '9', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4',
+    '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '5', '0', '5', '1', '5',
+    '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9',
+    '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6',
+    '7', '6', '8', '6', '9', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4',
+    '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '8', '0', '8', '1', '8',
+    '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9',
+    '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9',
+    '7', '9', '8', '9', '9'
+};
+
+
+/*
+ * pg_leftmost_one_pos64
+ *             As above, but for a 64-bit word.
+ */
+static inline int
+pg_leftmost_one_pos64(uint64_t word)
+{
+#ifdef HAVE__BUILTIN_CLZ
+#if defined(HAVE_LONG_INT_64)
+       return 63 - __builtin_clzl(word);
+#elif defined(HAVE_LONG_LONG_INT_64)
+       return 63 - __builtin_clzll(word);
+#else
+#error must have a working 64-bit integer datatype
+#endif
+#else                                                  /* !HAVE__BUILTIN_CLZ */
+       int                     shift = 64 - 8;
+
+       while ((word >> shift) == 0)
+               shift -= 8;
+
+       return shift + pg_leftmost_one_pos[(word >> shift) & 255];
+#endif                                                 /* HAVE__BUILTIN_CLZ */
+}
+
+
+static inline int
+decimalLength64(const uint64_t v)
+{
+       int                     t;
+       static const uint64_t PowersOfTen[] = {
+               UINT64CONST(1), UINT64CONST(10),
+               UINT64CONST(100), UINT64CONST(1000),
+               UINT64CONST(10000), UINT64CONST(100000),
+               UINT64CONST(1000000), UINT64CONST(10000000),
+               UINT64CONST(100000000), UINT64CONST(1000000000),
+               UINT64CONST(10000000000), UINT64CONST(100000000000),
+               UINT64CONST(1000000000000), UINT64CONST(10000000000000),
+               UINT64CONST(100000000000000), UINT64CONST(1000000000000000),
+               UINT64CONST(10000000000000000), UINT64CONST(100000000000000000),
+               UINT64CONST(1000000000000000000), UINT64CONST(10000000000000000000)
+       };
+
+       /*
+        * Compute base-10 logarithm by dividing the base-2 logarithm by a
+        * good-enough approximation of the base-2 logarithm of 10
+        */
+       t = (pg_leftmost_one_pos64(v) + 1) * 1233 / 4096;
+       return t + (v >= PowersOfTen[t]);
+}
+
+
+/*
+ * Get the decimal representation, not NUL-terminated, and return the length of
+ * same.  Caller must ensure that a points to at least MAXINT8LEN bytes.
+ */
+int
+pg_ulltoa_n(uint64_t value, char *a)
+{
+       int                     olength,
+                               i = 0;
+       uint32_t                value2;
+
+       /* Degenerate case */
+       if (value == 0)
+       {
+               *a = '0';
+               return 1;
+       }
+
+       olength = decimalLength64(value);
+
+       /* Compute the result string. */
+       while (value >= 100000000)
+       {
+               const uint64_t q = value / 100000000;
+               uint32_t                value2 = (uint32_t) (value - 100000000 * q);
+
+               const uint32_t c = value2 % 10000;
+               const uint32_t d = value2 / 10000;
+               const uint32_t c0 = (c % 100) << 1;
+               const uint32_t c1 = (c / 100) << 1;
+               const uint32_t d0 = (d % 100) << 1;
+               const uint32_t d1 = (d / 100) << 1;
+
+               char       *pos = a + olength - i;
+
+               value = q;
+
+               memcpy(pos - 2, DIGIT_TABLE + c0, 2);
+               memcpy(pos - 4, DIGIT_TABLE + c1, 2);
+               memcpy(pos - 6, DIGIT_TABLE + d0, 2);
+               memcpy(pos - 8, DIGIT_TABLE + d1, 2);
+               i += 8;
+       }
+
+       /* Switch to 32-bit for speed */
+       value2 = (uint32_t) value;
+
+       if (value2 >= 10000)
+       {
+               const uint32_t c = value2 - 10000 * (value2 / 10000);
+               const uint32_t c0 = (c % 100) << 1;
+               const uint32_t c1 = (c / 100) << 1;
+
+               char       *pos = a + olength - i;
+
+               value2 /= 10000;
+
+               memcpy(pos - 2, DIGIT_TABLE + c0, 2);
+               memcpy(pos - 4, DIGIT_TABLE + c1, 2);
+               i += 4;
+       }
+       if (value2 >= 100)
+       {
+               const uint32_t c = (value2 % 100) << 1;
+               char       *pos = a + olength - i;
+
+               value2 /= 100;
+
+               memcpy(pos - 2, DIGIT_TABLE + c, 2);
+               i += 2;
+       }
+       if (value2 >= 10)
+       {
+               const uint32_t c = value2 << 1;
+               char       *pos = a + olength - i;
+
+               memcpy(pos - 2, DIGIT_TABLE + c, 2);
+       }
+       else
+               *a = (char) ('0' + value2);
+
+       return olength;
+}
+
+/*
+ * pg_lltoa: converts a signed 64-bit integer to its string representation and
+ * returns strlen(a).
+ *
+ * Caller must ensure that 'a' points to enough memory to hold the result
+ * (at least MAXINT8LEN + 1 bytes, counting a leading sign and trailing NUL).
+ */
+int
+pg_lltoa(int64_t value, char *a)
+{
+       uint64_t                uvalue = value;
+       int                     len = 0;
+
+       if (value < 0)
+       {
+               uvalue = (uint64_t) 0 - uvalue;
+               a[len++] = '-';
+       }
+
+       len += pg_ulltoa_n(uvalue, a + len);
+       a[len] = '\0';
+       return len;
+}
index 87c185fc89bdc4ba0fbd02910d92946eb816cbaf..6495821a477eaae30b012aef4d9dacf1875fbb24 100644 (file)
@@ -57,7 +57,7 @@ class psycopg3_build_ext(build_ext):
             for ext in self.distribution.ext_modules:
                 for i in range(len(ext.sources)):
                     base, fext = os.path.splitext(ext.sources[i])
-                    if fext == ".c":
+                    if fext == ".c" and os.path.exists(base + ".pyx"):
                         ext.sources[i] = base + ".pyx"
 
             self.distribution.ext_modules = cythonize(
@@ -75,7 +75,10 @@ class psycopg3_build_ext(build_ext):
 # Some details missing, to be finished by psycopg3_build_ext.finalize_options
 pgext = Extension(
     "psycopg3_c._psycopg3",
-    ["psycopg3_c/_psycopg3.c"],
+    [
+        "psycopg3_c/_psycopg3.c",
+        "psycopg3_c/types/numutils.c",
+    ],
     libraries=["pq"],
     include_dirs=[],
 )