Skip to content

Commit 8dbd507

Browse files
authored
Merge pull request numpy#20343 from mattip/alloc-version
ENH: add a 'version' field to PyDataMem_Handler
2 parents 693e22d + 6312c18 commit 8dbd507

File tree

9 files changed

+87
-12
lines changed

9 files changed

+87
-12
lines changed

doc/neps/nep-0049.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ The name of the handler will be exposed on the python level via a
109109
``numpy.core.multiarray.get_handler_name()`` it will return the name of the
110110
handler that will be used to allocate data for the next new `ndarrray`.
111111

112+
The version of the handler will be exposed on the python level via a
113+
``numpy.core.multiarray.get_handler_version(arr)`` function. If called as
114+
``numpy.core.multiarray.get_handler_version()`` it will return the version of the
115+
handler that will be used to allocate data for the next new `ndarrray`.
116+
117+
The version, currently 1, allows for future enhancements to the
118+
``PyDataMemAllocator``. If fields are added, they must be added to the end.
119+
120+
112121
NumPy C-API functions
113122
=====================
114123

@@ -119,7 +128,8 @@ NumPy C-API functions
119128
.. code-block:: c
120129
121130
typedef struct {
122-
char name[128]; /* multiple of 64 to keep the struct aligned */
131+
char name[127]; /* multiple of 64 to keep the struct aligned */
132+
uint8_t version; /* currently 1 */
123133
PyDataMemAllocator allocator;
124134
} PyDataMem_Handler;
125135
@@ -279,6 +289,7 @@ the ``sz`` argument is correct.
279289
280290
static PyDataMem_Handler new_handler = {
281291
"secret_data_allocator",
292+
1,
282293
{
283294
&new_handler_ctx,
284295
shift_alloc, /* malloc */

doc/source/reference/c-api/data_memory.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ reallocate or free the data memory of the instance.
6262
.. code-block:: c
6363
6464
typedef struct {
65-
char name[128]; /* multiple of 64 to keep the struct aligned */
65+
char name[127]; /* multiple of 64 to keep the struct aligned */
66+
uint8_t version; /* currently 1 */
6667
PyDataMemAllocator allocator;
6768
} PyDataMem_Handler;
6869

numpy/core/_add_newdocs.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4759,6 +4759,16 @@
47594759
memory, in which case you can traverse ``a.base`` for a memory handler.
47604760
""")
47614761

4762+
add_newdoc('numpy.core.multiarray', 'get_handler_version',
4763+
"""
4764+
get_handler_version(a: ndarray) -> int,None
4765+
4766+
Return the version of the memory handler used by `a`. If not provided,
4767+
return the version of the memory handler that will be used to allocate data
4768+
for the next `ndarray` in this context. May return None if `a` does not own
4769+
its memory, in which case you can traverse ``a.base`` for a memory handler.
4770+
""")
4771+
47624772
add_newdoc('numpy.core.multiarray', '_set_madvise_hugepage',
47634773
"""
47644774
_set_madvise_hugepage(enabled: bool) -> bool

numpy/core/include/numpy/ndarraytypes.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,10 +674,15 @@ typedef struct {
674674
void* (*calloc) (void *ctx, size_t nelem, size_t elsize);
675675
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
676676
void (*free) (void *ctx, void *ptr, size_t size);
677+
/*
678+
* This is the end of the version=1 struct. Only add new fields after
679+
* this line
680+
*/
677681
} PyDataMemAllocator;
678682

679683
typedef struct {
680-
char name[128]; /* multiple of 64 to keep the struct aligned */
684+
char name[127]; /* multiple of 64 to keep the struct aligned */
685+
uint8_t version; /* currently 1 */
681686
PyDataMemAllocator allocator;
682687
} PyDataMem_Handler;
683688

numpy/core/multiarray.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
'dot', 'dragon4_positional', 'dragon4_scientific', 'dtype',
3434
'empty', 'empty_like', 'error', 'flagsobj', 'flatiter', 'format_longfloat',
3535
'frombuffer', 'fromfile', 'fromiter', 'fromstring',
36-
'get_handler_name', 'inner', 'interp', 'interp_complex', 'is_busday',
37-
'lexsort', 'matmul', 'may_share_memory', 'min_scalar_type', 'ndarray',
38-
'nditer', 'nested_iters', 'normalize_axis_index', 'packbits',
39-
'promote_types', 'putmask', 'ravel_multi_index', 'result_type', 'scalar',
40-
'set_datetimeparse_function', 'set_legacy_print_mode', 'set_numeric_ops',
41-
'set_string_function', 'set_typeDict', 'shares_memory',
42-
'tracemalloc_domain', 'typeinfo', 'unpackbits', 'unravel_index', 'vdot',
43-
'where', 'zeros']
36+
'get_handler_name', 'get_handler_version', 'inner', 'interp',
37+
'interp_complex', 'is_busday', 'lexsort', 'matmul', 'may_share_memory',
38+
'min_scalar_type', 'ndarray', 'nditer', 'nested_iters',
39+
'normalize_axis_index', 'packbits', 'promote_types', 'putmask',
40+
'ravel_multi_index', 'result_type', 'scalar', 'set_datetimeparse_function',
41+
'set_legacy_print_mode', 'set_numeric_ops', 'set_string_function',
42+
'set_typeDict', 'shares_memory', 'tracemalloc_domain', 'typeinfo',
43+
'unpackbits', 'unravel_index', 'vdot', 'where', 'zeros']
4444

