大型Python集的操作

时间:2016-08-24 22:08:35

标签: python set

更新1

两个集合都包含最大长度为20的字符串,并且只能从'abcdefghijklmnopqrstuvwxyz'获取值

更新2

我通过使用名为ujson的库(类似于simplejson)从磁盘读取2个文件然后将返回的列表转换为集合来构建集合。

我试图区分2组,每组包含1亿个元素。

此代码在2分钟内执行:

temp = set()                              #O(1)
for i in first_100_million_set:           #O(N)
    temp.add(i)                           #O(1)

此代码在6小时内执行:

temp = set()                              #O(1)
for i in first_100_million_set:           #O(N)
    if i in second_100_million_set:       #O(1)
        temp.add(i)                       #O(1)

我所做的只是添加会员资格检查,如果我没有弄错,可以在O(1)中完成吗?这种巨大的减少来自哪里?

我知道set(a) - set(b),它实际上正在完成我的第二个代码块正在做的事情,也需要6个小时才能完成,我只是想把整个过程写成证明了我的困惑。

您认为我正在努力做出更好的解决方案吗?

2 个答案:

答案 0 :(得分:11)

在谈论1亿个元素集时,我担心数据会从RAM中逐出(转到swap / pagefile)。 Python 3.5上的一个100M元素set是为64位处理器构建的(你正在使用它,因为你甚至无法在32位构建的Python中创建这样的set)使用4 GB内存仅用于set开销(忽略它包含的对象使用的内存)。

您创建新set而不对第二个set进行成员资格测试的代码会按顺序访问此内存,因此操作系统可以预测访问模式,并且可能会在您需要之前将数据提取到缓存中如果大多数set被分页。唯一的随机访问发生在第二个set的构建中(但很方便,插入的对象已经在缓存中,因为您从原始set中提取它们)。因此,您可以从无随机访问增长到可能随机访问的4 GB(加上包含对象的大小)内存,并且必须不会被分页,而不会导致性能问题。

在第二种情况下,每次测试都会随机访问正在测试成员资格的set,并且必须使用匹配的哈希加载存储桶冲突链中的每个对象(诚然,哈希生成良好,不应该这些比赛太多了。但这意味着随机访问内存的大小从0增长到4 GB,从4增长到8 GB(取决于set之间存在多少重叠;再次忽略对{1}}的访问权限存储的对象本身)。如果这促使你从执行大多数RAM访问到发生需要从页面文件读取的页面错误,这比RAM访问慢几个数量级,我不会感到惊讶。并非巧合的是,该代码执行的时间要长几个数量级。

对于记录,set开销可能只是存储对象成本的一小部分。 Python中最小的有用对象是float s(Python 3.5 x64上的24个字节),但由于完全相等测试的问题,它们是set的不良选择。可能需要小于30位的int是有用的,并且每块吃28个字节(为存储该值所需的每个完整30位增加4个字节)。因此,100M元素集可能“仅”使用4 GB用于数据结构本身,但这些值最少为2.6 GB左右;如果它们不是Python内置类型,那么即使使用__slots__,用户定义的对象也会至少加倍(如果不使用__slots__则为五倍),甚至在他们为他们支付RAM之前属性。我的机器上有12 GB的RAM,你的第二个用例会导致大量的页面抖动,而你的第一个案例对set初始化的range(100000000)运行得很好(尽管它会导致大多数其他进程被分页;带有两个set s加上它们之间共享的int的Python使用~11 GB)。

更新:您的数据(1-20个ASCII字符的字符串)将在Python 3.5 x64上使用50-69个字节(可能多一点包括分配器开销),或每set 4.65-6.43 GB(假设没有共享字符串,原始数据为9-13 GB。添加所涉及的三个set,您正在查看最多25 GB的RAM(您不会再为第三个set的成员付费,因为它们与第一个set共享{1}})。我不会尝试在任何RAM少于32 GB的计算机上运行代码。

至于“有更好的解决方案吗?”这取决于你需要什么。如果您实际上不需要原始的set,只需产生差异,流式传输您的数据会有所帮助。例如:

with open(file1) as f:
    # Assume one string per line with newlines separating
    myset = set(map(str.rstrip, f))

with open(file2) as f:
    myset.difference_update(map(str.rstrip, f))

那将在大约10-11 GB的内存中达到峰值,然后随着第二个输入中的元素被删除而下降,只留下差异set而没有别的。其他选项包括使用已排序的list数据,这会将开销从每set 4 GB减少到每list ~850 MB,然后并行迭代(但不是同时; zip此处不合适)找到第一个list但不存在第二个{{1}}中存在的元素,同时删除一些随机访问费用。

答案 1 :(得分:0)

检查元素是否在集合中似乎是O(1),请参阅下面的代码。必须在内部使用散列函数创建集合。哈希表的成功率取决于您使用的密钥,以及Python猜测您所做的事情的方式。然后使用range(n)作为集合是理想的。

import time
def foo(n):
  myset = set(range(n))
  to = time.time()
    for e in myset:     #O(n)
      if e in myset:    #O(n) or O(1)?
        pass
      else:
        raise "Error!"
  print time.time() - to
  return

>>> foo(10**6)
0.0479998588562
>>> foo(10**7)
0.476000070572

因此函数foo在O(n)中执行,并检查元素是否仅在使用O(1)的集合中。