为什么我的“xmap"函数不比内置的“map"快? [英] Why is my 'xmap' function not any faster than built-in 'map'?

查看:63
本文介绍了为什么我的“xmap"函数不比内置的“map"快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

假设我们想在列表中的每个实例上一遍又一遍地调用 A 类的特定方法 m(实际示例:实体.update() 方法在游戏循环中).我们知道这样做的简单方法如下:

 for obj in lst: obj.m()

然而,这种代码让我们睡着了.所以我们想到了这样使用map:

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

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

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

def xmap(func, lst):对于 lst 中的 obj: func(obj)

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

我们的第一个脚本将简单地使用 map 并无用地构建一个我们甚至不需要的列表.

script1.py :

A 类:def m(自我):经过lst = [A() for i in xrange(15)]导入时间开始 = time.time()对于 xrange(1000000) 中的 i:地图(lambda obj:obj.m(),lst)打印 time.time()-start, 'seconds'

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

script2.py

def xmap(func, lst):对于 lst 中的 obj: func(obj)A类:def m(自我):经过lst = [A() for i in xrange(15)]导入时间开始 = time.time()对于 xrange(1000000) 中的 i:xmap(lambda obj: obj.m(), lst)打印 time.time()-start, 'seconds'

我们终于完成了,并且很高兴看到我们的代码会快多少.然而,在相互运行这两个脚本几次之后,结果证明 script2.py 似乎并不比 script1.py 快.实际上,事实证明,script2.py 有时比 script1.py 的运行时间更长.xmap 似乎与 map 花费的时间或多或少相同.

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

C:\dev\py>python script1.py14.7799999714 秒C:\dev\py>python script2.py14.2170000076 秒C:\dev\py>python script1.py12.1800000668 秒C:\dev\py>python script2.py12.5759999752 秒C:\dev\py>python script1.py14.3020000458 秒C:\dev\py>python script2.py14.9490001202 秒C:\dev\py>python script1.py14.6879999638 秒C:\dev\py>python script2.py14.3139998913 秒

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

<预><代码>>>>导入时间>>>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('[无,无,无,无,无,无,无,无,无,无,无,无,无,无,无]')0.7460120889200539

那么为什么我的 xmap 函数似乎没有 map 快?

解决方案

首先,也是最重要的一点:像这样的微优化计时只会让您感到困惑,因为您正在测量诸如函数之类的非常低级别的开销.

不过,在这种情况下,我很确定问题在于您可能从 map/xmap 获得的收益会因为额外的 lambda 而损失.如果您直接使用 A.m 代替,事情看起来会好得多:

<预><代码>>>>%timeit for obj in lst: obj.m()100000 个循环,最好的 3 个:每个循环 2.99 µs>>>%timeit [obj.m() for obj in lst]100000 个循环,最好的 3 个:每个循环 3.5 µs>>>%timeit xmap(lambda obj: obj.m(), lst)100000 个循环,最好的 3 个:每个循环 5.69 µs>>>%timeit xmap(A.m, lst)100000 个循环,最好的 3 个:每个循环 3.32 µs

FWIW,在我看来,你的 xmap 最终会获胜:

<预><代码>>>>lst = [A() for i in xrange(10**3)]>>>%timeit for obj in lst: obj.m()1000 个循环,最好的 3 个:每个循环 198 µs>>>%timeit [obj.m() for obj in lst]1000 个循环,最好的 3 个:每个循环 216 µs>>>%timeit xmap(lambda obj: obj.m(), lst)1000 个循环,最好的 3 个:每个循环 353 µs>>>%timeit xmap(A.m, lst)10000 个循环,最好的 3 个:每个循环 189 µs

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

当您说那种代码 [即简单的 for 循环] 让我们睡着了"时,我同意 - 编写简单的循环意味着您可以更快地完成编程并且可以更早地睡觉.

Suppose there is a class A and that there is a list of instances of class A called lst.

Suppose we want to call a specific method, m, of class A millions and millions of times over and over again on every instance in our list, (practical example: an entity.update() method in a game loop). We know that the easy way to do this is the following:

for obj in lst: obj.m()

However that kind of code puts us to sleep. So we think of using map in the following way:

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

But we run a few time tests on the above line of code and it turns out that it's much much slower than our simple for loop. Sometimes it's even 2 times as slow. Then we think to ourselves, "hmm maybe its slower because map constructs a list of return values for all the function calls and returns that list".

Suppose we take inspiration from the lazy and memory-efficient built-in function called xrange. For the most part, we think to ourselves, it's a cooler version of range. So we define a function called xmap that simply applies a function over a list of objects without constructing a list of return values and returning it. The implementation is the following:

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

Pretty cool because this function just executes for loop above, only it allows us to stay fancy and send in our lambda functions. We think this is the perfect compromise. But we are meticulous and careful so we decide to make 2 scripts to test the speed of our code to see if we actually made it any faster than map.

Our first script will simply use map and uselessly construct a list that we don't even need.

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'

Our second script will use xmap and we believe that it will be faster because it won't have to construct a list of 15 return values 1,000,000 times and return it.

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'

Finally we are done and somewhat excited to see how much faster our code will be. However after running both scripts a few times against each other, it turns out that script2.py does not seem any faster than script1.py. Actually, as it turns out, script2.py sometimes takes even longer to run than script1.py. xmap seems to take, more or less, the same amount of time as map.

Why did I get these results?

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

I thought that I would have at least have optimized something from map because I wasn't constructing a list of return values, but my code doesn't seem to be any faster. I know for a fact that list construction takes some time because I've done the following:

>>> 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

So why does my xmap function not seem to be any faster than map?

解决方案

First, and most importantly: timing micro-optimizations like this is only going to confuse you, because you're measuring very low-level overheads on things like functions.

In this case, though, I'm pretty sure the issue is that the gains you might get from map/xmap you're losing because of the extra lambda. If you use A.m directly instead, things look much better:

>>> %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, it looks to me like eventually your xmap can win:

>>> 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

but I wouldn't take these numbers too seriously either.

When you say "that kind of code [i.e. simple for loops] puts us to sleep", I agree-- writing simple loops means that you're done programming much faster and can get to bed earlier.

这篇关于为什么我的“xmap"函数不比内置的“map"快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