Skip to content

Commit 924cbe4

Browse files
authored
Merge pull request numpy#16389 from seberg/hardcode-scalar-buffers
ENH: Hardcode buffer handling for simple scalars
2 parents 489de42 + ebb4e48 commit 924cbe4

File tree

12 files changed

+215
-30
lines changed

12 files changed

+215
-30
lines changed

benchmarks/benchmarks/bench_scalar.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from .common import Benchmark, TYPES1
2+
3+
import numpy as np
4+
5+
6+
class ScalarMath(Benchmark):
7+
# Test scalar math, note that each of these is run repeatedly to offset
8+
# the function call overhead to some degree.
9+
params = [TYPES1]
10+
param_names = ["type"]
11+
def setup(self, typename):
12+
self.num = np.dtype(typename).type(2)
13+
14+
def time_addition(self, typename):
15+
n = self.num
16+
res = n + n + n + n + n + n + n + n + n + n
17+
18+
def time_addition_pyint(self, typename):
19+
n = self.num
20+
res = n + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
21+
22+
def time_multiplication(self, typename):
23+
n = self.num
24+
res = n * n * n * n * n * n * n * n * n * n
25+
26+
def time_power_of_two(self, typename):
27+
n = self.num
28+
res = n**2, n**2, n**2, n**2, n**2, n**2, n**2, n**2, n**2, n**2
29+
30+
def time_abs(self, typename):
31+
n = self.num
32+
res = abs(abs(abs(abs(abs(abs(abs(abs(abs(abs(n))))))))))
33+

numpy/core/include/numpy/arrayscalars.h

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ typedef struct {
140140
/* note that the PyObject_HEAD macro lives right here */
141141
PyUnicodeObject base;
142142
Py_UCS4 *obval;
143+
char *buffer_fmt;
143144
} PyUnicodeScalarObject;
144145

145146

numpy/core/src/multiarray/arraytypes.c.src

-1
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,6 @@ VOID_setitem(PyObject *op, void *input, void *vap)
928928
memset(ip + view.len, 0, itemsize - view.len);
929929
}
930930
PyBuffer_Release(&view);
931-
_dealloc_cached_buffer_info(op);
932931
}
933932
return 0;
934933
}

numpy/core/src/multiarray/buffer.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ array_getbuffer(PyObject *obj, Py_buffer *view, int flags)
802802
* Retrieving buffers for scalars
803803
*/
804804
int
805-
gentype_getbuffer(PyObject *self, Py_buffer *view, int flags)
805+
void_getbuffer(PyObject *self, Py_buffer *view, int flags)
806806
{
807807
_buffer_info_t *info = NULL;
808808
PyArray_Descr *descr = NULL;

numpy/core/src/multiarray/common.c

-10
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims,
299299
PyErr_Clear();
300300
dtype = _descriptor_from_pep3118_format(buffer_view.format);
301301
PyBuffer_Release(&buffer_view);
302-
_dealloc_cached_buffer_info(obj);
303302
if (dtype) {
304303
goto promote_types;
305304
}
@@ -311,7 +310,6 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims,
311310
dtype = PyArray_DescrNewFromType(NPY_VOID);
312311
dtype->elsize = buffer_view.itemsize;
313312
PyBuffer_Release(&buffer_view);
314-
_dealloc_cached_buffer_info(obj);
315313
goto promote_types;
316314
}
317315
else {
@@ -626,14 +624,6 @@ _IsWriteable(PyArrayObject *ap)
626624
return NPY_FALSE;
627625
}
628626
PyBuffer_Release(&view);
629-
/*
630-
* The first call to PyObject_GetBuffer stores a reference to a struct
631-
* _buffer_info_t (from buffer.c, with format, ndim, strides and shape) in
632-
* a static dictionary, with id(base) as the key. Usually we release it
633-
* after the call to PyBuffer_Release, via a call to
634-
* _dealloc_cached_buffer_info, but in this case leave it in the cache to
635-
* speed up future calls to _IsWriteable.
636-
*/
637627
return NPY_TRUE;
638628
}
639629

numpy/core/src/multiarray/conversion_utils.c

