summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/code_generators/cversions.txt2
-rw-r--r--numpy/core/code_generators/numpy_api.py4
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h12
-rw-r--r--numpy/core/src/multiarray/multiarray_tests.c.src59
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c96
-rw-r--r--numpy/core/src/npysort/sort.c.src18
-rw-r--r--numpy/core/tests/test_multiarray.py13
-rw-r--r--numpy/lib/src/_compiled_base.c7
8 files changed, 197 insertions, 14 deletions
diff --git a/numpy/core/code_generators/cversions.txt b/numpy/core/code_generators/cversions.txt
index 99ea072ca..5bdb33c94 100644
--- a/numpy/core/code_generators/cversions.txt
+++ b/numpy/core/code_generators/cversions.txt
@@ -10,4 +10,4 @@
# PyArray_CountNonzero, PyArray_NewLikeArray and PyArray_MatrixProduct2.
0x00000006 = e61d5dc51fa1c6459328266e215d6987
# Version 7 (NumPy 1.7) improved datetime64, misc utilities.
-0x00000007 = 1768b6c404a3d5a2a6bfe7c68f89e3aa
+0x00000007 = e396ba3912dcf052eaee1b0b203a7724
diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py
index fd2b9628e..c49c3c346 100644
--- a/numpy/core/code_generators/numpy_api.py
+++ b/numpy/core/code_generators/numpy_api.py
@@ -324,6 +324,10 @@ multiarray_funcs_api = {
'PyArray_DebugPrint': 285,
'PyArray_FailUnlessWriteable': 286,
'PyArray_SetUpdateIfCopyBase': 287,
+ 'PyDataMem_NEW': 288,
+ 'PyDataMem_FREE': 289,
+ 'PyDataMem_RENEW': 290,
+ 'PyDataMem_SetEventHook': 291,
}
ufunc_types_api = {
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 9605c20f8..13483396b 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -327,10 +327,7 @@ struct NpyAuxData_tag {
* allocated.
*/
- /* Data buffer */
-#define PyDataMem_NEW(size) ((char *)malloc(size))
-#define PyDataMem_FREE(ptr) free(ptr)
-#define PyDataMem_RENEW(ptr,size) ((char *)realloc(ptr,size))
+ /* Data buffer - PyDataMem_NEW/FREE/RENEW are in multiarraymodule.c */
#define NPY_USE_PYMEM 1
@@ -1714,6 +1711,13 @@ typedef struct {
*/
} PyArrayInterface;
+/*
+ * This is a function for hooking into the PyDataMem_NEW/FREE/RENEW functions.
+ * See the documentation for PyDataMem_SetEventHook.
+ */
+typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
+ void *user_data);
+
#if !(defined(NPY_NO_DEPRECATED_API) && (NPY_API_VERSION <= NPY_NO_DEPRECATED_API))
#include "npy_deprecated_api.h"
#endif
diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src
index 15d4c871e..a0f70aabc 100644
--- a/numpy/core/src/multiarray/multiarray_tests.c.src
+++ b/numpy/core/src/multiarray/multiarray_tests.c.src
@@ -375,6 +375,59 @@ clean_ax:
return NULL;
}
+/* PyDataMem_SetHook tests */
+static int malloc_free_counts[2];
+static PyDataMem_EventHookFunc *old_hook = NULL;
+static void *old_data;
+
+static void test_hook(void *old, void *new, size_t size, void *user_data)
+{
+ int* counters = (int *) user_data;
+ if (old == NULL) {
+ counters[0]++; /* malloc counter */
+ }
+ if (size == 0) {
+ counters[1]++; /* free counter */
+ }
+}
+
+static PyObject*
+test_pydatamem_seteventhook_start(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
+{
+ malloc_free_counts[0] = malloc_free_counts[1] = 0;
+ old_hook = PyDataMem_SetEventHook(test_hook, (void *) malloc_free_counts, &old_data);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject*
+test_pydatamem_seteventhook_end(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
+{
+ PyDataMem_EventHookFunc *my_hook;
+ void *my_data;
+
+ my_hook = PyDataMem_SetEventHook(old_hook, old_data, &my_data);
+ if ((my_hook != test_hook) || (my_data != (void *) malloc_free_counts)) {
+ PyErr_SetString(PyExc_ValueError,
+ "hook/data was not the expected test hook");
+ return NULL;
+ }
+
+ if (malloc_free_counts[0] == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "malloc count is zero after test");
+ return NULL;
+ }
+ if (malloc_free_counts[1] == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "free count is zero after test");
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
static PyMethodDef Multiarray_TestsMethods[] = {
{"test_neighborhood_iterator",
test_neighborhood_iterator,
@@ -382,6 +435,12 @@ static PyMethodDef Multiarray_TestsMethods[] = {
{"test_neighborhood_iterator_oob",
test_neighborhood_iterator_oob,
METH_VARARGS, NULL},
+ {"test_pydatamem_seteventhook_start",
+ test_pydatamem_seteventhook_start,
+ METH_NOARGS, NULL},
+ {"test_pydatamem_seteventhook_end",
+ test_pydatamem_seteventhook_end,
+ METH_NOARGS, NULL},
{NULL, NULL, 0, NULL} /* Sentinel */
};
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index 1ab7823ad..0180b1f89 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -3430,6 +3430,102 @@ test_interrupt(PyObject *NPY_UNUSED(self), PyObject *args)
return PyInt_FromLong(a);
}
+/* malloc/free/realloc hook */
+NPY_NO_EXPORT PyDataMem_EventHookFunc *_PyDataMem_eventhook;
+NPY_NO_EXPORT void *_PyDataMem_eventhook_user_data;
+
+/*NUMPY_API
+ * Sets the allocation event hook for numpy array data.
+ * Takes a PyDataMem_EventHookFunc *, which has the signature:
+ * void hook(void *old, void *new, size_t size, void *user_data).
+ * Also takes a void *user_data, and void **old_data.
+ *
+ * Returns a pointer to the previous hook or NULL. If old_data is
+ * non-NULL, the previous user_data pointer will be copied to it.
+ *
+ * If not NULL, hook will be called at the end of each PyDataMem_NEW/FREE/RENEW:
+ * result = PyDataMem_NEW(size) -> (*hook)(NULL, result, size, user_data)
+ * PyDataMem_FREE(ptr) -> (*hook)(ptr, NULL, 0, user_data)
+ * result = PyDataMem_RENEW(ptr, size) -> (*hook)(ptr, result, size, user_data)
+ *
+ * When the hook is called, the GIL will be held by the calling
+ * thread. The hook should be written to be reentrant, if it performs
+ * operations that might cause new allocation events (such as the
+ * creation/descruction numpy objects, or creating/destroying Python
+ * objects which might cause a gc)
+ */
+NPY_NO_EXPORT PyDataMem_EventHookFunc *
+PyDataMem_SetEventHook(PyDataMem_EventHookFunc *newhook,
+ void *user_data, void **old_data)
+{
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+ PyDataMem_EventHookFunc *temp = _PyDataMem_eventhook;
+ _PyDataMem_eventhook = newhook;
+ if (old_data != NULL) {
+ *old_data = _PyDataMem_eventhook_user_data;
+ }
+ _PyDataMem_eventhook_user_data = user_data;
+ PyGILState_Release(gilstate);
+ return temp;
+}
+
+/*NUMPY_API
+ * Allocates memory for array data.
+ */
+NPY_NO_EXPORT void *
+PyDataMem_NEW(size_t size)
+{
+ void *result;
+
+ result = malloc(size);
+ if (_PyDataMem_eventhook != NULL) {
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+ if (_PyDataMem_eventhook != NULL) {
+ (*_PyDataMem_eventhook)(NULL, result, size,
+ _PyDataMem_eventhook_user_data);
+ }
+ PyGILState_Release(gilstate);
+ }
+ return (char *)result;
+}
+
+/*NUMPY_API
+ * Free memory for array data.
+ */
+NPY_NO_EXPORT void
+PyDataMem_FREE(void *ptr)
+{
+ free(ptr);
+ if (_PyDataMem_eventhook != NULL) {
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+ if (_PyDataMem_eventhook != NULL) {
+ (*_PyDataMem_eventhook)(ptr, NULL, 0,
+ _PyDataMem_eventhook_user_data);
+ }
+ PyGILState_Release(gilstate);
+ }
+}
+
+/*NUMPY_API
+ * Reallocate/resize memory for array data.
+ */
+NPY_NO_EXPORT void *
+PyDataMem_RENEW(void *ptr, size_t size)
+{
+ void *result;
+
+ result = realloc(ptr, size);
+ if (_PyDataMem_eventhook != NULL) {
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+ if (_PyDataMem_eventhook != NULL) {
+ (*_PyDataMem_eventhook)(ptr, result, size,
+ _PyDataMem_eventhook_user_data);
+ }
+ PyGILState_Release(gilstate);
+ }
+ return (char *)result;
+}
+
static struct PyMethodDef array_module_methods[] = {
{"_get_ndarray_c_version",
(PyCFunction)array__get_ndarray_c_version,
diff --git a/numpy/core/src/npysort/sort.c.src b/numpy/core/src/npysort/sort.c.src
index 618259f96..514131748 100644
--- a/numpy/core/src/npysort/sort.c.src
+++ b/numpy/core/src/npysort/sort.c.src
@@ -38,6 +38,12 @@
#define SMALL_MERGESORT 20
#define SMALL_STRING 16
+/* These should be changed to PyDataMem_NEW/FREE if npysort is moved
+ * to the multiarray directory.
+ */
+#define NpySortArray_malloc(size) ((char *)malloc(size))
+#define NpySortArray_free(ptr) free(ptr)
+
/*
*****************************************************************************
@@ -335,14 +341,14 @@ mergesort_@suff@(@type@ *start, npy_intp num, void *NOT_USED)
pl = start;
pr = pl + num;
- pw = (@type@ *) PyDataMem_NEW((num/2)*sizeof(@type@));
+ pw = (@type@ *) NpySortArray_malloc((num/2)*sizeof(@type@));
if (!pw) {
PyErr_NoMemory();
return -1;
}
mergesort0_@suff@(pl, pr, pw);
- PyDataMem_FREE(pw);
+ NpySortArray_free(pw);
return 0;
}
@@ -476,13 +482,13 @@ mergesort_@suff@(@type@ *start, npy_intp num, PyArrayObject *arr)
pl = start;
pr = pl + num*len;
- pw = (@type@ *) PyDataMem_NEW((num/2)*elsize);
+ pw = (@type@ *) NpySortArray_malloc((num/2)*elsize);
if (!pw) {
PyErr_NoMemory();
err = -1;
goto fail_0;
}
- vp = (@type@ *) PyDataMem_NEW(elsize);
+ vp = (@type@ *) NpySortArray_malloc(elsize);
if (!vp) {
PyErr_NoMemory();
err = -1;
@@ -490,9 +496,9 @@ mergesort_@suff@(@type@ *start, npy_intp num, PyArrayObject *arr)
}
mergesort0_@suff@(pl, pr, pw, vp, len);
- PyDataMem_FREE(vp);
+ NpySortArray_free(vp);
fail_1:
- PyDataMem_FREE(pw);
+ NpySortArray_free(pw);
fail_0:
return err;
}
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 82ead8958..73abc81b9 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -10,7 +10,8 @@ from numpy.testing.utils import WarningManager
from numpy.compat import asbytes, getexception, strchar
from test_print import in_foreign_locale
from numpy.core.multiarray_tests import (
- test_neighborhood_iterator, test_neighborhood_iterator_oob
+ test_neighborhood_iterator, test_neighborhood_iterator_oob,
+ test_pydatamem_seteventhook_start, test_pydatamem_seteventhook_end,
)
from numpy.testing import (
TestCase, run_module_suite, assert_, assert_raises,
@@ -2649,6 +2650,16 @@ def test_flat_element_deletion():
except:
raise AssertionError
+class TestMemEventHook(TestCase):
+ def test_mem_seteventhook(self):
+ # The actual tests are within the C code in
+ # multiarray/multiarray_tests.c.src
+ test_pydatamem_seteventhook_start()
+ # force an allocation and free of a numpy array
+ a = np.zeros(10)
+ del a
+ test_pydatamem_seteventhook_end()
+
if __name__ == "__main__":
run_module_suite()
diff --git a/numpy/lib/src/_compiled_base.c b/numpy/lib/src/_compiled_base.c
index c31ee5cd8..9bb8a613d 100644
--- a/numpy/lib/src/_compiled_base.c
+++ b/numpy/lib/src/_compiled_base.c
@@ -670,7 +670,10 @@ arr_interp(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwdict)
/* only pre-calculate slopes if there are relatively few of them. */
if (lenxp <= lenx) {
- slopes = (double *) PyDataMem_NEW((lenxp - 1)*sizeof(double));
+ slopes = (double *) PyArray_malloc((lenxp - 1)*sizeof(double));
+ if (! slopes) {
+ goto fail;
+ }
NPY_BEGIN_ALLOW_THREADS;
for (i = 0; i < lenxp - 1; i++) {
slopes[i] = (dy[i + 1] - dy[i])/(dx[i + 1] - dx[i]);
@@ -692,7 +695,7 @@ arr_interp(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwdict)
}
}
NPY_END_ALLOW_THREADS;
- PyDataMem_FREE(slopes);
+ PyArray_free(slopes);
}
else {
NPY_BEGIN_ALLOW_THREADS;