On Mon, Jun 06, 2022 at 09:08:33PM -0500, Eric Blake wrote:
Instead of storing a PyCapsule in _o and repeatedly doing lookups to
dereference a stored malloc'd pointer, it is easier to just directly
store a Python buffer-like object as _o. Track initialization via
._init: absent for nbd.Buffer(int), present for
nbd.Buffer.from_bytearray or after we have forced initialization due
to aio_pread or .to_bytearray. At this point, we are still copying in
and out of .{from,to}_bytearray, but we are getting closer to reducing
the number of copies. The sheer size in reduced lines of code is a
testament to the ease of doing things closer to Python, rather than
hidden behind unpacking a PyCapsule layer.
Acceptable side effect: nbd.Buffer(-1) changes from:
RuntimeError: length < 0
to
SystemError: Negative size passed to PyByteArray_FromStringAndSize
The generated code changes as follows:
| --- python/methods.h.bak 2022-06-06 20:36:01.973327739 -0500
| +++ python/methods.h 2022-06-06 20:36:16.146352508 -0500
| @@ -62,9 +62,6 @@
| extern PyObject *nbd_internal_py_close (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_display_version (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_alloc_aio_buffer (PyObject *self, PyObject *args);
| -extern PyObject *nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject
*args);
| -extern PyObject *nbd_internal_py_aio_buffer_to_bytearray (PyObject *self, PyObject
*args);
| -extern PyObject *nbd_internal_py_aio_buffer_size (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_aio_buffer_is_zero (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_set_debug (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_get_debug (PyObject *self, PyObject *args);
| --- python/nbd.py.bak 2022-06-06 20:36:01.978327748 -0500
| +++ python/nbd.py 2022-06-06 20:56:41.866729560 -0500
| @@ -134,18 +134,21 @@ class Buffer(object):
| bytearray constructor. Otherwise, ba is copied. Either way, the
| resulting AIO buffer is independent from the original.
| '''
| - o = libnbdmod.aio_buffer_from_bytearray(ba)
| self = cls(0)
| - self._o = o
| + self._o = bytearray(ba)
| + self._init = True
| return self
|
| def to_bytearray(self):
| '''Copy an AIO buffer into a bytearray.'''
| - return libnbdmod.aio_buffer_to_bytearray(self)
| + if not hasattr(self, '_init'):
| + self._o = bytearray(len(self._o))
| + self._init = True
| + return bytearray(self._o)
|
| def size(self):
| '''Return the size of an AIO buffer.'''
| - return libnbdmod.aio_buffer_size(self)
| + return len(self._o)
|
| def is_zero(self, offset=0, size=-1):
| '''Returns true if and only if all bytes in the buffer are
zeroes.
| @@ -161,7 +164,8 @@ class Buffer(object):
| always returns true. If size > 0, we check the interval
| [offset..offset+size-1].
| '''
| - return libnbdmod.aio_buffer_is_zero(self, offset, size)
| + return libnbdmod.aio_buffer_is_zero(self._o, offset, size,
| + hasattr(self, '_init'))
|
|
| class NBD(object):
---
generator/Python.ml | 20 ++--
python/handle.c | 242 +++++++++-----------------------------------
2 files changed, 56 insertions(+), 206 deletions(-)
diff --git a/generator/Python.ml b/generator/Python.ml
index c49af4f..86781ac 100644
--- a/generator/Python.ml
+++ b/generator/Python.ml
@@ -73,9 +73,6 @@ let
) ([ "create"; "close";
"display_version";
"alloc_aio_buffer";
- "aio_buffer_from_bytearray";
- "aio_buffer_to_bytearray";
- "aio_buffer_size";
"aio_buffer_is_zero"] @ List.map fst handle_calls);
pr "\n";
@@ -105,9 +102,6 @@ let
) ([ "create"; "close";
"display_version";
"alloc_aio_buffer";
- "aio_buffer_from_bytearray";
- "aio_buffer_to_bytearray";
- "aio_buffer_size";
"aio_buffer_is_zero"] @ List.map fst handle_calls);
pr " { NULL, NULL, 0, NULL }\n";
pr "};\n";
@@ -748,18 +742,21 @@ let
bytearray constructor. Otherwise, ba is copied. Either way, the
resulting AIO buffer is independent from the original.
'''
- o = libnbdmod.aio_buffer_from_bytearray(ba)
self = cls(0)
- self._o = o
+ self._o = bytearray(ba)
+ self._init = True
return self
def to_bytearray(self):
'''Copy an AIO buffer into a bytearray.'''
- return libnbdmod.aio_buffer_to_bytearray(self)
+ if not hasattr(self, '_init'):
+ self._o = bytearray(len(self._o))
+ self._init = True
+ return bytearray(self._o)
def size(self):
'''Return the size of an AIO buffer.'''
- return libnbdmod.aio_buffer_size(self)
+ return len(self._o)
def is_zero(self, offset=0, size=-1):
'''Returns true if and only if all bytes in the buffer are zeroes.
@@ -775,7 +772,8 @@ let
always returns true. If size > 0, we check the interval
[offset..offset+size-1].
'''
- return libnbdmod.aio_buffer_is_zero(self, offset, size)
+ return libnbdmod.aio_buffer_is_zero(self._o, offset, size,
+ hasattr(self, '_init'))
class NBD(object):
diff --git a/python/handle.c b/python/handle.c
index 79b369d..ca6c8e9 100644
--- a/python/handle.c
+++ b/python/handle.c
@@ -40,12 +40,6 @@
#include "methods.h"
-struct py_aio_buffer {
- Py_ssize_t len;
- void *data;
- bool initialized;
-};
-
static inline PyObject *
put_handle (struct nbd_handle *h)
{
@@ -102,242 +96,100 @@ nbd_internal_py_display_version (PyObject *self, PyObject *args)
return Py_None;
}
-static const char aio_buffer_name[] = "nbd.Buffer";
-
-/* Return internal buffer pointer of nbd.Buffer */
-static struct py_aio_buffer *
-nbd_internal_py_get_aio_buffer (PyObject *object)
+/* Return new reference to MemoryView wrapping aio_buffer contents */
+PyObject *
+nbd_internal_py_get_aio_view (PyObject *object, bool require_init)
{
+ PyObject *buffer = NULL;
+
if (PyObject_IsInstance (object, nbd_internal_py_get_nbd_buffer_type ())) {
- PyObject *capsule = PyObject_GetAttrString(object, "_o");
+ buffer = PyObject_GetAttrString (object, "_o");
- return PyCapsule_GetPointer (capsule, aio_buffer_name);
+ if (require_init && ! PyObject_HasAttrString (object, "_init")) {
+ assert (PyByteArray_Check (buffer));
+ memset (PyByteArray_AS_STRING (buffer), 0,
+ PyByteArray_GET_SIZE (buffer));
+ if (PyObject_SetAttrString (object, "_init", Py_True) < 0)
+ return NULL;
+ }
}
+ if (buffer)
+ return PyMemoryView_FromObject (buffer);
+
PyErr_SetString (PyExc_TypeError,
"aio_buffer: expecting nbd.Buffer instance");
return NULL;
}
-/* Return new reference to MemoryView wrapping aio_buffer contents */
-PyObject *
-nbd_internal_py_get_aio_view (PyObject *object, bool require_init)
-{
- struct py_aio_buffer *buf = nbd_internal_py_get_aio_buffer (object);
-
- if (!buf)
- return NULL;
-
- if (require_init && !buf->initialized) {
- memset (buf->data, 0, buf->len);
- buf->initialized = true;
- }
-
- return PyMemoryView_FromMemory (buf->data, buf->len,
- require_init ? PyBUF_READ : PyBUF_WRITE);
-}
-
int
nbd_internal_py_init_aio_buffer (PyObject *object)
{
- struct py_aio_buffer *buf = nbd_internal_py_get_aio_buffer (object);
-
- assert (buf);
- buf->initialized = true;
+ if (PyObject_IsInstance (object, nbd_internal_py_get_nbd_buffer_type ()))
+ return PyObject_SetAttrString (object, "_init", Py_True);
return 0;
}
-static void
-free_aio_buffer (PyObject *capsule)
-{
- struct py_aio_buffer *buf = PyCapsule_GetPointer (capsule, aio_buffer_name);
-
- if (buf)
- free (buf->data);
- free (buf);
-}
-
-/* Allocate a persistent buffer used for nbd_aio_pread. */
+/* Allocate an uninitialized persistent buffer used for nbd_aio_pread. */
PyObject *
nbd_internal_py_alloc_aio_buffer (PyObject *self, PyObject *args)
{
- struct py_aio_buffer *buf;
- PyObject *ret;
+ Py_ssize_t len;
- buf = malloc (sizeof *buf);
- if (buf == NULL) {
- PyErr_NoMemory ();
- return NULL;
- }
-
- buf->initialized = false;
if (!PyArg_ParseTuple (args, "n:nbd_internal_py_alloc_aio_buffer",
- &buf->len)) {
- free (buf);
+ &len))
return NULL;
- }
- if (buf->len < 0) {
- PyErr_SetString (PyExc_RuntimeError, "length < 0");
- free (buf);
- return NULL;
- }
- buf->data = malloc (buf->len);
- if (buf->data == NULL) {
- PyErr_NoMemory ();
- free (buf);
- return NULL;
- }
-
- ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer);
- if (ret == NULL) {
- free (buf->data);
- free (buf);
- return NULL;
- }
-
- return ret;
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- PyObject *arr = NULL;
- Py_ssize_t len;
- void *data;
- struct py_aio_buffer *buf;
- PyObject *ret;
-
- if (!PyArg_ParseTuple (args,
- "O:nbd_internal_py_aio_buffer_from_bytearray",
- &obj))
- return NULL;
-
- if (! PyByteArray_Check (obj)) {
- arr = PyByteArray_FromObject (obj);
- if (arr == NULL)
- return NULL;
- obj = arr;
- }
- data = PyByteArray_AsString (obj);
- if (!data) {
- PyErr_SetString (PyExc_RuntimeError,
- "parameter is not a bytearray or buffer");
- Py_XDECREF (arr);
- return NULL;
- }
- len = PyByteArray_Size (obj);
-
- buf = malloc (sizeof *buf);
- if (buf == NULL) {
- PyErr_NoMemory ();
- Py_XDECREF (arr);
- return NULL;
- }
-
- buf->len = len;
- buf->data = malloc (len);
- if (buf->data == NULL) {
- PyErr_NoMemory ();
- free (buf);
- Py_XDECREF (arr);
- return NULL;
- }
- memcpy (buf->data, data, len);
- Py_XDECREF (arr);
- buf->initialized = true;
-
- ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer);
- if (ret == NULL) {
- free (buf->data);
- free (buf);
- return NULL;
- }
-
- return ret;
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_to_bytearray (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- PyObject *view;
- PyObject *ret;
-
- if (!PyArg_ParseTuple (args,
- "O:nbd_internal_py_aio_buffer_to_bytearray",
- &obj))
- return NULL;
-
- view = nbd_internal_py_get_aio_view (obj, true);
- if (view == NULL)
- return NULL;
-
- ret = PyByteArray_FromObject (view);
- Py_DECREF (view);
- return ret;
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_size (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- struct py_aio_buffer *buf;
-
- if (!PyArg_ParseTuple (args,
- "O:nbd_internal_py_aio_buffer_size",
- &obj))
- return NULL;
-
- buf = nbd_internal_py_get_aio_buffer (obj);
- if (buf == NULL)
- return NULL;
-
- return PyLong_FromSsize_t (buf->len);
+ /* Constructing bytearray(len) in python zeroes the memory; doing it this
+ * way gives uninitialized memory. This correctly flags negative len.
+ */
+ return PyByteArray_FromStringAndSize (NULL, len);
}
PyObject *
nbd_internal_py_aio_buffer_is_zero (PyObject *self, PyObject *args)
{
- PyObject *obj;
- struct py_aio_buffer *buf;
+ Py_buffer buf;
Py_ssize_t offset, size;
+ int init;
+ PyObject *ret = NULL;
if (!PyArg_ParseTuple (args,
- "Onn:nbd_internal_py_aio_buffer_is_zero",
- &obj, &offset, &size))
+ "y*nnp:nbd_internal_py_aio_buffer_is_zero",
+ &buf, &offset, &size, &init))
return NULL;
- if (size == 0)
- Py_RETURN_TRUE;
-
- buf = nbd_internal_py_get_aio_buffer (obj);
- if (buf == NULL)
- return NULL;
+ if (size == 0) {
+ ret = Py_True;
+ goto out;
+ }
/* Check the bounds of the offset. */
- if (offset < 0 || offset > buf->len) {
+ if (offset < 0 || offset > buf.len) {
PyErr_SetString (PyExc_IndexError, "offset out of range");
- return NULL;
+ goto out;
}
/* Compute or check the length. */
if (size == -1)
- size = buf->len - offset;
+ size = buf.len - offset;
else if (size < 0) {
PyErr_SetString (PyExc_IndexError,
"size cannot be negative, "
"except -1 to mean to the end of the buffer");
- return NULL;
+ goto out;
}
- else if ((size_t) offset + size > buf->len) {
+ else if ((size_t) offset + size > buf.len) {
PyErr_SetString (PyExc_IndexError, "size out of range");
- return NULL;
+ goto out;
}
- if (!buf->initialized || is_zero (buf->data + offset, size))
- Py_RETURN_TRUE;
+ if (!init || is_zero (buf.buf + offset, size))
+ ret = Py_True;
else
- Py_RETURN_FALSE;
+ ret = Py_False;
+ out:
+ PyBuffer_Release (&buf);
+ Py_XINCREF (ret);
+ return ret;
}
Looks like a nice simplification!
Reviewed-by: Richard W.M. Jones <rjones(a)redhat.com>
Rich.
--
Richard Jones, Virtualization Group, Red Hat
http://people.redhat.com/~rjones
Read my programming and virtualization blog:
http://rwmj.wordpress.com
virt-p2v converts physical machines to virtual machines. Boot with a
live CD or over the network (PXE) and turn machines into KVM guests.
http://libguestfs.org/virt-v2v