-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ PyArray_BufferConverter(PyObject *obj, PyArray_Chunk *buf)
207207
* sticks around after the release.
208208
*/
209209
PyBuffer_Release(&view);
210-
_dealloc_cached_buffer_info(obj);
211210

212211
/* Point to the base of the buffer object if present */
213212
if (PyMemoryView_Check(obj)) {

numpy/core/src/multiarray/ctors.c

-2
Original file line numberDiff line numberDiff line change
@@ -2576,7 +2576,6 @@ PyArray_FromInterface(PyObject *origin)
25762576
* sticks around after the release.
25772577
*/
25782578
PyBuffer_Release(&view);
2579-
_dealloc_cached_buffer_info(base);
25802579

25812580
/* Get offset number from interface specification */
25822581
attr = _PyDict_GetItemStringWithError(iface, "offset");
@@ -3801,7 +3800,6 @@ PyArray_FromBuffer(PyObject *buf, PyArray_Descr *type,
38013800
* sticks around after the release.
38023801
*/
38033802
PyBuffer_Release(&view);
3804-
_dealloc_cached_buffer_info(buf);
38053803

38063804
if ((offset < 0) || (offset > ts)) {
38073805
PyErr_Format(PyExc_ValueError,

numpy/core/src/multiarray/descriptor.c

-1
Original file line numberDiff line numberDiff line change
@@ -1808,7 +1808,6 @@ arraydescr_dealloc(PyArray_Descr *self)
18081808
Py_INCREF(self);
18091809
return;
18101810
}
1811-
_dealloc_cached_buffer_info((PyObject*)self);
18121811
Py_XDECREF(self->typeobj);
18131812
Py_XDECREF(self->names);
18141813
Py_XDECREF(self->fields);

numpy/core/src/multiarray/getset.c

-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ array_strides_set(PyArrayObject *self, PyObject *obj)
147147
offset = PyArray_BYTES(self) - (char *)view.buf;
148148
numbytes = view.len + offset;
149149
PyBuffer_Release(&view);
150-
_dealloc_cached_buffer_info((PyObject*)new);
151150
}
152151
else {
153152
PyErr_Clear();
@@ -376,7 +375,6 @@ array_data_set(PyArrayObject *self, PyObject *op)
376375
* sticks around after the release.
377376
*/
378377
PyBuffer_Release(&view);
379-
_dealloc_cached_buffer_info(op);
380378

381379
if (!PyArray_ISONESEGMENT(self)) {
382380
PyErr_SetString(PyExc_AttributeError,

numpy/core/src/multiarray/npy_buffer.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ NPY_NO_EXPORT PyArray_Descr*
1010
_descriptor_from_pep3118_format(char const *s);
1111

1212
NPY_NO_EXPORT int
13-
gentype_getbuffer(PyObject *obj, Py_buffer *view, int flags);
13+
void_getbuffer(PyObject *obj, Py_buffer *view, int flags);
1414

1515
#endif

numpy/core/src/multiarray/scalartypes.c.src

+167-7
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ gentype_alloc(PyTypeObject *type, Py_ssize_t nitems)
8585
static void
8686
gentype_dealloc(PyObject *v)
8787
{
88-
_dealloc_cached_buffer_info(v);
8988
Py_TYPE(v)->tp_free(v);
9089
}
9190

@@ -1691,7 +1690,6 @@ gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args))
16911690
* sticks around after the release.
16921691
*/
16931692
PyBuffer_Release(&view);
1694-
_dealloc_cached_buffer_info(self);
16951693
}
16961694
else {
16971695
Py_DECREF(ret);
@@ -2365,9 +2363,167 @@ static PySequenceMethods voidtype_as_sequence = {
23652363
};
23662364

23672365

2368-
static PyBufferProcs gentype_as_buffer = {
2369-
.bf_getbuffer = gentype_getbuffer,
2370-
/* release buffer not defined (see buffer.c) */
2366+
2367+
/**begin repeat
2368+
* #name = bool, byte, short, int, long, longlong, ubyte, ushort, uint, ulong,
2369+
* ulonglong, half, float, double, longdouble, cfloat, cdouble,
2370+
* clongdouble#
2371+
* #Name = Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong,
2372+
* ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble,
2373+
* CLongDouble#
2374+
* #NAME = BOOL, BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG,
2375+
* ULONGLONG, HALF, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE,
2376+
* CLONGDOUBLE#
2377+
* #fmt = ?, b, h, i, l, q, B, H, I, L, Q, e, f, d, g, Zf, Zd, Zg#
2378+
*/
2379+
2380+
static int
2381+
@name@_getbuffer(PyObject *self, Py_buffer *view, int flags)
2382+
{
2383+
if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
2384+
return -1;
2385+
}
2386+
Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self;
2387+
2388+
static char fmt[3] = "@fmt@";
2389+
2390+
view->ndim = 0;
2391+
view->len = sizeof(scalar->obval);
2392+
view->itemsize = sizeof(scalar->obval);
2393+
view->shape = NULL;
2394+
view->strides = NULL;
2395+
view->suboffsets = NULL;
2396+
Py_INCREF(self);
2397+
view->obj = self;
2398+
view->buf = &(scalar->obval);
2399+
2400+
if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
2401+
/* It is unnecessary to find the correct format */
2402+
view->format = NULL;
2403+
return 0;
2404+
}
2405+
2406+
view->format = fmt;
2407+
2408+
return 0;
2409+
}
2410+
2411+
static PyBufferProcs @name@_arrtype_as_buffer = {
2412+
.bf_getbuffer = @name@_getbuffer,
2413+
/* No need to release the buffer */
2414+
};
2415+
2416+
/**end repeat**/
2417+
2418+
static int
2419+
unicode_getbuffer(PyObject *self, Py_buffer *view, int flags)
2420+
{
2421+
if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
2422+
return -1;
2423+
}
2424+
PyUnicodeScalarObject *scalar = (PyUnicodeScalarObject *)self;
2425+
Py_ssize_t length = PyUnicode_GetLength(self);
2426+
2427+
view->ndim = 0;
2428+
view->len = length * 4;
2429+
view->itemsize = length * 4;
2430+
view->shape = NULL;
2431+
view->strides = NULL;
2432+
view->suboffsets = NULL;
2433+
Py_INCREF(self);
2434+
view->obj = self;
2435+
2436+
if (scalar->obval == NULL) {
2437+
/*
2438+
* Unicode may not have the representation available, `scalar_value`
2439+
* ensures materialization.
2440+
*/
2441+
PyArray_Descr *descr = PyArray_DescrFromType(NPY_UNICODE);
2442+
scalar_value(self, descr);
2443+
Py_DECREF(descr);
2444+
if (scalar->obval == NULL) {
2445+
/* allocating memory failed */
2446+
Py_SETREF(view->obj, NULL);
2447+
return -1;
2448+
}
2449+
}
2450+
view->buf = scalar->obval;
2451+
2452+
if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
2453+
/* It is unnecessary to find the correct format */
2454+
view->format = NULL;
2455+
return 0;
2456+
}
2457+
2458+
if (scalar->buffer_fmt != NULL) {
2459+
view->format = scalar->buffer_fmt;
2460+
}
2461+
else {
2462+
scalar->buffer_fmt = PyObject_Malloc(22);
2463+
if (scalar->buffer_fmt == NULL) {
2464+
Py_SETREF(view->obj, NULL);
2465+
return -1;
2466+
}
2467+
PyOS_snprintf(scalar->buffer_fmt, 22, "%" NPY_INTP_FMT "w", length);
2468+
view->format = scalar->buffer_fmt;
2469+
}
2470+
2471+
return 0;
2472+
}
2473+
2474+
static PyBufferProcs unicode_arrtype_as_buffer = {
2475+
.bf_getbuffer = unicode_getbuffer,
2476+
/* No need to release the buffer */
2477+
};
2478+
2479+
2480+
/**begin repeat
2481+
* #name = datetime, timedelta#
2482+
* #Name = Datetime, Timedelta#
2483+
*/
2484+
2485+
static int
2486+
@name@_getbuffer(PyObject *self, Py_buffer *view, int flags)
2487+
{
2488+
if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
2489+
return -1;
2490+
}
2491+
Py@Name@ScalarObject *scalar = (Py@Name@ScalarObject *)self;
2492+
2493+
view->ndim = 1;
2494+
view->len = 8;
2495+
view->itemsize = 1;
2496+
static Py_ssize_t length = 8;
2497+
view->shape = &length;
2498+
view->strides = NULL;
2499+
view->suboffsets = NULL;
2500+
Py_INCREF(self);
2501+
view->obj = self;
2502+
2503+
view->buf = &(scalar->obval);
2504+
2505+
if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
2506+
/* It is unnecessary to find the correct format */
2507+
view->format = NULL;
2508+
return 0;
2509+
}
2510+
2511+
/* export datetime scalars as bytes (although arrays are not exported) */
2512+
view->format = "B";
2513+
2514+
return 0;
2515+
}
2516+
2517+
static PyBufferProcs @name@_arrtype_as_buffer = {
2518+
.bf_getbuffer = @name@_getbuffer,
2519+
/* No need to release the buffer */
2520+
};
2521+
2522+
/**end repeat**/
2523+
2524+
static PyBufferProcs void_arrtype_as_buffer = {
2525+
.bf_getbuffer = void_getbuffer, /* defined in buffer.c */
2526+
/* No need to release the buffer */
23712527
};
23722528

