更新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个小时才能完成,我只是想把整个过程写成证明了我的困惑。
您认为我正在努力做出更好的解决方案吗?
答案 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)的集合中。