summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorJulian Taylor <jtaylor.debian@googlemail.com>2014-07-09 21:17:47 +0200
committerJulian Taylor <jtaylor.debian@googlemail.com>2014-07-09 21:54:36 +0200
commit2e83a5d9d31691789d48277e194369ca6a9a9592 (patch)
tree481348306f7811285a9611a55d4307c2d0066d21 /numpy
parent251acc007a07a2ed06a4b4666df82c8160c89f58 (diff)
downloadnumpy-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.src42
-rw-r--r--numpy/core/tests/test_multiarray.py60
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__