23732529

@@ -3584,7 +3740,6 @@ initialize_numeric_types(void)
35843740
init_basetypes();
35853741
PyGenericArrType_Type.tp_dealloc = (destructor)gentype_dealloc;
35863742
PyGenericArrType_Type.tp_as_number = &gentype_as_number;
3587-
PyGenericArrType_Type.tp_as_buffer = &gentype_as_buffer;
35883743
PyGenericArrType_Type.tp_as_mapping = &gentype_as_mapping;
35893744
PyGenericArrType_Type.tp_flags = BASEFLAGS;
35903745
PyGenericArrType_Type.tp_methods = gentype_methods;
@@ -3668,10 +3823,15 @@ initialize_numeric_types(void)
36683823
Py@NAME@ArrType_Type.tp_new = @name@_arrtype_new;
36693824
Py@NAME@ArrType_Type.tp_richcompare = gentype_richcompare;
36703825

3826+
#define _IS_@NAME@ /* inherit string buffer */
3827+
#if !defined(_IS_String)
3828+
Py@NAME@ArrType_Type.tp_as_buffer = &@name@_arrtype_as_buffer;
3829+
#endif
3830+
#undef _IS_@NAME@
3831+
36713832
/**end repeat**/
36723833

36733834
PyUnicodeArrType_Type.tp_dealloc = unicode_arrtype_dealloc;
3674-
PyUnicodeArrType_Type.tp_as_buffer = &gentype_as_buffer;
36753835

36763836
/**begin repeat
36773837
* #name = bool, byte, short, ubyte, ushort, uint, ulong, ulonglong,

numpy/core/tests/test_scalarinherit.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66

77
import numpy as np
8-
from numpy.testing import assert_
8+
from numpy.testing import assert_, assert_raises
99

1010

1111
class A:
@@ -74,13 +74,21 @@ def test_char_radd(self):
7474
assert_(s + np_s == b'defabc')
7575
assert_(u + np_u == u'defabc')
7676

77+
class MyStr(str, np.generic):
78+
# would segfault
79+
pass
80+
81+
with assert_raises(TypeError):
82+
# Previously worked, but gave completely wrong result
83+
ret = s + MyStr('abc')
7784

78-
class Mystr(str, np.generic):
85+
class MyBytes(bytes, np.generic):
7986
# would segfault
8087
pass
8188

82-
ret = s + Mystr('abc')
83-
assert_(type(ret) is type(s))
89+
ret = s + MyBytes(b'abc')
90+
assert(type(ret) is type(s))
91+
assert ret == b"defabc"
8492

8593
def test_char_repeat(self):
8694
np_s = np.string_('abc')

0 commit comments

Comments
 (0)