分享大量数据去重的方法,顺便问下 python 内存占用问题

2016-04-18 22:01:48 +08:00
 cdwyd

单个文本文件,大小 11G ,数据总量 6000 万左右,去重后约 4000 万,去重的依据是 md5 值列。

首先尝试的方法是:建立 md5 的唯一索引, load data infile 语句导入,跑了一个晚上没跑完。 后来取 md5 的前三位进行判断,把不重复的数据写到新的文本文件,去掉唯一索引,再次用 load data infile 语句导入,共计( 10 + 8 = 18 分钟)。

代码大致如下,问题是,这段代码运行后会把 6G 内存全部用完(系统 1G , python 占用 5G ),想问下怎么会占用这么多内存?

import time

start_time = time.time()
lines = []


md5s = {}
for x in 'abcdef1234567890':
    for y in 'abcdef1234567890':
        for z in 'abcdef1234567890':
            md5s[x + y + z] = set()

with open('files.txt', 'r', encoding = 'utf-8') as f:
    for i, line in enumerate(f):
        try:
            if i % 10000 == 0:
                print(i)
            md5 = line.split('|')[3]
            key = md5[:3]
            if md5 not in md5s[key]:
                md5s[key].add(md5)
                lines.append(line)
                if len(lines) > 10000:
                    with open('new.txt', 'a', encoding = 'utf-8') as f:
                        f.write(''.join(lines))
                    lines = []
        except Exception as e:
            print(e)
            print(line)

with open('new.txt', 'a', encoding = 'utf-8') as f:
    f.write(''.join(lines))
lines = []

print((time.time() - start_time) / 60)
14661 次点击
所在节点    Python
34 条回复
loading
2016-04-18 22:12:54 +08:00
使用 SQLite 内存模式比你使用 txt 快很多,而且数据库去重很方便。
decaywood
2016-04-18 22:30:04 +08:00
如果要自己造轮子,一般解决方案是 hash 取余到多个文件,分别去重然后进行归并,这样就不存在内存耗尽问题了
binux
2016-04-18 23:21:45 +08:00
缓存 lines 的意义何在?
把 md5 分组意义何在? x in set 是 O(1) 的
sicklife
2016-04-18 23:29:08 +08:00
redis
yzongyue
2016-04-18 23:31:58 +08:00
md5s = {} 这个字典会越来越大, 这个问题要不要试试 hadoop ?
SlipStupig
2016-04-18 23:40:00 +08:00
费这个力气干嘛,有更简单解决访问方案啊!
with open('ufiles.txt', 'r') as f:
with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)as m:
with open(‘ object.txt ’, 'wb') as new_file:
new_file.wirtelines(list(set(list)))
glasslion
2016-04-19 00:13:36 +08:00
md5 做 hash 有何意义? 第一个版本一个晚上没跑完,第二个版本 md5 前三位 只用了 18 分钟是否说明第一个的瓶颈是在 算 md5 上? string 直接塞 set 不行吗?
Gn
2016-04-19 00:20:44 +08:00
4000 万条 md5 ,最少需要 32*4000*10^4/10^9 = 1.28 GB
python 的 set 是 hash set ,虽然估计是动态的,但你分了 4000 个,空洞肯定少不了,用完 5 GB 内存应该不难吧。

其实我的话就直接外部排序,归并的时候顺便去重,不算太慢。 hash 查重的话短了容易冲突,长了算得太慢。 string 直接放 set 里内存不够吧。
zqhong
2016-04-19 00:30:54 +08:00
直接用 Linux 命令解决就好了

$ wc -l testfile
352235592 testfile

$ sort.exe -u testfile

跑了三分钟,占用内存 1G 多。 CPU 是 i5-4200U ,内存为 8G DDR3 1600 。
kslr
2016-04-19 01:22:57 +08:00
布隆过滤器
9hills
2016-04-19 02:54:03 +08:00
awk 一行搞定,而且很快,。。。
cdwyd
2016-04-19 08:05:05 +08:00
@binux
缓存 lines 是为了减少文件打开关闭次数,一定数量后统一写入。
不知道 in set 是 O(1) 的,感谢指出,我去试试不分组的速度。
cdwyd
2016-04-19 08:06:39 +08:00
@glasslion
可能是我描述不清楚导致了误会, md5 是其中的一个字段,作为去重依据。分组的问题是我之前不清楚 x in set 是 O(1) 的,想着分组能快一些。
cdwyd
2016-04-19 08:10:11 +08:00
@Gn
感谢解释了内存占用问题。是我的描述不清楚,这是个 csv 文件, md5 字段是已经存在的,用它来去重,并没有把整行的 str 放到 set ,只放了完整的 md5.
cdwyd
2016-04-19 08:12:18 +08:00
@zqhong
@9hills
详细说下
cdwyd
2016-04-19 08:13:05 +08:00
@SlipStupig
没看懂这个,去找找资料
crowds
2016-04-19 08:27:27 +08:00
@9hills 已笑哭。。。
其实 sort+uniq 就可以了吧
Zzzzzzzzz
2016-04-19 08:39:11 +08:00
pandas 有现成的 drop_duplicates
nevin47
2016-04-19 08:54:43 +08:00
虽然有很多轮子用了, LZ 自己这个轮子也挺有意思的。我觉得应该是 md5s = {}这个字典占用太大了吧

另外这种数据随便一个数据库就能解决的,搞文本确实有点得不偿失
Gn
2016-04-19 09:35:22 +08:00
@cdwyd 恩,我说 string 直接放 set 里是回复楼上的,算的是 md5 的占用。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/272090

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX