[pypy-commit] pypy default: Merged in scalar-operations (pull request #243)

rlamy noreply at buildbot.pypy.org
Sun Jul 6 15:41:11 CEST 2014


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: 
Changeset: r72369:f1bd7e48eb65
Date: 2014-07-06 14:40 +0100
http://bitbucket.org/pypy/pypy/changeset/f1bd7e48eb65/

Log:	Merged in scalar-operations (pull request #243)

	Fix performance regression on ufunc(<scalar>, <scalar>) in numpy

diff --git a/pypy/module/micronumpy/base.py b/pypy/module/micronumpy/base.py
--- a/pypy/module/micronumpy/base.py
+++ b/pypy/module/micronumpy/base.py
@@ -18,7 +18,12 @@
     pass
 
 
-class W_NDimArray(W_Root):
+class W_NumpyObject(W_Root):
+    """Base class for ndarrays and scalars (aka boxes)."""
+    _attrs_ = []
+
+
+class W_NDimArray(W_NumpyObject):
     __metaclass__ = extendabletype
 
     def __init__(self, implementation):
@@ -85,6 +90,14 @@
             w_val = dtype.coerce(space, space.wrap(0))
         return convert_to_array(space, w_val)
 
+    @staticmethod
+    def from_scalar(space, w_scalar):
+        """Convert a scalar into a 0-dim array"""
+        dtype = w_scalar.get_dtype(space)
+        w_arr = W_NDimArray.from_shape(space, [], dtype)
+        w_arr.set_scalar_value(w_scalar)
+        return w_arr
+
 
 def convert_to_array(space, w_obj):
     from pypy.module.micronumpy.ctors import array
diff --git a/pypy/module/micronumpy/boxes.py b/pypy/module/micronumpy/boxes.py
--- a/pypy/module/micronumpy/boxes.py
+++ b/pypy/module/micronumpy/boxes.py
@@ -1,4 +1,3 @@
-from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.mixedmodule import MixedModule
@@ -14,7 +13,7 @@
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.tool.sourcetools import func_with_new_name
 from pypy.module.micronumpy import constants as NPY
-from pypy.module.micronumpy.base import W_NDimArray
+from pypy.module.micronumpy.base import W_NDimArray, W_NumpyObject
 from pypy.module.micronumpy.concrete import VoidBoxStorage
 from pypy.module.micronumpy.flagsobj import W_FlagsObject
 
@@ -126,7 +125,7 @@
         return ret
 
 
-class W_GenericBox(W_Root):
+class W_GenericBox(W_NumpyObject):
     _attrs_ = ['w_flags']
 
     def descr__new__(space, w_subtype, __args__):
@@ -136,6 +135,12 @@
     def get_dtype(self, space):
         return self._get_dtype(space)
 
+    def is_scalar(self):
+        return True
+
+    def get_scalar_value(self):
+        return self
+
     def item(self, space):
         return self.get_dtype(space).itemtype.to_builtin_type(space, self)
 
diff --git a/pypy/module/micronumpy/ctors.py b/pypy/module/micronumpy/ctors.py
--- a/pypy/module/micronumpy/ctors.py
+++ b/pypy/module/micronumpy/ctors.py
@@ -4,7 +4,8 @@
 from rpython.rlib.rstring import strip_spaces
 from rpython.rtyper.lltypesystem import lltype, rffi
 from pypy.module.micronumpy import descriptor, loop
-from pypy.module.micronumpy.base import W_NDimArray, convert_to_array
+from pypy.module.micronumpy.base import (
+    W_NDimArray, convert_to_array, W_NumpyObject)
 from pypy.module.micronumpy.converters import shape_converter
 
 
@@ -24,24 +25,44 @@
     return box
 
 
+def try_array_method(space, w_object, w_dtype=None):
+    w___array__ = space.lookup(w_object, "__array__")
+    if w___array__ is None:
+        return None
+    if w_dtype is None:
+        w_dtype = space.w_None
+    w_array = space.get_and_call_function(w___array__, w_object, w_dtype)
+    if isinstance(w_array, W_NDimArray):
+        return w_array
+    else:
+        raise oefmt(space.w_ValueError,
+                    "object __array__ method not producing an array")
+
+
 @unwrap_spec(ndmin=int, copy=bool, subok=bool)
 def array(space, w_object, w_dtype=None, copy=True, w_order=None, subok=False,
           ndmin=0):
+    w_res = _array(space, w_object, w_dtype, copy, w_order, subok)
+    shape = w_res.get_shape()
+    if len(shape) < ndmin:
+        shape = [1] * (ndmin - len(shape)) + shape
+        impl = w_res.implementation.set_shape(space, w_res, shape)
+        if w_res is w_object:
+            return W_NDimArray(impl)
+        else:
+            w_res.implementation = impl
+    return w_res
+
+def _array(space, w_object, w_dtype=None, copy=True, w_order=None, subok=False):
     from pypy.module.micronumpy import strides
 
     # for anything that isn't already an array, try __array__ method first
     if not isinstance(w_object, W_NDimArray):
-        w___array__ = space.lookup(w_object, "__array__")
-        if w___array__ is not None:
-            if space.is_none(w_dtype):
-                w_dtype = space.w_None
-            w_array = space.get_and_call_function(w___array__, w_object, w_dtype)
-            if isinstance(w_array, W_NDimArray):
-                # feed w_array back into array() for other properties
-                return array(space, w_array, w_dtype, False, w_order, subok, ndmin)
-            else:
-                raise oefmt(space.w_ValueError,
-                            "object __array__ method not producing an array")
+        w_array = try_array_method(space, w_object, w_dtype)
+        if w_array is not None:
+            # continue with w_array, but do further operations in place
+            w_object = w_array
+            copy = False
 
     dtype = descriptor.decode_w_dtype(space, w_dtype)
 
@@ -57,19 +78,10 @@
     # arrays with correct dtype
     if isinstance(w_object, W_NDimArray) and \
             (space.is_none(w_dtype) or w_object.get_dtype() is dtype):
-        shape = w_object.get_shape()
         if copy:
-            w_ret = w_object.descr_copy(space)
+            return w_object.descr_copy(space)
         else:
-            if ndmin <= len(shape):
-                return w_object
-            new_impl = w_object.implementation.set_shape(space, w_object, shape)
-            w_ret = W_NDimArray(new_impl)
-        if ndmin > len(shape):
-            shape = [1] * (ndmin - len(shape)) + shape
-            w_ret.implementation = w_ret.implementation.set_shape(space,
-                                                                  w_ret, shape)
-        return w_ret
+            return w_object
 
     # not an array or incorrect dtype
     shape, elems_w = strides.find_shape_and_elems(space, w_object, dtype)
@@ -81,8 +93,6 @@
             # promote S0 -> S1, U0 -> U1
             dtype = descriptor.variable_dtype(space, dtype.char + '1')
 
-    if ndmin > len(shape):
-        shape = [1] * (ndmin - len(shape)) + shape
     w_arr = W_NDimArray.from_shape(space, shape, dtype, order=order)
     if len(elems_w) == 1:
         w_arr.set_scalar_value(dtype.coerce(space, elems_w[0]))
@@ -91,6 +101,33 @@
     return w_arr
 
 
+def numpify(space, w_object):
+    """Convert the object to a W_NumpyObject"""
+    # XXX: code duplication with _array()
+    from pypy.module.micronumpy import strides
+    if isinstance(w_object, W_NumpyObject):
+        return w_object
+    # for anything that isn't already an array, try __array__ method first
+    w_array = try_array_method(space, w_object)
+    if w_array is not None:
+        return w_array
+
+    shape, elems_w = strides.find_shape_and_elems(space, w_object, None)
+    dtype = strides.find_dtype_for_seq(space, elems_w, None)
+    if dtype is None:
+        dtype = descriptor.get_dtype_cache(space).w_float64dtype
+    elif dtype.is_str_or_unicode() and dtype.elsize < 1:
+        # promote S0 -> S1, U0 -> U1
+        dtype = descriptor.variable_dtype(space, dtype.char + '1')
+
+    if len(elems_w) == 1:
+        return dtype.coerce(space, elems_w[0])
+    else:
+        w_arr = W_NDimArray.from_shape(space, shape, dtype)
+        loop.assign(space, w_arr, elems_w)
+        return w_arr
+
+
 def _zeros_or_empty(space, w_shape, w_dtype, w_order, zero):
     dtype = space.interp_w(descriptor.W_Dtype,
         space.call_function(space.gettypefor(descriptor.W_Dtype), w_dtype))
diff --git a/pypy/module/micronumpy/ufuncs.py b/pypy/module/micronumpy/ufuncs.py
--- a/pypy/module/micronumpy/ufuncs.py
+++ b/pypy/module/micronumpy/ufuncs.py
@@ -7,6 +7,7 @@
 from rpython.tool.sourcetools import func_with_new_name
 from pypy.module.micronumpy import boxes, descriptor, loop, constants as NPY
 from pypy.module.micronumpy.base import convert_to_array, W_NDimArray
+from pypy.module.micronumpy.ctors import numpify
 from pypy.module.micronumpy.strides import shape_agreement
 
 
@@ -17,6 +18,13 @@
 def done_if_false(dtype, val):
     return not dtype.itemtype.bool(val)
 
+def _get_dtype(space, w_npyobj):
+    if isinstance(w_npyobj, boxes.W_GenericBox):
+        return w_npyobj.get_dtype(space)
+    else:
+        assert isinstance(w_npyobj, W_NDimArray)
+        return w_npyobj.get_dtype()
+
 
 class W_Ufunc(W_Root):
     _immutable_fields_ = [
@@ -304,8 +312,8 @@
             out = args_w[1]
             if space.is_w(out, space.w_None):
                 out = None
-        w_obj = convert_to_array(space, w_obj)
-        dtype = w_obj.get_dtype()
+        w_obj = numpify(space, w_obj)
+        dtype = _get_dtype(space, w_obj)
         if dtype.is_flexible():
             raise OperationError(space.w_TypeError,
                       space.wrap('Not implemented for this type'))
@@ -315,7 +323,7 @@
             raise oefmt(space.w_TypeError,
                 "ufunc %s not supported for the input type", self.name)
         calc_dtype = find_unaryop_result_dtype(space,
-                                  w_obj.get_dtype(),
+                                  dtype,
                                   promote_to_float=self.promote_to_float,
                                   promote_bools=self.promote_bools)
         if out is not None:
@@ -345,6 +353,7 @@
             else:
                 out.fill(space, w_val)
             return out
+        assert isinstance(w_obj, W_NDimArray)
         shape = shape_agreement(space, w_obj.get_shape(), out,
                                 broadcast_down=False)
         return loop.call1(space, shape, self.func, calc_dtype, res_dtype,
@@ -385,10 +394,10 @@
         else:
             [w_lhs, w_rhs] = args_w
             w_out = None
-        w_lhs = convert_to_array(space, w_lhs)
-        w_rhs = convert_to_array(space, w_rhs)
-        w_ldtype = w_lhs.get_dtype()
-        w_rdtype = w_rhs.get_dtype()
+        w_lhs = numpify(space, w_lhs)
+        w_rhs = numpify(space, w_rhs)
+        w_ldtype = _get_dtype(space, w_lhs)
+        w_rdtype = _get_dtype(space, w_rhs)
         if w_ldtype.is_str() and w_rdtype.is_str() and \
                 self.comparison_func:
             pass
@@ -451,6 +460,12 @@
             else:
                 out = arr
             return out
+        if isinstance(w_lhs, boxes.W_GenericBox):
+            w_lhs = W_NDimArray.from_scalar(space, w_lhs)
+        assert isinstance(w_lhs, W_NDimArray)
+        if isinstance(w_rhs, boxes.W_GenericBox):
+            w_rhs = W_NDimArray.from_scalar(space, w_rhs)
+        assert isinstance(w_rhs, W_NDimArray)
         new_shape = shape_agreement(space, w_lhs.get_shape(), w_rhs)
         new_shape = shape_agreement(space, new_shape, out, broadcast_down=False)
         return loop.call2(space, new_shape, self.func, calc_dtype,
diff --git a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
--- a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
@@ -30,6 +30,7 @@
         """)
 
     def test_array_getitem_accumulate(self):
+        """Check that operations/ufuncs on array items are jitted correctly"""
         def main():
             import _numpypy.multiarray as np
             arr = np.zeros((300, 300))
@@ -43,7 +44,6 @@
         log = self.run(main, [])
         assert log.result == 0
         loop, = log.loops_by_filename(self.filepath)
-        skip('used to pass on 69421-f3e717c94913')
         assert loop.match("""
             i81 = int_lt(i76, 300)
             guard_true(i81, descr=...)


More information about the pypy-commit mailing list