From 06ef25e8f4e88adea8053e843c40cd976cc05212 Mon Sep 17 00:00:00 2001 From: John Kirkham Date: Fri, 23 Nov 2018 18:22:42 -0500 Subject: [PATCH 1/2] Skip creating a `memoryview` from `obj` At this stage, there is no real value to creating a `memoryview` from our data to create an object vs. just using the buffer protocol to fill out our buffer. Under the hood, the `PyMemoryView_FromObject` call is doing exactly what we are doing and providing little more. Filling out the buffer directly avoids creating an intermediate object between our buffer and the data. Not to mention this should make it easier for us to get access to the underlying object used to create the buffer. Also should cutdown on the overhead of generating a `cybuffer` object. --- src/cybuffer.pyx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cybuffer.pyx b/src/cybuffer.pyx index 2315469..785cff4 100644 --- a/src/cybuffer.pyx +++ b/src/cybuffer.pyx @@ -35,8 +35,6 @@ include "version.pxi" cdef extern from "Python.h": size_t Py_UNICODE_SIZE - object PyMemoryView_FromObject(object obj) - cdef extern from *: """ @@ -145,8 +143,7 @@ cdef class cybuffer(object): else: raise TypeError("Unable to get buffer protocol API for `data`.") - # Create a buffer based on memoryview - data_buf = PyMemoryView_FromObject(data_buf) + # Fill out our buffer based on the data cpython.buffer.PyObject_GetBuffer(data_buf, &self._buf, PyBUF_FULL_RO) # Allocate and/or initialize metadata for casting @@ -155,7 +152,7 @@ cdef class cybuffer(object): self._shape = self._buf.shape self._strides = self._buf.strides - # Figure out whether the memoryview is contiguous + # Figure out whether the data is contiguous self.c_contiguous = cpython.buffer.PyBuffer_IsContiguous( &self._buf, b'C' ) From a9e42645bfa33da6a890a360b02f7772724d68ca Mon Sep 17 00:00:00 2001 From: John Kirkham Date: Fri, 23 Nov 2018 18:55:37 -0500 Subject: [PATCH 2/2] Check (new) buffer protocol support on Python 2 Skip checking to see if the object supports the (new) buffer protocol unless we are on Python 2. On Python 2/3, `PyObject_GetBuffer` already raises a `TypeError` with an informative message. This is also true of `PyBuffer_FromObject` on Python 2. So it is better to allow Python's `TypeError` messages to propagate up instead of adding our own. This keeps the code simple for our use case with the same end effect. Also makes the Python 3 case a little cleaner. Of course on Python 2 if the object doesn't support (new) buffer protocol, it's good to figure that upfront as the check is cheap compared to catching an exception. That way we can fallback to the buffer protocol simply. Instead of carrying around a new object for storing the result, just overwrite `data` as we already have a copy of it earlier and this won't affect anything outside of this function. Any exceptions that would be propagated on the Python 2 side are handled or not just as before. --- src/cybuffer.pyx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/cybuffer.pyx b/src/cybuffer.pyx index 785cff4..b669995 100644 --- a/src/cybuffer.pyx +++ b/src/cybuffer.pyx @@ -130,21 +130,17 @@ cdef class cybuffer(object): self.obj = data - cdef object data_buf - if cpython.buffer.PyObject_CheckBuffer(data): - data_buf = data - elif PY2K: + # Fallback to old buffer protocol on Python 2 if necessary + if PY2K and not cpython.buffer.PyObject_CheckBuffer(self.obj): try: - data_buf = cpython.oldbuffer.PyBuffer_FromReadWriteObject( + data = cpython.oldbuffer.PyBuffer_FromReadWriteObject( data, 0, -1 ) except TypeError: - data_buf = cpython.oldbuffer.PyBuffer_FromObject(data, 0, -1) - else: - raise TypeError("Unable to get buffer protocol API for `data`.") + data = cpython.oldbuffer.PyBuffer_FromObject(data, 0, -1) # Fill out our buffer based on the data - cpython.buffer.PyObject_GetBuffer(data_buf, &self._buf, PyBUF_FULL_RO) + cpython.buffer.PyObject_GetBuffer(data, &self._buf, PyBUF_FULL_RO) # Allocate and/or initialize metadata for casting self._format = self._buf.format @@ -164,9 +160,9 @@ cdef class cybuffer(object): # Workaround some special cases with the builtin array cdef size_t len_nd_b cdef int n_1 - if isinstance(data, array): + if isinstance(self.obj, array): # Fix-up typecode - typecode = data.typecode + typecode = self.obj.typecode if typecode == "B": return elif PY2K and typecode == "c": @@ -182,7 +178,7 @@ cdef class cybuffer(object): # Adjust itemsize, shape, and strides based on casting if PY2K: - self.itemsize = data.itemsize + self.itemsize = self.obj.itemsize len_nd_b = self._buf.ndim * sizeof(Py_ssize_t) self._shape = cpython.mem.PyMem_Malloc(len_nd_b)