8000 PERF: Fast path for returning default memory allocator · numpy/numpy@34e5e02 · GitHub
[go: up one dir, main page]

Skip to content

Commit 34e5e02

Browse files
committed
PERF: Fast path for returning default memory allocator
1 parent c8de16e commit 34e5e02

File tree

2 files changed

+30
-4
lines changed

2 files changed

+30
-4
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ functions may change during the lifetime of the process, each ``ndarray``
5555
carries with it the functions used at the time of its instantiation, and these
5656
will be used to reallocate or free the data memory of the instance.
5757

58+
For details see: :ref:`NEP 49 — Data allocation strategies <NEP49>`.
59+
5860
.. c:type:: PyDataMem_Handler
5961
6062
A struct to hold function pointers used to manipulate memory
@@ -87,6 +89,8 @@ will be used to reallocate or free the data memory of the instance.
8789
return ``NULL`` if an error has occurred. We wrap the user-provided functions
8890
so they will still call the python and numpy memory management callback
8991
hooks.
92+
The handlers are stored in a Python context variable (see https://docs.python.org/3/library/contextvars.html),
93+
so there can be multiple handlers in a Python session.
9094
9195
.. c:function:: PyObject * PyDataMem_GetHandler()
9296

numpy/core/src/multiarray/alloc.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,11 +430,25 @@ int uo_index=0; /* user_override index */
430430

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

433+
int default_allocator_policy = 1;
434+
435+
static inline PyDataMem_Handler *
436+
_PyDataMem_GetHandler_Internal(PyObject *mem_handler)
437+
{
438+
if (PyDataMem_DefaultHandler == mem_handler)
439+
// fast path for default allocator
440+
return &default_handler;
441+
442+
PyDataMem_Handler *handler = (PyDataMem_Handler *)PyCapsule_GetPointer(
443+
mem_handler, "mem_handler");
444+
return handler;
445+
}
446+
433447
NPY_NO_EXPORT void *
434448
PyDataMem_UserNEW(size_t size, PyObject *mem_handler)
435449
{
436450
void *result;
437-
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
451+
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
438452
if (handler == NULL) {
439453
return NULL;
440454
}
@@ -457,7 +471,7 @@ NPY_NO_EXPORT void *
457471
PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler)
458472
{
459473
void *result;
460-
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
474+
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
461475
if (handler == NULL) {
462476
return NULL;
463477
}
@@ -479,7 +493,7 @@ PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler)
479493
NPY_NO_EXPORT void
480494
PyDataMem_UserFREE(void *ptr, size_t size, PyObject *mem_handler)
481495
{
482-
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
496+
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
483497
if (handler == NULL) {
484498
WARN_NO_RETURN(PyExc_RuntimeWarning,
485499
"Could not get pointer to 'mem_handler' from PyCapsule");
@@ -502,7 +516,7 @@ NPY_NO_EXPORT void *
502516
PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler)
503517
{
504518
void *result;
505-
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(mem_handler, "mem_handler");
519+
PyDataMem_Handler *handler = _PyDataMem_GetHandler_Internal(mem_handler);
506520
if (handler == NULL) {
507521
return NULL;
508522
}
@@ -535,6 +549,9 @@ PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler)
535549
NPY_NO_EXPORT PyObject *
536550
PyDataMem_SetHandler(PyObject *handler)
537551
{
552+
// once the user sets an allocation policy, we cannot guarantee the default allocator without checking the context
553+
default_allocator_policy = 0;
554+
538555
PyObject *old_handler;
539556
PyObject *token;
540557
if (PyContextVar_Get(current_handler, NULL, &old_handler)) {
@@ -560,6 +577,11 @@ NPY_NO_EXPORT PyObject *
560577
PyDataMem_GetHandler()
561578
{
562579
PyObject *handler;
580+
581+
if (default_allocator_policy) {
582+
Py_INCREF(PyDataMem_DefaultHandler);
583+
return PyDataMem_DefaultHandler;
584+
}
563585
if (PyContextVar_Get(current_handler, NULL, &handler)) {
564586
return NULL;
565587
}

0 commit comments

Comments
 (0)
0