[pypy-commit] pypy default: (fijal, alex) Move map() from interp-level to app-level. fijal benchmarked this as between a 2x speedup and 10% slowdown. Apparently the JIT is kind of good.

alex_gaynor noreply at buildbot.pypy.org
Tue Oct 11 23:15:38 CEST 2011


Author: Alex Gaynor <alex.gaynor at gmail.com>
Branch: 
Changeset: r47944:cc47e5aeff35
Date: 2011-10-11 17:15 -0400
http://bitbucket.org/pypy/pypy/changeset/cc47e5aeff35/

Log:	(fijal, alex) Move map() from interp-level to app-level. fijal
	benchmarked this as between a 2x speedup and 10% slowdown.
	Apparently the JIT is kind of good.

diff --git a/pypy/module/__builtin__/__init__.py b/pypy/module/__builtin__/__init__.py
--- a/pypy/module/__builtin__/__init__.py
+++ b/pypy/module/__builtin__/__init__.py
@@ -20,6 +20,7 @@
         'any'           : 'app_functional.any',
         'all'           : 'app_functional.all',
         'sum'           : 'app_functional.sum',
+        'map'           : 'app_functional.map',
         'vars'          : 'app_inspect.vars',
         'dir'           : 'app_inspect.dir',
 
@@ -86,7 +87,6 @@
         'enumerate'     : 'functional.W_Enumerate',
         'min'           : 'functional.min',
         'max'           : 'functional.max',
-        'map'           : 'functional.map',
         'zip'           : 'functional.zip',
         'reduce'        : 'functional.reduce',
         'reversed'      : 'functional.reversed',
diff --git a/pypy/module/__builtin__/app_functional.py b/pypy/module/__builtin__/app_functional.py
--- a/pypy/module/__builtin__/app_functional.py
+++ b/pypy/module/__builtin__/app_functional.py
@@ -48,4 +48,53 @@
         # Very intentionally *not* +=, that would have different semantics if
         # start was a mutable type, such as a list
         last = last + x
-    return last
\ No newline at end of file
+    return last
+
+def map(func, *collections):
+    """map(function, sequence[, sequence, ...]) -> list
+
+Return a list of the results of applying the function to the items of
+the argument sequence(s).  If more than one sequence is given, the
+function is called with an argument list consisting of the corresponding
+item of each sequence, substituting None for missing values when not all
+sequences have the same length.  If the function is None, return a list of
+the items of the sequence (or a list of tuples if more than one sequence)."""
+    if not collections:
+        raise TypeError("map() requires at least two arguments")
+    num_collections = len(collections)
+    none_func = func is None
+    if num_collections == 1:
+        if none_func:
+            return list(collections[0])
+        else:
+            # Special case for the really common case of a single collection,
+            # this can be eliminated if we could unroll that loop that creates
+            # `args` based on whether or not len(collections) was constant
+            result = []
+            for item in collections[0]:
+                result.append(func(item))
+            return result
+    result = []
+    # Pair of (iterator, has_finished)
+    iterators = [(iter(seq), False) for seq in collections]
+    while True:
+        cont = False
+        args = []
+        for idx, (iterator, has_finished) in enumerate(iterators):
+            val = None
+            if not has_finished:
+                try:
+                    val = next(iterator)
+                except StopIteration:
+                    iterators[idx] = (None, True)
+                else:
+                    cont = True
+            args.append(val)
+        args = tuple(args)
+        if cont:
+            if none_func:
+                result.append(args)
+            else:
+                result.append(func(*args))
+        else:
+            return result
diff --git a/pypy/module/__builtin__/functional.py b/pypy/module/__builtin__/functional.py
--- a/pypy/module/__builtin__/functional.py
+++ b/pypy/module/__builtin__/functional.py
@@ -201,118 +201,6 @@
     """
     return min_max(space, __args__, "min")
 
- at unwrap_spec(collections_w="args_w")
-def map(space, w_func, collections_w):
-    """does 3 separate things, hence this enormous docstring.
-       1.  if function is None, return a list of tuples, each with one
-           item from each collection.  If the collections have different
-           lengths,  shorter ones are padded with None.
-
-       2.  if function is not None, and there is only one collection,
-           apply function to every item in the collection and return a
-           list of the results.
-
-       3.  if function is not None, and there are several collections,
-           repeatedly call the function with one argument from each
-           collection.  If the collections have different lengths,
-           shorter ones are padded with None
-    """
-    if not collections_w:
-        msg = "map() requires at least two arguments"
-        raise OperationError(space.w_TypeError, space.wrap(msg))
-    none_func = space.is_w(w_func, space.w_None)
-    if len(collections_w) == 1:
-        w_collection = collections_w[0]
-        if none_func:
-            result_w = space.unpackiterable(w_collection)
-        else:
-            result_w = map_single_collection(space, w_func, w_collection)
-    else:
-        result_w = map_multiple_collections(space, w_func, collections_w,
-                                            none_func)
-    return space.newlist(result_w)
-
-def map_single_collection(space, w_func, w_collection):
-    """Special case for 'map(func, coll)', where 'func' is not None and there
-    is only one 'coll' argument."""
-    w_iter = space.iter(w_collection)
-    # xxx special hacks for speed
-    from pypy.interpreter import function, pycode
-    if isinstance(w_func, function.Function):
-        # xxx compatibility issue: what if func_code is modified in the
-        # middle of running map()??  That's far too obscure for me to care...
-        code = w_func.getcode()
-        fast_natural_arity = code.fast_natural_arity
-        if fast_natural_arity == (1|pycode.PyCode.FLATPYCALL):
-            assert isinstance(code, pycode.PyCode)
-            return map_single_user_function(code, w_func, w_iter)
-    # /xxx end of special hacks
-    return map_single_other_callable(space, w_func, w_iter)
-
-def map_single_other_callable(space, w_func, w_iter):
-    result_w = []
-    while True:
-        try:
-            w_item = space.next(w_iter)
-        except OperationError, e:
-            if not e.match(space, space.w_StopIteration):
-                raise
-            break
-        result_w.append(space.call_function(w_func, w_item))
-    return result_w
-map_single_other_callable._dont_inline_ = True
-
-from pypy.rlib.jit import JitDriver
-mapjitdriver = JitDriver(greens = ['code'],
-                         reds = ['w_func', 'w_iter', 'result_w'])
-def map_single_user_function(code, w_func, w_iter):
-    result_w = []
-    while True:
-        mapjitdriver.can_enter_jit(code=code, w_func=w_func,
-                                   w_iter=w_iter, result_w=result_w)
-        mapjitdriver.jit_merge_point(code=code, w_func=w_func,
-                                     w_iter=w_iter, result_w=result_w)
-        space = w_func.space
-        try:
-            w_item = space.next(w_iter)
-        except OperationError, e:
-            if not e.match(space, space.w_StopIteration):
-                raise
-            break
-        new_frame = space.createframe(code, w_func.w_func_globals,
-                                      w_func)
-        new_frame.locals_stack_w[0] = w_item
-        w_res = new_frame.run()
-        result_w.append(w_res)
-    return result_w
-
-def map_multiple_collections(space, w_func, collections_w, none_func):
-    result_w = []
-    iterators_w = [space.iter(w_seq) for w_seq in collections_w]
-    num_iterators = len(iterators_w)
-    while True:
-        cont = False
-        args_w = [space.w_None] * num_iterators
-        for i in range(num_iterators):
-            if iterators_w[i] is not None:
-                try:
-                    args_w[i] = space.next(iterators_w[i])
-                except OperationError, e:
-                    if not e.match(space, space.w_StopIteration):
-                        raise
-                    iterators_w[i] = None
-                else:
-                    cont = True
-        if not cont:
-            break
-        w_args = space.newtuple(args_w)
-        if none_func:
-            w_res = w_args
-        else:
-            w_res = space.call(w_func, w_args)
-        result_w.append(w_res)
-    return result_w
-
 @unwrap_spec(sequences_w="args_w")
 def zip(space, sequences_w):
     """Return a list of tuples, where the nth tuple contains every nth item of
diff --git a/pypy/module/__builtin__/test/test_functional.py b/pypy/module/__builtin__/test/test_functional.py
--- a/pypy/module/__builtin__/test/test_functional.py
+++ b/pypy/module/__builtin__/test/test_functional.py
@@ -147,7 +147,7 @@
        assert list(xrange(A())) == [0, 1, 2, 3, 4]
        assert list(xrange(0, A())) == [0, 1, 2, 3, 4]
        assert list(xrange(0, 10, A())) == [0, 5]
- 
+
    def test_xrange_float(self):
       assert list(xrange(0.1, 2.0, 1.1)) == [0, 1]
 


More information about the pypy-commit mailing list