假设有一个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
快?
答案 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中运行。