为什么我的(新手)代码运行如此缓慢?

9
我正在学习Python,看到了一个我之前见过的模型的仿真示例。其中一个函数看起来不必要地冗长,所以我认为尝试让它更有效率是个好练习。我的尝试虽然需要更少的代码,但速度大约是原函数的1/60。是的,我把它搞糟了60倍。
我的问题是,我哪里错了?我已经尝试计时函数的各个部分,但没有看出瓶颈在哪里。
这是原始函数。它是用于模拟人们生活在网格上的模型,并且他们的幸福感取决于他们是否与大多数邻居相同种族(这是谢林隔离模型)。因此,我们为每个人提供一个x,y坐标,并通过检查每个邻居的种族来确定他们的幸福感。
def is_unhappy(self, x, y):

    race = self.agents[(x,y)]
    count_similar = 0
    count_different = 0

    if x > 0 and y > 0 and (x-1, y-1) not in self.empty_houses:
        if self.agents[(x-1, y-1)] == race:
            count_similar += 1
        else:
            count_different += 1
    if y > 0 and (x,y-1) not in self.empty_houses:
        if self.agents[(x,y-1)] == race:
            count_similar += 1
        else:
            count_different += 1
    if x < (self.width-1) and y > 0 and (x+1,y-1) not in self.empty_houses:
        if self.agents[(x+1,y-1)] == race:
            count_similar += 1
        else:
            count_different += 1
    if x > 0 and (x-1,y) not in self.empty_houses:
        if self.agents[(x-1,y)] == race:
            count_similar += 1
        else:
            count_different += 1        
    if x < (self.width-1) and (x+1,y) not in self.empty_houses:
        if self.agents[(x+1,y)] == race:
            count_similar += 1
        else:
            count_different += 1
    if x > 0 and y < (self.height-1) and (x-1,y+1) not in self.empty_houses:
        if self.agents[(x-1,y+1)] == race:
            count_similar += 1
        else:
            count_different += 1        
    if x > 0 and y < (self.height-1) and (x,y+1) not in self.empty_houses:
        if self.agents[(x,y+1)] == race:
            count_similar += 1
        else:
            count_different += 1        
    if x < (self.width-1) and y < (self.height-1) and (x+1,y+1) not in self.empty_houses:
        if self.agents[(x+1,y+1)] == race:
            count_similar += 1
        else:
            count_different += 1

    if (count_similar+count_different) == 0:
        return False
    else:
        return float(count_similar)/(count_similar+count_different) < self.similarity_threshold 

这是我的代码,正如我所说的那样,速度非常慢。我想通过创建一个“偏移量”列表来避免在上面编写所有的if语句,以便将其添加到每个人的坐标中,以确定可能邻居的位置,检查是否为有效位置,然后检查邻居的种族。

def is_unhappy2(self, x, y):
    thisRace = self.agents[(x,y)]
    count_same = 0
    count_other = 0

    for xo, yo in list(itertools.product([-1,0,1],[-1,0,1])):
        if xo==0 and yo==0:
            # do nothing for case of no offset
            next
        else:
            # check if there's a neighbor at the offset of (xo, yo)
            neighbor = tuple(np.add( (x,y), (xo,yo) ))
            if neighbor in self.agents.keys():
                if self.agents[neighbor] == thisRace:
                    count_same += 1
                else:
                    count_other += 1
    if count_same+count_other == 0:
        return False
    else:
        return float(count_same) / (count_same + count_other) < self.similarity threshold

(The rest of the code that creates the class is 在该网站上,这里是示例代码的来源。)
(以下是时序结果:)
%timeit s.is_unhappy2(49,42)
100 loops, best of 3: 5.99 ms per loop

%timeit s.is_unhappy(49,42)
10000 loops, best of 3: 103 µs per loop

我希望有Python知识的人能立即看出我的错误,而不必深入了解其余代码的细节。你能看出为什么我的代码比原始代码差那么多吗?

2
你的慢吗?每次5.99毫秒,100次循环总计599毫秒,每次103微秒,10000次循环总计1030毫秒。也许我的时间结果有点混乱? - ryan
1
@Ryan:每个循环的时间才是关键,而不是运行基准测试所花费的总时间。迭代次数只是给你一个置信区间的概念。 - John Kugelman
4
根据目前的状况,这个问题在 Stack Overflow 上似乎是被认可的,有 9 个赞同票并且还没有任何关闭投票。有些问题可能在两个网站上都是相关的。由于该问题包含了一些关于理解其他代码的信息,因此它并不是一个非常好的代码审查问题。 - Simon Forsberg
5
好的,那么请使用实际的关闭原因。在别的地方谈论话题并不意味着它在这里不合适。说它应该放到别处不是关闭的原因,TylerH 先生。 - RubberDuck
@RubberDuck 很抱歉,即使有新信息,我们也无法更改我们的简历原因,所以我无法做到这一点。 - TylerH
你可以依靠人们从代码中直观地判断“问题在Foo”。他们可能是正确的。他们正确的概率是某个数字,也许高达20%。你能依赖这个吗?找出需要时间的方法不是直观地判断,而是使用这种方法,它永远不会错。 - Mike Dunlavey
2个回答

8
不要使用np.add,只需使用neighbor = (x+xo, y+yo)。这应该会使它快得多(在我的小测试中快了10倍)。
你也可以...
  • 询问if neighbor in self.agents:而不用.keys()
  • 省略list
  • 检查xo或yo并且不要有空的if块
  • 避免双重查找self-agents中的邻居
for xo, yo in itertools.product([-1,0,1],[-1,0,1]):
    if xo or yo:
        neighbor = self.agents.get((x+xo, y+yo))
        if neighbor is not None:
            if neighbor == thisRace:
                count_same += 1
            else:
                count_other += 1

而且您可以添加

self.neighbor_deltas = tuple(set(itertools.product([-1,0,1],[-1,0,1])) - {(0, 0)})

将这些预先计算的增量传递给类初始化程序,然后您的函数只需使用这些预先计算的增量即可:

for xo, yo in self.neighbor_deltas:
    neighbor = self.agents.get((x+xo, y+yo))
    if neighbor is not None:
        if neighbor == thisRace:
            count_same += 1
        else:
            count_other += 1

顺便恭喜您决定改进那个作者极其重复的代码。


3
罪魁祸首看起来是这一行代码:
neighbor = tuple(np.add( (x,y), (xo,yo) ))

将其更改为以下内容可以大大提高速度:
neighbor = (x + xo, y + yo)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接