diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2020-05-26 15:17:33 -0500 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2020-05-31 12:32:31 -0500 |
commit | ebb4e48f83bbbc6cea3c5781a838b032e120663d (patch) | |
tree | 66f2e3e4a2a60db2522744402e79e8bb4bb08c6e /numpy/core | |
parent | 426b3cac7932289f24be7dc6dba6197a4d65740b (diff) | |
download | numpy-ebb4e48f83bbbc6cea3c5781a838b032e120663d.tar.gz |
ENH: Hardcode buffer handling for simple scalars
For most scalars (except void ones) exposing the buffer interface
is trivial and does not require any fancy "format" handling.
Except for unicode, their buffer interface is also static and thus
can simply be hardcoded.
The main advantage, is that currently the buffer interface uses
a global dictionary to store these buffers and thus requires
probing that dictionary for every single scalar dealloc. This
results in an overhead of about 30% for simple operations (such
as slicing and simple math).
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/include/numpy/arrayscalars.h | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/buffer.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/conversion_utils.c | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/descriptor.c | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/getset.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/npy_buffer.h | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 174 | ||||
-rw-r--r-- | numpy/core/tests/test_scalarinherit.py | 16 |
11 files changed, 182 insertions, 30 deletions
diff --git a/numpy/core/include/numpy/arrayscalars.h b/numpy/core/include/numpy/arrayscalars.h index 42a0df76a..6dce88df3 100644 --- a/numpy/core/include/numpy/arrayscalars.h +++ b/numpy/core/include/numpy/arrayscalars.h @@ -140,6 +140,7 @@ typedef struct { /* note that the PyObject_HEAD macro lives right here */ PyUnicodeObject base; Py_UCS4 *obval; + char *buffer_fmt; } PyUnicodeScalarObject; diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 38d5f21eb..0cde3a78a 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -924,7 +924,6 @@ VOID_setitem(PyObject *op, void *input, void *vap) memset(ip + view.len, 0, itemsize - view.len); } PyBuffer_Release(&view); - _dealloc_cached_buffer_info(op); } return 0; } diff --git a/numpy/core/src/multiarray/buffer.c b/numpy/core/src/multiarray/buffer.c index 9a1f7b230..232176011 100644 --- a/numpy/core/src/multiarray/buffer.c +++ b/numpy/core/src/multiarray/buffer.c @@ -802,7 +802,7 @@ fail: * Retrieving buffers for scalars */ int -gentype_getbuffer(PyObject *self, Py_buffer *view, int flags) +void_getbuffer(PyObject *self, Py_buffer *view, int flags) { _buffer_info_t *info = NULL; PyArray_Descr *descr = NULL; diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 0150ae10e..55ae73779 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -299,7 +299,6 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims, PyErr_Clear(); dtype = _descriptor_from_pep3118_format(buffer_view.format); PyBuffer_Release(&buffer_view); - _dealloc_cached_buffer_info(obj); if (dtype) { goto promote_types; } @@ -311,7 +310,6 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims, dtype = PyArray_DescrNewFromType(NPY_VOID); dtype->elsize = buffer_view.itemsize; PyBuffer_Release(&buffer_view); - _dealloc_cached_buffer_info(obj); goto promote_types; } else { @@ -626,14 +624,6 @@ _IsWriteable(PyArrayObject *ap) return NPY_FALSE; } PyBuffer_Release(&view); - /* - * The first call to PyObject_GetBuffer stores a reference to a struct - * _buffer_info_t (from buffer.c, with format, ndim, strides and shape) in - * a static dictionary, with id(base) as the key. Usually we release it - * after the call to PyBuffer_Release, via a call to - * _dealloc_cached_buffer_info, but in this case leave it in the cache to - * speed up future calls to _IsWriteable. - */ return NPY_TRUE; } diff --git a/numpy/core/src/multiarray/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c index 14d546867..33f909e3f 100644 --- a/numpy/core/src/multiarray/conversion_utils.c +++ b/numpy/core/src/multiarray/conversion_utils.c @@ -195,7 +195,6 @@ PyArray_BufferConverter(PyObject *obj, PyArray_Chunk *buf) * sticks around after the release. */ PyBuffer_Release(&view); - _dealloc_cached_buffer_info(obj); /* Point to the base of the buffer object if present */ if (PyMemoryView_Check(obj)) { diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index 14e64b647..3c3bcb387 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -2576,7 +2576,6 @@ PyArray_FromInterface(PyObject *origin) * sticks around after the release. */ PyBuffer_Release(&view); - _dealloc_cached_buffer_info(base); /* Get offset number from interface specification */ attr = _PyDict_GetItemStringWithError(iface, "offset"); @@ -3801,7 +3800,6 @@ PyArray_FromBuffer(PyObject *buf, PyArray_Descr *type, * sticks around after the release. */ PyBuffer_Release(&view); - _dealloc_cached_buffer_info(buf); if ((offset < 0) || (offset > ts)) { PyErr_Format(PyExc_ValueError, diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index b26a26abf..c2b90141a 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -1808,7 +1808,6 @@ arraydescr_dealloc(PyArray_Descr *self) Py_INCREF(self); return; } - _dealloc_cached_buffer_info((PyObject*)self); Py_XDECREF(self->typeobj); Py_XDECREF(self->names); Py_XDECREF(self->fields); diff --git a/numpy/core/src/multiarray/getset.c b/numpy/core/src/multiarray/getset.c index 80a1cd4a1..5405a25db 100644 --- a/numpy/core/src/multiarray/getset.c +++ b/numpy/core/src/multiarray/getset.c @@ -147,7 +147,6 @@ array_strides_set(PyArrayObject *self, PyObject *obj) offset = PyArray_BYTES(self) - (char *)view.buf; numbytes = view.len + offset; PyBuffer_Release(&view); - _dealloc_cached_buffer_info((PyObject*)new); } else { PyErr_Clear(); @@ -376,7 +375,6 @@ array_data_set(PyArrayObject *self, PyObject *op) * sticks around after the release. */ PyBuffer_Release(&view); - _dealloc_cached_buffer_info(op); if (!PyArray_ISONESEGMENT(self)) { PyErr_SetString(PyExc_AttributeError, diff --git a/numpy/core/src/multiarray/npy_buffer.h b/numpy/core/src/multiarray/npy_buffer.h index 2eb97c4b9..5ff8b6c2c 100644 --- a/numpy/core/src/multiarray/npy_buffer.h +++ b/numpy/core/src/multiarray/npy_buffer.h @@ -10,6 +10,6 @@ NPY_NO_EXPORT PyArray_Descr* _descriptor_from_pep3118_format(char const *s); NPY_NO_EXPORT int -gentype_getbuffer(PyObject *obj, Py_buffer *view, int flags); +void_getbuffer(PyObject *obj, Py_buffer *view, int flags); #endif diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index f13f50759..ae33bbedf 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -85,7 +85,6 @@ gentype_alloc(PyTypeObject *type, Py_ssize_t nitems) static void gentype_dealloc(PyObject *v) { - _dealloc_cached_buffer_info(v); Py_TYPE(v)->tp_free(v); } @@ -1691,7 +1690,6 @@ gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args)) * sticks around after the release. */ PyBuffer_Release(&view); - _dealloc_cached_buffer_info(self); } else { Py_DECREF(ret); @@ -2365,9 +2363,167 @@ static PySequenceMethods voidtype_as_sequence = { }; -static PyBufferProcs gentype_as_buffer = { - .bf_getbuffer = gentype_getbuffer, - /* release buffer not defined (see buffer.c) */ + +/**begin repeat + * #name = bool, byte, short, int, long, longlong, ubyte, ushort, uint, ulong, + * ulonglong, half, float, double, longdouble, cfloat, cdouble, + * clongdouble# + * #Name = Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong, + * ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble, + * CLongDouble# + * #NAME = BOOL, BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG, + * ULONGLONG, HALF, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, + * CLONGDOUBLE# + * #fmt = ?, b, h, i, l, q, B, H, I, L, Q, e, f, d, g, Zf, Zd, Zg# + */ + +static int +@name@_getbuffer(PyObject *self, Py_buffer *view, int flags) +{ + if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) { + return -1; + } + Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self; + + static char fmt[3] = "@fmt@"; + + view->ndim = 0; + view->len = sizeof(scalar->obval); + view->itemsize = sizeof(scalar->obval); + view->shape = NULL; + view->strides = NULL; + view->suboffsets = NULL; + Py_INCREF(self); + view->obj = self; + view->buf = &(scalar->obval); + + if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) { + /* It is unnecessary to find the correct format */ + view->format = NULL; + return 0; + } + + view->format = fmt; + + return 0; +} + +static PyBufferProcs @name@_arrtype_as_buffer = { + .bf_getbuffer = @name@_getbuffer, + /* No need to release the buffer */ +}; + +/**end repeat**/ + +static int +unicode_getbuffer(PyObject *self, Py_buffer *view, int flags) +{ + if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) { + return -1; + } + PyUnicodeScalarObject *scalar = (PyUnicodeScalarObject *)self; + Py_ssize_t length = PyUnicode_GetLength(self); + + view->ndim = 0; + view->len = length * 4; + view->itemsize = length * 4; + view->shape = NULL; + view->strides = NULL; + view->suboffsets = NULL; + Py_INCREF(self); + view->obj = self; + + if (scalar->obval == NULL) { + /* + * Unicode may not have the representation available, `scalar_value` + * ensures materialization. + */ + PyArray_Descr *descr = PyArray_DescrFromType(NPY_UNICODE); + scalar_value(self, descr); + Py_DECREF(descr); + if (scalar->obval == NULL) { + /* allocating memory failed */ + Py_SETREF(view->obj, NULL); + return -1; + } + } + view->buf = scalar->obval; + + if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) { + /* It is unnecessary to find the correct format */ + view->format = NULL; + return 0; + } + + if (scalar->buffer_fmt != NULL) { + view->format = scalar->buffer_fmt; + } + else { + scalar->buffer_fmt = PyObject_Malloc(22); + if (scalar->buffer_fmt == NULL) { + Py_SETREF(view->obj, NULL); + return -1; + } + PyOS_snprintf(scalar->buffer_fmt, 22, "%" NPY_INTP_FMT "w", length); + view->format = scalar->buffer_fmt; + } + + return 0; +} + +static PyBufferProcs unicode_arrtype_as_buffer = { + .bf_getbuffer = unicode_getbuffer, + /* No need to release the buffer */ +}; + + +/**begin repeat + * #name = datetime, timedelta# + * #Name = Datetime, Timedelta# + */ + +static int +@name@_getbuffer(PyObject *self, Py_buffer *view, int flags) +{ + if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) { + return -1; + } + Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self; + + view->ndim = 1; + view->len = 8; + view->itemsize = 1; + static Py_ssize_t length = 8; + view->shape = &length; + view->strides = NULL; + view->suboffsets = NULL; + Py_INCREF(self); + view->obj = self; + + view->buf = &(scalar->obval); + + if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) { + /* It is unnecessary to find the correct format */ + view->format = NULL; + return 0; + } + + /* export datetime scalars as bytes (although arrays are not exported) */ + view->format = "B"; + + return 0; +} + +static PyBufferProcs @name@_arrtype_as_buffer = { + .bf_getbuffer = @name@_getbuffer, + /* No need to release the buffer */ +}; + +/**end repeat**/ + +static PyBufferProcs void_arrtype_as_buffer = { + .bf_getbuffer = void_getbuffer, /* defined in buffer.c */ + /* No need to release the buffer */ }; @@ -3584,7 +3740,6 @@ initialize_numeric_types(void) init_basetypes(); PyGenericArrType_Type.tp_dealloc = (destructor)gentype_dealloc; PyGenericArrType_Type.tp_as_number = &gentype_as_number; - PyGenericArrType_Type.tp_as_buffer = &gentype_as_buffer; PyGenericArrType_Type.tp_as_mapping = &gentype_as_mapping; PyGenericArrType_Type.tp_flags = BASEFLAGS; PyGenericArrType_Type.tp_methods = gentype_methods; @@ -3668,10 +3823,15 @@ initialize_numeric_types(void) Py@NAME@ArrType_Type.tp_new = @name@_arrtype_new; Py@NAME@ArrType_Type.tp_richcompare = gentype_richcompare; +#define _IS_@NAME@ /* inherit string buffer */ +#if !defined(_IS_String) + Py@NAME@ArrType_Type.tp_as_buffer = &@name@_arrtype_as_buffer; +#endif +#undef _IS_@NAME@ + /**end repeat**/ PyUnicodeArrType_Type.tp_dealloc = unicode_arrtype_dealloc; - PyUnicodeArrType_Type.tp_as_buffer = &gentype_as_buffer; /**begin repeat * #name = bool, byte, short, ubyte, ushort, uint, ulong, ulonglong, diff --git a/numpy/core/tests/test_scalarinherit.py b/numpy/core/tests/test_scalarinherit.py index 74829986c..cc53eb244 100644 --- a/numpy/core/tests/test_scalarinherit.py +++ b/numpy/core/tests/test_scalarinherit.py @@ -5,7 +5,7 @@ import pytest import numpy as np -from numpy.testing import assert_ +from numpy.testing import assert_, assert_raises class A: @@ -74,13 +74,21 @@ class TestCharacter: assert_(s + np_s == b'defabc') assert_(u + np_u == u'defabc') + class MyStr(str, np.generic): + # would segfault + pass + + with assert_raises(TypeError): + # Previously worked, but gave completely wrong result + ret = s + MyStr('abc') - class Mystr(str, np.generic): + class MyBytes(bytes, np.generic): # would segfault pass - ret = s + Mystr('abc') - assert_(type(ret) is type(s)) + ret = s + MyBytes(b'abc') + assert(type(ret) is type(s)) + assert ret == b"defabc" def test_char_repeat(self): np_s = np.string_('abc') |