Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Reduce overhead of configurable data allocation strategy (NEP49) #21531

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/source/reference/c-api/data_memory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ functions may change during the lifetime of the process, each ``ndarray``
carries with it the functions used at the time of its instantiation, and these
will be used to reallocate or free the data memory of the instance.

For details see: :ref:`NEP 49 — Data allocation strategies <NEP49>`.

.. c:type:: PyDataMem_Handler

A struct to hold function pointers used to manipulate memory
Expand Down Expand Up @@ -87,6 +89,8 @@ will be used to reallocate or free the data memory of the instance.
return ``NULL`` if an error has occurred. We wrap the user-provided functions
so they will still call the python and numpy memory management callback
hooks.
The handlers are stored in a Python context variable (see https://docs.python.org/3/library/contextvars.html),
so there can be multiple handlers in a Python session.
eendebakpt marked this conversation as resolved.
Show resolved Hide resolved

.. c:function:: PyObject * PyDataMem_GetHandler()

Expand Down
30 changes: 26 additions & 4 deletions numpy/core/src/multiarray/alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,25 @@ int uo_index=0; /* user_override index */

/* Wrappers for the default or any user-assigned PyDataMem_Handler */

int default_allocator_policy = 1;

static inline PyDataMem_Handler *
_PyDataMem_GetHandler_Internal(PyObject *mem_handler)
{
if (PyDataMem_DefaultHandler == mem_handler)
// fast path for default allocator
return &default_handler;

PyDataMem_Handler *handler = (PyDataMem_Handler *)PyCapsule_GetPointer(
mem_handler, "mem_handler");
return handler;
}

NPY_NO_EXPORT void *
PyDataMem_UserNEW(size_t size, PyObject *mem_handler)
{
void *result;
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
if (handler == NULL) {
return NULL;
}
Expand All @@ -457,7 +471,7 @@ NPY_NO_EXPORT void *
PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler)
{
void *result;
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
if (handler == NULL) {
return NULL;
}
Expand All @@ -479,7 +493,7 @@ PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler)
NPY_NO_EXPORT void
PyDataMem_UserFREE(void *ptr, size_t size, PyObject *mem_handler)
{
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
if (handler == NULL) {
WARN_NO_RETURN(PyExc_RuntimeWarning,
"Could not get pointer to 'mem_handler' from PyCapsule");
Expand All @@ -502,7 +516,7 @@ NPY_NO_EXPORT void *
PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler)
{
void *result;
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
if (handler == NULL) {
return NULL;
}
Expand Down Expand Up @@ -535,6 +549,9 @@ PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler)
NPY_NO_EXPORT PyObject *
PyDataMem_SetHandler(PyObject *handler)
{
// once the user sets an allocation policy, we cannot guarantee the default allocator without checking the context
eendebakpt marked this conversation as resolved.
Show resolved Hide resolved
default_allocator_policy = 0;

PyObject *old_handler;
PyObject *token;
if (PyContextVar_Get(current_handler, NULL, &old_handler)) {
Expand All @@ -560,6 +577,11 @@ NPY_NO_EXPORT PyObject *
PyDataMem_GetHandler()
{
PyObject *handler;

if (default_allocator_policy) {
Py_INCREF(PyDataMem_DefaultHandler);
return PyDataMem_DefaultHandler;
}
if (PyContextVar_Get(current_handler, NULL, &handler)) {
return NULL;
}
Expand Down