python函数因没有明显原因而放慢速度

时间:2009-11-12 16:26:16

标签: python

我有一个python函数定义如下,我用它从list1中删除已经在list2中的项目。我在Windows XP上使用python 2.6.2

def compareLists(list1, list2):
    curIndex = 0
    while curIndex < len(list1):
        if list1[curIndex] in list2:
            list1.pop(curIndex)
        else:
            curIndex += 1

这里,list1和list2是列表列表

list1 = [ ['a', 11221, '2232'], ['b', 1321, '22342'] .. ]

# list2 has a similar format.

我尝试了这个函数,list1有38,000个元素,list2有150,000个元素。如果我输入print语句来打印当前迭代,我发现每次迭代时函数都会变慢。首先,它在一秒钟内处理大约1000个或更多的物品,然后在一段时间后它减少到大约20-50秒。为什么会发生这种情况?

编辑:对于我的数据,curIndex保持为0或非常接近0,因此list1上的弹出操作几乎总是在第一个项目上。

如果可能的话,有人也可以提出一种更好的方法来以不同的方式做同样的事情吗?

6 个答案:

答案 0 :(得分:12)

尝试使用更加pythonic的过滤方法,例如

[x for x in list1 if x not in set(list2)]

将两个列表转换为集合都是unnessescary,并且对于大量数据来说非常慢并且内存很耗力。

由于您的数据是列表列表,因此您需要执行某些操作才能对其进行哈希处理。 试试

list2_set = set([tuple(x) for x in list2])
diff = [x for x in list1 if tuple(x) not in list2_set]

我使用以下测试数据测试了您的原始功能和我的方法:

list1 = [[x+1, x*2] for x in range(38000)]
list2 = [[x+1, x*2] for x in range(10000, 160000)]

计时 - 不科学,但仍然:

 #Original function
 real    2m16.780s
 user    2m16.744s
 sys     0m0.017s

 #My function
 real    0m0.433s
 user    0m0.423s
 sys     0m0.007s

答案 1 :(得分:3)

有两个问题导致您的算法扩展性不佳:

  1. x in list是O(n)操作。
  2. pop(n)其中n位于数组的中间是O(n)操作。
  3. 这两种情况都会导致大量数据的O(n ^ 2)扩展得很差。 gnud的实现可能是最好的解决方案,因为它可以在不改变元素顺序或删除潜在重复项的情况下解决这两个问题。

答案 2 :(得分:2)

如果我们将数据结构本身排除在外,请查看下一步的内存使用情况。如果您最终要求操作系统为您交换(即,列表占用的内存比您更多),Python将坐在 iowait 中等待操作系统从磁盘获取页面,根据你的描述,这是有道理的。

当这种减速发生时,Python是否坐在 iowait 的按摩浴缸里?环境中还有其他事情发生了吗?

(如果您不确定,请使用您的平台进行更新,我们其中一人会告诉您如何判断。)

答案 3 :(得分:2)

代码变慢的唯一原因是你在两个列表中都有大的元素,它们共享很多共同的元素(所以list1[curIndex] in list2需要更多的时间)。

以下是解决此问题的几种方法:

  • 如果您不关心订单,请将两个列表转换为set并使用set1.difference(set2)

  • 如果list1中的顺序很重要,那么至少将list2转换为一个集合,因为使用in set要快得多。

  • 最后,尝试使用过滤器:filter(list1, lambda x: x not in set2)

[编辑]由于set()不适用于递归列表(没想到),请尝试:

result = filter(list1, lambda x: x not in list2)

它应该比你的版本快得多。如果不是,那么您的最后一个选择是确保两个列表中都不存在重复元素。这将允许您从两个列表中删除项目(因此,当您找到list2中的元素时,比较会更便宜。

答案 4 :(得分:1)

编辑:我已经更新了我的答案,因为列表不可用,以及其他一些反馈。这个甚至经过了测试。

它可能与从列表中间弹出项目的成本有关。

或者您是否尝试使用集来处理此问题?

def difference(list1, list2):
    return [x for x in list1 if tuple(x) in set(tuple(y) for y in list2)]

如果您的意图是

,则可以将列表一设置到结果列表中
list1 = difference(list1, list2)

答案 5 :(得分:0)

经常建议的set不会在这里工作,因为这两个列表包含不可用的列表。您需要先更改数据结构。

你可以

  • 将子列表转换为元组或类实例以使其可以清除,然后使用集合。
  • 将两个列表排序,然后您只需要比较列表的头部。