From: Jörg Breitbart Date: Wed, 10 Sep 2025 00:40:21 +0000 (+0200) Subject: alloca for the rescue X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=53bcd781436572c394e9b8ad9d41678b8abcfc15;p=thirdparty%2Fpsycopg.git alloca for the rescue --- diff --git a/psycopg_c/psycopg_c/_psycopg/transform.pyx b/psycopg_c/psycopg_c/_psycopg/transform.pyx index 1f8f0d3cc..c88fcf1fa 100644 --- a/psycopg_c/psycopg_c/_psycopg/transform.pyx +++ b/psycopg_c/psycopg_c/_psycopg/transform.pyx @@ -25,6 +25,11 @@ from psycopg.pq import Format as PqFormat from psycopg.rows import Row, RowMaker from psycopg._encodings import conn_encoding + +cdef extern from "alloca.h": + void *alloca(Py_ssize_t size) + + NoneType = type(None) # internal structure: you are not supposed to know this. But it's worth some @@ -422,7 +427,7 @@ cdef class Transformer: self.formats = pqformats return out - def load_rows(self, int row0, int row1, object make_row) -> list[Row]: + def load_rows_(self, int row0, int row1, object make_row) -> list[Row]: if self._pgresult is None: raise e.InterfaceError("result not set") @@ -440,11 +445,89 @@ cdef class Transformer: cdef PGresAttValue *attval cdef object record # not 'tuple' as it would check on assignment - cdef object records = PyList_New(row1 - row0) + cdef int rowcount = row1 - row0 + cdef object records = PyList_New(rowcount) cdef PyObject *loader # borrowed RowLoader + cdef PyObject *brecord # borrowed + cdef int advance row_loaders = self._row_loaders # avoid an incref/decref per item + while row0 < row1: + # TODO: make advance configurable + advance = 100 if row1 - row0 > 100 else row1 - row0 + for row in range(advance): + record = PyTuple_New(self._nfields) + Py_INCREF(record) + PyList_SET_ITEM(records, row, record) + for col in range(self._nfields): + loader = PyList_GET_ITEM(row_loaders, col) + if (loader).cloader is not None: + for row in range(row0, row0+advance): + brecord = PyList_GET_ITEM(records, row - row0) + attval = &(ires.tuples[row][col]) + if attval.len == -1: # NULL_LEN + pyval = None + else: + pyval = (loader).cloader.cload( + attval.value, attval.len) + + Py_INCREF(pyval) + PyTuple_SET_ITEM(brecord, col, pyval) + + else: + for row in range(row0, row0+advance): + brecord = PyList_GET_ITEM(records, row - row0) + attval = &(ires.tuples[row][col]) + if attval.len == -1: # NULL_LEN + pyval = None + else: + b = PyMemoryView_FromObject( + ViewBuffer._from_buffer( + self._pgresult, + attval.value, attval.len)) + pyval = PyObject_CallFunctionObjArgs( + (loader).loadfunc, b, NULL) + + Py_INCREF(pyval) + PyTuple_SET_ITEM(brecord, col, pyval) + row0 += advance + + if make_row is not tuple: + for i in range(rowcount): + brecord = PyList_GET_ITEM(records, i) + record = PyObject_CallFunctionObjArgs( + make_row, brecord, NULL) + Py_INCREF(record) + PyList_SET_ITEM(records, i, record) + Py_DECREF(brecord) + return records + + def load_rows(self, int row0, int row1, object make_row) -> list[Row]: + if self._pgresult is None: + raise e.InterfaceError("result not set") + + if not (0 <= row0 <= self._ntuples and 0 <= row1 <= self._ntuples): + raise e.InterfaceError( + f"rows must be included between 0 and {self._ntuples}" + ) + + cdef libpq.PGresult *res = self._pgresult._pgresult_ptr + # cheeky access to the internal PGresult structure + cdef pg_result_int *ires = res + + cdef int row + cdef int col + cdef PGresAttValue *attval + cdef object record # not 'tuple' as it would check on assignment + + cdef object records = PyList_New(row1 - row0) + + # hacky but fastest access to loaders + cdef PyObject **loaders = alloca(self._nfields * sizeof(PyObject *)) + for col in range(self._nfields): + loaders[col] = PyList_GET_ITEM(self._row_loaders, col) + for row in range(row0, row1): record = PyTuple_New(self._nfields) for col in range(self._nfields): @@ -452,9 +535,8 @@ cdef class Transformer: if attval.len == -1: # NULL_LEN pyval = None else: - loader = PyList_GET_ITEM(row_loaders, col) - if (loader).cloader is not None: - pyval = (loader).cloader.cload( + if (loaders[col]).cloader is not None: + pyval = (loaders[col]).cloader.cload( attval.value, attval.len) else: b = PyMemoryView_FromObject( @@ -464,7 +546,7 @@ cdef class Transformer: attval.len )) pyval = PyObject_CallFunctionObjArgs( - (loader).loadfunc, b, NULL) + (loaders[col]).loadfunc, b, NULL) Py_INCREF(pyval) PyTuple_SET_ITEM(record, col, pyval) if make_row is not tuple: