Author: Armin Rigo <[email protected]>
Branch:
Changeset: r78715:4012e89fb82d
Date: 2015-07-29 23:28 +0200
http://bitbucket.org/pypy/pypy/changeset/4012e89fb82d/
Log: Issue #2100: massively improve the performance of map() with more
than one sequence argument
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
@@ -53,6 +53,33 @@
last = last + x
return last
+
+class _Cons(object):
+ def __init__(self, prev, iter):
+ self.prev = prev
+ self.iter = iter
+
+ def fetch(self):
+ # recursive, loop-less version of the algorithm: works best for a
+ # fixed number of "collections" in the call to map(func, *collections)
+ prev = self.prev
+ if prev is None:
+ args1 = ()
+ stop = True
+ else:
+ args1, stop = prev.fetch()
+ iter = self.iter
+ if iter is None:
+ val = None
+ else:
+ try:
+ val = next(iter)
+ stop = False
+ except StopIteration:
+ self.iter = None
+ val = None
+ return args1 + (val,), stop
+
def map(func, *collections):
"""map(function, sequence[, sequence, ...]) -> list
@@ -69,45 +96,30 @@
if num_collections == 1:
if none_func:
return list(collections[0])
- # 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
+ # Special case for the really common case of a single collection
seq = collections[0]
with _ManagedNewlistHint(operator._length_hint(seq, 0)) as result:
for item in seq:
result.append(func(item))
return result
- # Gather the iterators (pair of (iter, has_finished)) and guess the
+ # Gather the iterators into _Cons objects and guess the
# result length (the max of the input lengths)
- iterators = []
+ c = None
max_hint = 0
for seq in collections:
- iterators.append((iter(seq), False))
+ c = _Cons(c, iter(seq))
max_hint = max(max_hint, operator._length_hint(seq, 0))
with _ManagedNewlistHint(max_hint) as result:
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))
+ args, stop = c.fetch()
+ if stop:
+ return result
+ if none_func:
+ result.append(args)
else:
- return result
+ result.append(func(*args))
class _ManagedNewlistHint(object):
""" Context manager returning a newlist_hint upon entry.
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
@@ -57,6 +57,11 @@
b = []
assert map(lambda x, y: x, a, b) == a
+ def test_map_second_item(self):
+ a = []
+ b = [1, 2, 3, 4, 5]
+ assert map(lambda x, y: y, a, b) == b
+
def test_map_iterables(self):
class A(object):
def __init__(self, n):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit