为什么我的'xmap'功能不比内置'map'快?

时间:2013-09-25 00:52:58

标签: python performance python-2.7

假设有一个A类,并且有一个名为lst的A类实例列表。

假设我们想要在列表中的每个实例上反复调用类A的特定方法m数百万次,(实际示例:a entity.update()方法游戏循环)。我们知道这样做的简单方法如下:

for obj in lst: obj.m()

然而,那种代码让我们入睡。因此,我们考虑以下列方式使用map

map(lambda obj: obj.m(), lst)

但是我们对上面的代码行进行了一些时间测试,结果发现它比我们简单的for循环慢得多。有时它甚至慢2倍。然后我们对自己说,“嗯可能它的速度慢,因为map构造了所有函数调用的返回值列表并返回该列表”。

假设我们从名为xrange的惰性且内存有效的内置函数中获取灵感。在大多数情况下,我们认为它是range的较酷版本。因此,我们定义了一个名为xmap的函数,它只是将函数应用于对象列表,而不用构造返回值列表并返回它。实施如下:

def xmap(func, lst):
    for obj in lst: func(obj)

非常酷,因为这个函数只是在上面执行for循环,只有它允许我们保持幻想并发送我们的lambda函数。我们认为这是完美的妥协。但是我们一丝不苟和谨慎,所以我们决定制作2个脚本来测试代码的速度,看看我们是否实际上比map更快。

我们的第一个脚本只会使用map而无用地构建一个我们甚至不需要的列表。

script1.py

class A:
    def m(self):
        pass

lst = [A() for i in xrange(15)]

import time
start = time.time()

for i in xrange(1000000):
    map(lambda obj: obj.m(), lst)

print time.time()-start, 'seconds'

我们的第二个脚本将使用xmap,我们相信它会更快,因为它不必构建一个包含15个返回值1,000,000次的列表并将其返回。

script2.py

def xmap(func, lst):
    for obj in lst: func(obj)

class A:
    def m(self):
        pass

lst = [A() for i in xrange(15)]

import time
start = time.time()

for i in xrange(1000000):
    xmap(lambda obj: obj.m(), lst)

print time.time()-start, 'seconds'

最后,我们已经完成并且有点兴奋地看到我们的代码会更快。但是,在将这两个脚本相互运行几次之后,事实证明script2.py似乎没有script1.py快。实际上,事实证明,script2.py有时需要比script1.py更长的时间才能运行。 xmap似乎或多或少地花费的时间与map相同。

为什么我会得到这些结果?

C:\dev\py>python script1.py
14.7799999714 seconds

C:\dev\py>python script2.py
14.2170000076 seconds

C:\dev\py>python script1.py
12.1800000668 seconds

C:\dev\py>python script2.py
12.5759999752 seconds

C:\dev\py>python script1.py
14.3020000458 seconds

C:\dev\py>python script2.py
14.9490001202 seconds

C:\dev\py>python script1.py
14.6879999638 seconds

C:\dev\py>python script2.py
14.3139998913 seconds

我认为我至少会从map优化某些,因为我没有构建返回值列表,但我的代码似乎没有更快。我知道列表构建需要一些时间,因为我已经完成了以下操作:

>>> import timeit
>>> timeit.timeit('[]')
0.1297345953932106
>>> timeit.timeit('[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]')
0.6807682686160632
>>> timeit.timeit('[None,None,None,None,None,None,None,None,None,None,None,None,
None,None,None]')
0.7460120889200539

那么为什么我的xmap功能似乎不比map快?

2 个答案:

答案 0 :(得分:7)

首先,也是最重要的一点:像这样的定时微优化只会让你感到困惑,因为你在测量功能等方面的开销非常低。

在这种情况下,我很确定问题是,由于额外的map/xmap,您可能会从lambda中获得收益。如果您直接使用A.m,事情看起来会更好:

>>> %timeit for obj in lst: obj.m()
100000 loops, best of 3: 2.99 µs per loop
>>> %timeit [obj.m() for obj in lst]
100000 loops, best of 3: 3.5 µs per loop
>>> %timeit xmap(lambda obj: obj.m(), lst)
100000 loops, best of 3: 5.69 µs per loop
>>> %timeit xmap(A.m, lst)
100000 loops, best of 3: 3.32 µs per loop

FWIW,我觉得最终你的xmap可以获胜:

>>> lst = [A() for i in xrange(10**3)]
>>> %timeit for obj in lst: obj.m()
1000 loops, best of 3: 198 µs per loop
>>> %timeit [obj.m() for obj in lst]
1000 loops, best of 3: 216 µs per loop
>>> %timeit xmap(lambda obj: obj.m(), lst)
1000 loops, best of 3: 353 µs per loop
>>> %timeit xmap(A.m, lst)
10000 loops, best of 3: 189 µs per loop

但我也不会认真对待这些数字。

当你说“那种代码[即简单的循环]让我们入睡”时,我同意 - 编写简单的循环意味着你编程速度更快,可以提早上床。

答案 1 :(得分:3)

map在C中实现,并在C-loop-land中工作。你在Python-loop-land中运行。