diff options
| author | Julian Taylor <jtaylor.debian@googlemail.com> | 2014-07-09 21:17:47 +0200 |
|---|---|---|
| committer | Julian Taylor <jtaylor.debian@googlemail.com> | 2014-07-09 21:54:36 +0200 |
| commit | 2e83a5d9d31691789d48277e194369ca6a9a9592 (patch) | |
| tree | 481348306f7811285a9611a55d4307c2d0066d21 /numpy | |
| parent | 251acc007a07a2ed06a4b4666df82c8160c89f58 (diff) | |
| download | numpy-2e83a5d9d31691789d48277e194369ca6a9a9592.tar.gz | |
TST: add temporary elision testcases
A very significant optimization for numpy would be to avoid the
temporaries in these types of expressions:
res = a + b + c
transforming it into:
res = a + b
res += c
An approach to do that is to check the reference count of the PyNumber
slot arguments, temporary expressions coming from python have reference
count 1 and can be converted to inplace operations.
Unfortunately C-extensions can skip increasing reference counts for
operations breaking the assumption that an inplace operation can be
performed, e.g. Cython does this type of optimizations.
To ensure to not accidentally break such extensions in future test
that refcount == 1 operands from extensions are not elided.
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/src/multiarray/multiarray_tests.c.src | 42 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 60 |
2 files changed, 102 insertions, 0 deletions
diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index bd0366bd5..a22319cfe 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -556,6 +556,42 @@ fail: return NULL; } +/* check no elison for avoided increfs */ +static PyObject * +incref_elide(PyObject *dummy, PyObject *args) +{ + PyObject *arg = NULL, *res, *tup; + if (!PyArg_ParseTuple(args, "O", &arg)) { + return NULL; + } + + /* refcount 1 array but should not be elided */ + arg = PyArray_NewCopy((PyArrayObject*)arg, NPY_KEEPORDER); + res = PyNumber_Add(arg, arg); + + /* return original copy, should be equal to input */ + tup = PyTuple_Pack(2, arg, res); + Py_DECREF(arg); + Py_DECREF(res); + return tup; +} + +/* check no elison for get from list without incref */ +static PyObject * +incref_elide_l(PyObject *dummy, PyObject *args) +{ + PyObject *arg = NULL, *r, *res; + if (!PyArg_ParseTuple(args, "O", &arg)) { + return NULL; + } + /* get item without increasing refcount, item may still be on the python + * stack but above the inaccessible top */ + r = PyList_GetItem(arg, 4); + res = PyNumber_Add(r, r); + + return res; +} + #if !defined(NPY_PY3K) static PyObject * @@ -839,6 +875,12 @@ static PyMethodDef Multiarray_TestsMethods[] = { {"test_inplace_increment", inplace_increment, METH_VARARGS, NULL}, + {"incref_elide", + incref_elide, + METH_VARARGS, NULL}, + {"incref_elide_l", + incref_elide_l, + METH_VARARGS, NULL}, #if !defined(NPY_PY3K) {"test_int_subclass", int_subclass, diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index cb5c0095c..57fae23aa 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -1695,6 +1695,66 @@ class TestMethods(TestCase): class TestBinop(object): + def test_inplace(self): + # test refcount 1 inplace conversion + assert_array_almost_equal(np.array([0.5]) * np.array([1.0, 2.0]), + [0.5, 1.0]) + + d = np.array([0.5, 0.5])[::2] + assert_array_almost_equal(d * (d * np.array([1.0, 2.0])), + [0.25, 0.5]) + + a = np.array([0.5]) + b = np.array([0.5]) + c = a + b + c = a - b + c = a * b + c = a / b + assert_equal(a, b) + assert_almost_equal(c, 1.) + + c = a + b * 2. / b * a - a / b + assert_equal(a, b) + assert_equal(c, 0.5) + + # true divide + a = np.array([5]) + b = np.array([3]) + c = (a * a) / b + + assert_almost_equal(c, 25 / 3) + assert_equal(a, 5) + assert_equal(b, 3) + + def test_extension_incref_elide(self): + # test extension (e.g. cython) calling PyNumber_* slots without + # increasing the reference counts + # + # def incref_elide(a): + # d = input.copy() # refcount 1 + # return d, d + d # PyNumber_Add without increasing refcount + from numpy.core.multiarray_tests import incref_elide + d = np.ones(5) + orig, res = incref_elide(d) + # the return original should not be changed to an inplace operation + assert_array_equal(orig, d) + assert_array_equal(res, d + d) + + def test_extension_incref_elide_stack(self): + # scanning if the refcount == 1 object is on the python stack to check + # that we are called directly from python is flawed as object may still + # be above the stack pointer and we have no access to the top of it + # + # def incref_elide_l(d): + # return l[4] + l[4] # PyNumber_Add without increasing refcount + from numpy.core.multiarray_tests import incref_elide_l + # padding with 1 makes sure the object on the stack is not overwriten + l = [1, 1, 1, 1, np.ones(5)] + res = incref_elide_l(l) + # the return original should not be changed to an inplace operation + assert_array_equal(l[4], np.ones(5)) + assert_array_equal(res, l[4] + l[4]) + def test_ufunc_override_rop_precedence(self): # Check that __rmul__ and other right-hand operations have # precedence over __numpy_ufunc__ |
