为什么我的代码在pypy中比默认的python解释器慢?

时间:2016-03-21 11:09:15

标签: python performance pypy

鉴于一些玩家n,我需要找到H,所有元组的列表,其中每个元组是联盟的组合(玩家,例如(1,2,3))球员1,2和3的联盟。((1,2,3),(4,5),(6,))是一个尊重这个规则的联盟 - 也是元组的组合:每个球员出现只有一次(即只有一个联盟)。 附:联盟的每个组合在代码中称为布局。

一开始我写了一个片段,其中我计算了所有联盟的所有组合,并且对于每个组合我检查了规则。问题是,对于5-6名球员来说,联盟组合的数量已经很大,以至于我的计算机变得很糟糕。 为了避免计算的大部分(所有可能的组合,循环和ifs),我写了以下内容(我测试了它,它等同于前面的代码片段):

from itertools  import combinations, combinations_with_replacement, product, permutations

players = range(1,n+1)
coalitions = [[coal for coal in list(combinations(players,length))] for length in players]

H = [tuple(coalitions[0]),(coalitions[-1][0],)]
combs = [comb for length in xrange(2,n) for comb in combinations_with_replacement(players,length) if sum(comb) == n]
perms = list(permutations(players))
layouts = set(frozenset(frozenset(perm[i:i+x]) for (i,x) in zip([0]+[sum(comb[:y]) for y in xrange(1,len(comb))],comb)) for comb in combs for perm in perms)
H.extend(tuple(tuple(tuple(coal) for coal in layout) for layout in layouts))
print H

解释:说n = 3

首先,我创建所有可能的联盟:

coalitions = [[(1,),(2,),(3,)],[(1,2),(1,3),(2,3)],[(1,2,3)]]

然后我用明显的组合初始化H:他自己的联盟​​中的每个球员和最大联盟中的每个球员。

H = [((1,),(2,),(3,)),((1,2,3),)]

然后我计算所有可能的布局形式:

combs = [(1,2)]   #(1,2) represents a layout in which there is 
                  #one 1-player coalition and one 2-player coalition.

我计算排列(perms)。 最后,对于每个烫发和每个梳子,我计算不同的可能布局。我set结果(layouts)以删除重复项并添加到H.

H = [((1,),(2,),(3,)),((1,2,3),),((1,2),(3,)),((1,3),(2,)),((2,3),(1,))]

以下是比较:

python script.py

  • 4:0.000520944595337秒
  • 5:0.0038321018219秒
  • 6:0.0408189296722秒
  • 7:0.431486845016秒
  • 8:6.05224680901秒
  • 9:76.4520540237秒

pypy script.py

  • 4:0.00342392921448秒
  • 5:0.0668039321899秒
  • 6:0.311077833176秒
  • 7:1.13124799728秒
  • 8:11.5973010063秒
  • 9:去了phut

为什么pypy会变慢?我应该改变什么?

2 个答案:

答案 0 :(得分:3)

首先,我想指出你正在研究Bell numbers,这可能会在你完成生成所有子集之后简化你工作的下一部分。例如,很容易知道每个Bell组的大小; OEIS已经the sequence of Bell numbers

我亲手编写循环以生成贝尔集;这是我的代码:

cache = {0: (), 1: ((set([1]),),)}

def bell(x):
    # Change these lines to alter memoization.
    if x in cache:
        return cache[x]
    previous = bell(x - 1)
    new = []
    for sets in previous:
        r = []
        for mark in range(len(sets)):
            l = [s | set([x]) if i == mark else s for i, s in enumerate(sets)]
            r.append(tuple(l))
        new.extend(r)
        new.append(sets + (set([x]),))
    cache[x] = tuple(new)
    return new

为了实际目的,我在这里包含了一些备忘录。但是,通过注释掉一些代码并编写其他代码,您可以获得以下未记忆的版本,我将其用于基准测试:

def bell(x):
    if x == 0:
        return ()
    if x == 1:
        return ((set([1]),),)
    previous = bell(x - 1)
    new = []
    for sets in previous:
        r = []
        for mark in range(len(sets)):
            l = [s | set([x]) if i == mark else s for i, s in enumerate(sets)]
            r.append(tuple(l))
        new.extend(r)
        new.append(sets + (set([x]),))
    cache[x] = tuple(new)
    return new

我的数据是基于几年前的Thinkpad,我完成了大部分工作。大多数较小的案例都太快而无法可靠地测量(前几个案例的每个试验甚至没有一毫秒),因此我的基准测试正在测试bell(9)bell(11)

CPython 2.7.11的基准测试,使用标准timeit模块:

$ python -mtimeit -s 'from derp import bell' 'bell(9)'
10 loops, best of 3: 31.5 msec per loop
$ python -mtimeit -s 'from derp import bell' 'bell(10)'
10 loops, best of 3: 176 msec per loop
$ python -mtimeit -s 'from derp import bell' 'bell(11)'
10 loops, best of 3: 1.07 sec per loop

在PyPy 4.0.1上,也使用timeit

$ pypy -mtimeit -s 'from derp import bell' 'bell(9)'
100 loops, best of 3: 14.3 msec per loop
$ pypy -mtimeit -s 'from derp import bell' 'bell(10)'
10 loops, best of 3: 90.8 msec per loop
$ pypy -mtimeit -s 'from derp import bell' 'bell(11)'
10 loops, best of 3: 675 msec per loop

所以,我得出的结论是itertools当你试图在它的预期习语之外使用它时不是很快。贝尔数字是组合有趣的,但它们自然不会出现在我能找到的itertools小部件的任何简单组合中。

响应您对如何加快速度的原始查询:只需打开代码即可。希望这有帮助!

~C。

答案 1 :(得分:1)

这是itertools.product上的Pypy问题。

https://bitbucket.org/pypy/pypy/issues/1677/itertoolsproduct-slower-than-nested-fors

  

请注意,我们的目标是确保itertools的速度不会大于   简单的Python,但我们并不真正关心如何快速(或   更快)作为普通的Python。只要它没有大幅减慢,它就没问题了。 (在   至少我不同意你关于a)或b)是否更容易阅读: - )

如果不详细研究您的代码,它似乎会大量使用itertools组合,排列和产品功能。在常规CPython中,这些是用编译的C代码编写的,目的是使它们快速。 Pypy没有实现C代码,因此这些函数速度较慢并不令人惊讶。