Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r72312:93a8f5aeb3bc
Date: 2014-07-02 11:18 +0200
http://bitbucket.org/pypy/pypy/changeset/93a8f5aeb3bc/

Log:    Issue #1783

        Improve array.extend(x) by not requiring an intermediate list in
        case there isn't one, e.g. if x is a generator or iterator.

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -967,6 +967,13 @@
         """
         return self.unpackiterable(w_iterable, expected_length)
 
+    def listview_no_unpack(self, w_iterable):
+        """ Same as listview() if cheap.  If 'w_iterable' is something like
+        a generator, for example, then return None instead.
+        May return None anyway.
+        """
+        return None
+
     def listview_bytes(self, w_list):
         """ Return a list of unwrapped strings out of a list of strings. If the
         argument is not a list or does not contain only strings, return None.
diff --git a/pypy/module/array/interp_array.py 
b/pypy/module/array/interp_array.py
--- a/pypy/module/array/interp_array.py
+++ b/pypy/module/array/interp_array.py
@@ -15,6 +15,7 @@
     interp2app, interpindirect2app, unwrap_spec)
 from pypy.interpreter.typedef import (
     GetSetProperty, TypeDef, make_weakref_descr)
+from pypy.interpreter.generator import GeneratorIterator
 from pypy.module._file.interp_file import W_File
 from pypy.objspace.std.floatobject import W_FloatObject
 
@@ -630,6 +631,10 @@
 def make_array(mytype):
     W_ArrayBase = globals()['W_ArrayBase']
 
+    unpack_driver = jit.JitDriver(name='unpack_array',
+                                  greens=['tp'],
+                                  reds=['self', 'w_iterator'])
+
     class W_Array(W_ArrayBase):
         itemsize = mytype.bytes
         typecode = mytype.typecode
@@ -739,31 +744,64 @@
             space = self.space
             oldlen = self.len
             newlen = oldlen
-            try:
-                # optimized case for arrays of integers or floats
-                if mytype.unwrap == 'int_w':
-                    lst = space.listview_int(w_seq)
-                elif mytype.unwrap == 'float_w':
-                    lst = space.listview_float(w_seq)
-                else:
-                    lst = None
-                if lst is not None:
-                    self.setlen(oldlen + len(lst))
+
+            # optimized case for arrays of integers or floats
+            if mytype.unwrap == 'int_w':
+                lst = space.listview_int(w_seq)
+            elif mytype.unwrap == 'float_w':
+                lst = space.listview_float(w_seq)
+            else:
+                lst = None
+            if lst is not None:
+                self.setlen(oldlen + len(lst))
+                try:
                     buf = self.buffer
                     for num in lst:
                         buf[newlen] = self.item_from_int_or_float(num)
                         newlen += 1
-                    return
-                #
-                # this is the general case
-                lst_w = space.listview(w_seq)
+                except OperationError:
+                    self.setlen(newlen)
+                    raise
+                return
+
+            # this is the common case: w_seq is a list or a tuple
+            lst_w = space.listview_no_unpack(w_seq)
+            if lst_w is not None:
                 self.setlen(oldlen + len(lst_w))
-                for w_num in lst_w:
-                    self.buffer[newlen] = self.item_w(w_num)
-                    newlen += 1
-            finally:
-                if self.len != newlen:
-                    self.setlen(newlen)
+                buf = self.buffer
+                try:
+                    for w_num in lst_w:
+                        # note: self.item_w() might invoke arbitrary code.
+                        # In case it resizes the same array, then strange
+                        # things may happen, but as we don't reload 'buf'
+                        # we know that one is big enough for all items
+                        # (so at least we avoid crashes)
+                        buf[newlen] = self.item_w(w_num)
+                        newlen += 1
+                except OperationError:
+                    if buf == self.buffer:
+                        self.setlen(newlen)
+                    raise
+                return
+
+            self._fromiterable(w_seq)
+
+        def _fromiterable(self, w_seq):
+            # a more careful case if w_seq happens to be a very large
+            # iterable: don't copy the items into some intermediate list
+            w_iterator = self.space.iter(w_seq)
+            tp = self.space.type(w_iterator)
+            while True:
+                unpack_driver.jit_merge_point(tp=tp, self=self,
+                                              w_iterator=w_iterator)
+                space = self.space
+                try:
+                    w_item = space.next(w_iterator)
+                except OperationError, e:
+                    if not e.match(space, space.w_StopIteration):
+                        raise
+                    break  # done
+                self.descr_append(space, w_item)
 
         def extend(self, w_iterable, accept_different_array=False):
             space = self.space
@@ -806,8 +844,9 @@
 
         def descr_append(self, space, w_x):
             x = self.item_w(w_x)
-            self.setlen(self.len + 1)
-            self.buffer[self.len - 1] = x
+            index = self.len
+            self.setlen(index + 1)
+            self.buffer[index] = x
 
         # List interface
         def descr_count(self, space, w_val):
diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -421,14 +421,19 @@
         assert expected_length >= 0
         return self.fixedview(w_obj, expected_length, unroll=True)
 
+    def listview_no_unpack(self, w_obj):
+        if type(w_obj) is W_ListObject:
+            return w_obj.getitems()
+        elif isinstance(w_obj, W_AbstractTupleObject) and 
self._uses_tuple_iter(w_obj):
+            return w_obj.getitems_copy()
+        elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj):
+            return w_obj.getitems()
+        else:
+            return None
+
     def listview(self, w_obj, expected_length=-1):
-        if type(w_obj) is W_ListObject:
-            t = w_obj.getitems()
-        elif isinstance(w_obj, W_AbstractTupleObject) and 
self._uses_tuple_iter(w_obj):
-            t = w_obj.getitems_copy()
-        elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj):
-            t = w_obj.getitems()
-        else:
+        t = self.listview_no_unpack(w_obj)
+        if t is None:
             return ObjSpace.unpackiterable(self, w_obj, expected_length)
         if expected_length != -1 and len(t) != expected_length:
             raise self._wrap_expected_length(expected_length, len(t))
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to