4545
# For backward compatibility, make sure pickle imports these functions from here
4646
_reconstruct.__module__ = 'numpy.core.multiarray'

numpy/core/src/multiarray/alloc.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ default_free(void *NPY_UNUSED(ctx), void *ptr, size_t size)
370370
/* Memory handler global default */
371371
PyDataMem_Handler default_handler = {
372372
"default_allocator",
373+
1,
373374
{
374375
NULL, /* ctx */
375376
default_malloc, /* malloc */
@@ -395,7 +396,6 @@ PyDataMem_UserNEW(size_t size, PyObject *mem_handler)
395396
if (handler == NULL) {
396397
return NULL;
397398
}
398-
399399
assert(size != 0);
400400
result = handler->allocator.malloc(handler->allocator.ctx, size);
401401
if (_PyDataMem_eventhook != NULL) {
@@ -639,3 +639,40 @@ get_handler_name(PyObject *NPY_UNUSED(self), PyObject *args)
639639
Py_DECREF(mem_handler);
640640
return name;
641641
}
642+
643+
NPY_NO_EXPORT PyObject *
644+
get_handler_version(PyObject *NPY_UNUSED(self), PyObject *args)
645+
{
646+
PyObject *arr=NULL;
647+
if (!PyArg_ParseTuple(args, "|O:get_handler_version", &arr)) {
648+
return NULL;
649+
}
650+
if (arr != NULL && !PyArray_Check(arr)) {
651+
PyErr_SetString(PyExc_ValueError, "if supplied, argument must be an ndarray");
652+
return NULL;
653+
}
654+
PyObject *mem_handler;
655+
PyDataMem_Handler *handler;
656+
PyObject *version;
657+
if (arr != NULL) {
658+
mem_handler = PyArray_HANDLER((PyArrayObject *) arr);
659+
if (mem_handler == NULL) {
660+
Py_RETURN_NONE;
661+
}
662+
Py_INCREF(mem_handler);
663+
}
664+
else {
665+
mem_handler = PyDataMem_GetHandler();
666+
if (mem_handler == NULL) {
667+
return NULL;
668+
}
669+
}
670+
handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
671+
if (handler == NULL) {
672+
Py_DECREF(mem_handler);
673+
return NULL;
674+
}
675+
version = PyLong_FromLong(handler->version);
676+
Py_DECREF(mem_handler);
677+
return version;
678+
}

numpy/core/src/multiarray/alloc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ extern PyDataMem_Handler default_handler;
4747

4848
NPY_NO_EXPORT PyObject *
4949
get_handler_name(PyObject *NPY_UNUSED(self), PyObject *obj);
50+
NPY_NO_EXPORT PyObject *
51+
get_handler_version(PyObject *NPY_UNUSED(self), PyObject *obj);
5052

5153
#endif /* NUMPY_CORE_SRC_MULTIARRAY_ALLOC_H_ */

numpy/core/src/multiarray/multiarraymodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4437,6 +4437,9 @@ static struct PyMethodDef array_module_methods[] = {
44374437
{"get_handler_name",
44384438
(PyCFunction) get_handler_name,
44394439
METH_VARARGS, NULL},
4440+
{"get_handler_version",
4441+
(PyCFunction) get_handler_version,
4442+
METH_VARARGS, NULL},
44404443
{"_add_newdoc_ufunc", (PyCFunction)add_newdoc_ufunc,
44414444
METH_VARARGS, NULL},
44424445
{"_get_sfloat_dtype",

numpy/core/tests/test_mem_policy.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def get_module(tmp_path):
179179
};
180180
static PyDataMem_Handler secret_data_handler = {
181181
"secret_data_allocator",
182+
1,
182183
{
183184
&secret_data_handler_ctx, /* ctx */
184185
shift_alloc, /* malloc */
@@ -212,17 +213,22 @@ def get_module(tmp_path):
212213
def test_set_policy(get_module):
213214

214215
get_handler_name = np.core.multiarray.get_handler_name
216+
get_handler_version = np.core.multiarray.get_handler_version
215217
orig_policy_name = get_handler_name()
216218

217219
a = np.arange(10).reshape((2, 5)) # a doesn't own its own data
218220
assert get_handler_name(a) is None
221+
assert get_handler_version(a) is None
219222
assert get_handler_name(a.base) == orig_policy_name
223+
assert get_handler_version(a.base) == 1
220224

221225
orig_policy = get_module.set_secret_data_policy()
222226

223227
b = np.arange(10).reshape((2, 5)) # b doesn't own its own data
224228
assert get_handler_name(b) is None
229+
assert get_handler_version(b) is None
225230
assert get_handler_name(b.base) == 'secret_data_allocator'
231+
assert get_handler_version(b.base) == 1
226232

227233
if orig_policy_name == 'default_allocator':
228234
get_module.set_old_policy(None) # tests PyDataMem_SetHandler(NULL)

0 commit comments

Comments
 (0)