]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Release the gil around functions known to be slow
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 21 Dec 2020 02:58:24 +0000 (03:58 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 21 Dec 2020 15:50:56 +0000 (16:50 +0100)
Profiling some time ago showed that these functions take relevant time.
Other paths will be reviewed.

https://www.varrazzo.com/blog/2020/05/19/a-trip-into-optimisation/

psycopg3_c/psycopg3_c/generators.pyx
psycopg3_c/psycopg3_c/libpq.pxd
psycopg3_c/psycopg3_c/pq_cython.pyx

index ab840c6657a9096f0a4a457615b69795616b3450..16eb8267bebdb35e7560b53045957beab577c6cd 100644 (file)
@@ -70,6 +70,8 @@ def execute(PGconn pgconn) -> PQGen[List[pq.proto.PGresult]]:
     cdef libpq.PGconn *pgconn_ptr = pgconn.pgconn_ptr
     cdef int status
     cdef libpq.PGnotify *notify
+    cdef libpq.PGresult *pgres
+    cdef int cires, ibres
 
     # Start the generator by sending the connection fd, which won't change
     # during the query process.
@@ -82,19 +84,26 @@ def execute(PGconn pgconn) -> PQGen[List[pq.proto.PGresult]]:
 
         status = yield WAIT_RW
         if status & READY_R:
-            # This call may read notifies which will be saved in the
-            # PGconn buffer and passed to Python later.
-            if 1 != libpq.PQconsumeInput(pgconn_ptr):
+            with nogil:
+                # This call may read notifies which will be saved in the
+                # PGconn buffer and passed to Python later.
+                cires = libpq.PQconsumeInput(pgconn_ptr)
+            if 1 != cires:
                 raise pq.PQerror(
                     f"consuming input failed: {pq.error_message(pgconn)}")
         continue
 
     # Fetching the result
     while 1:
-        if 1 != libpq.PQconsumeInput(pgconn_ptr):
+        with nogil:
+            cires = libpq.PQconsumeInput(pgconn_ptr)
+            if cires == 1:
+                ibres = libpq.PQisBusy(pgconn_ptr)
+
+        if 1 != cires:
             raise pq.PQerror(
                 f"consuming input failed: {pq.error_message(pgconn)}")
-        if libpq.PQisBusy(pgconn_ptr):
+        if ibres:
             yield WAIT_R
             continue
 
@@ -112,12 +121,12 @@ def execute(PGconn pgconn) -> PQGen[List[pq.proto.PGresult]]:
                     break
                 libpq.PQfreemem(notify)
 
-        res = libpq.PQgetResult(pgconn_ptr)
-        if res is NULL:
+        pgres = libpq.PQgetResult(pgconn_ptr)
+        if pgres is NULL:
             break
-        results.append(PGresult._from_ptr(res))
+        results.append(PGresult._from_ptr(pgres))
 
-        status = libpq.PQresultStatus(res)
+        status = libpq.PQresultStatus(pgres)
         if status in (libpq.PGRES_COPY_IN, libpq.PGRES_COPY_OUT, libpq.PGRES_COPY_BOTH):
             # After entering copy mode the libpq will create a phony result
             # for every request so let's break the endless loop.
index a9e241d59ae262f73d49f010b6b5c8a443a8160d..d4498c4c6b3cccf891f93f0ba169850aa0cf324f 100644 (file)
@@ -224,8 +224,8 @@ cdef extern from "libpq-fe.h":
     int PQsendDescribePrepared(PGconn *conn, const char *stmtName)
     int PQsendDescribePortal(PGconn *conn, const char *portalName)
     PGresult *PQgetResult(PGconn *conn)
-    int PQconsumeInput(PGconn *conn)
-    int PQisBusy(PGconn *conn)
+    int PQconsumeInput(PGconn *conn) nogil
+    int PQisBusy(PGconn *conn) nogil
     int PQsetnonblocking(PGconn *conn, int arg)
     int PQisnonblocking(const PGconn *conn)
     int PQflush(PGconn *conn)
@@ -236,7 +236,7 @@ cdef extern from "libpq-fe.h":
     int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
 
     # 33.8. Asynchronous Notification
-    PGnotify *PQnotifies(PGconn *conn)
+    PGnotify *PQnotifies(PGconn *conn) nogil
 
     # 33.9. Functions Associated with the COPY Command
     int PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
@@ -244,7 +244,7 @@ cdef extern from "libpq-fe.h":
     int PQgetCopyData(PGconn *conn, char **buffer, int async)
 
     # 33.11. Miscellaneous Functions
-    void PQfreemem(void *ptr)
+    void PQfreemem(void *ptr) nogil
     void PQconninfoFree(PQconninfoOption *connOptions)
     PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
     int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs)
index fac39a5b2638de1ba1d91b7e00ab760e6f6cbee9..6dd973ac1e493b2f453c04b4b667bf25b27729a2 100644 (file)
@@ -41,7 +41,7 @@ def version():
     return impl.PQlibVersion()
 
 
-cdef void notice_receiver(void *arg, const impl.PGresult *res_ptr):
+cdef void notice_receiver(void *arg, const impl.PGresult *res_ptr) with gil:
     cdef PGconn pgconn = <object>arg
     if pgconn.notice_handler is None:
         return
@@ -401,7 +401,10 @@ cdef class PGconn:
             raise PQerror(f"consuming input failed: {error_message(self)}")
 
     def is_busy(self) -> int:
-        return impl.PQisBusy(self.pgconn_ptr)
+        cdef int rv
+        with nogil:
+            rv = impl.PQisBusy(self.pgconn_ptr)
+        return rv
 
     @property
     def nonblocking(self) -> int:
@@ -425,7 +428,10 @@ cdef class PGconn:
         return PGcancel._from_ptr(ptr)
 
     def notifies(self) -> Optional[PGnotify]:
-        cdef impl.PGnotify *ptr = impl.PQnotifies(self.pgconn_ptr)
+        cdef impl.PGnotify *ptr
+        with nogil:
+            ptr = impl.PQnotifies(self.pgconn_ptr)
+
         if ptr:
             ret = PGnotify(ptr.relname, ptr.be_pid, ptr.extra)
             impl.PQfreemem(ptr)