Python中迭代遍历一个大文件(10GB+)的最有效方法

12
我正在编写一个Python脚本,用于处理两个文件 - 一个包含UUID列表,另一个包含大量的日志条目 - 每行都包含来自另一个文件的UUID之一。该程序的目的是从file1创建UUIDS列表,然后对于每次在日志文件中找到的UUID,增加与之关联的值以记录匹配次数。
简而言之,计算每个UUID在日志文件中出现的次数。目前,我有一个列表,其中UUID作为键,'hits'作为值。然后是另一个循环,它遍历日志文件的每一行,并检查日志中的UUID是否与UUID列表中的UUID匹配。如果匹配,则会增加值。
    for i, logLine in enumerate(logHandle):         #start matching UUID entries in log file to UUID from rulebase
        if logFunc.progress(lineCount, logSize):    #check progress
            print logFunc.progress(lineCount, logSize)  #print progress in 10% intervals
        for uid in uidHits:
            if logLine.count(uid) == 1:             #for each UUID, check the current line of the log for a match in the UUID list
                uidHits[uid] += 1                   #if matched, increment the relevant value in the uidHits list
                break                                #as we've already found the match, don't process the rest
        lineCount += 1               

它按照预期工作 - 但我相信有一种更有效的处理文件的方法。我看了几个指南,发现使用“count”比使用编译后的正则表达式更快。我认为分块读取文件而不是逐行读取可以通过减少磁盘I/O时间来提高性能,但在一个测试文件(约200MB)上的性能差异微不足道。如果有人有其他方法,我将非常感激 :)


2
文件I/O通常是缓冲的,无论您实际读取的块的大小如何。 - user395760
3
需要更高效吗?需要多长时间?你需要它花费多长时间?你可能已经达到了存储(磁盘)的性能极限,这种情况下,你的 Python 脚本运行得更快也没有用。 - Nicholas Knight
现在正在运行测试文件-已经完成了一个大小为10GB的文件的一半,大约花费了30分钟。作为我第一次使用Python,我不太清楚这是快还是慢。没有要求它必须在x分钟内完成,但是更快当然更好;) - SG84
在你的例子中,第二个 if 语句是空的(之后没有缩进的代码)。你能修复一下吗? - Steven Rumbalski
4
你需要遍历文件中每一行的所有UID。相反,应该在每一行中找到UUID并在字典中查找。尽可能少地在代码的最常调用部分执行操作。 - Rosh Oxymoron
5个回答

14

思考函数式编程!

  1. 编写一个函数,它将接受日志文件的一行并返回uuid。称其为uuid

  2. 将此函数应用于日志文件的每一行。如果您使用的是Python 3,则可以使用内置函数map;否则,您需要使用itertools.imap。

  3. 将此迭代器传递给collections.Counter。

collections.Counter(map(uuid, open("log.txt")))

这将会非常高效。

以下是一些注释:

  • 这完全忽略了UUID列表,只计算出现在日志文件中的个数。如果你不想这样的话,你需要修改程序。

    • 你的代码很慢,因为你使用了错误的数据结构。你需要一个字典。

感谢您的输入 - 一旦这个测试运行完成并且我收回我的资源,我会看一下。我认为我使用列表而不是字典是因为我想保持UUIDS的顺序,但我猜我可以稍后使用列表作为索引,然后从字典中提取相应的值? - SG84
5
@SG84,你可以阅读一篇关于Python生成器的优秀文章:http://www.dabeaz.com/generators/Generators.pdf,特别适用于处理大文件。你会有所启发的 :-) - OnesimusUnbound
我之前尝试过使用生成器,虽然在摸清了其中的奥秘并让它正常工作后,性能提升并没有比直接使用for循环读取文件时有太大的区别。不过还是感谢你提供的链接,这些都是很好的阅读材料。 - SG84
好的,掌握了生成器和集合模块之后,我修改了代码中的一些函数,现在在一个250MB的文件上的总执行时间从96.4秒降到了5.4秒!这是一个巨大的胜利。非常感谢大家的帮助 :) - SG84

3

你尝试过mincemeat.py吗?它是MapReduce分布式计算框架的Python实现。我不确定你是否会获得性能提升,因为在使用它之前我还没有处理过10GB的数据,但你可以探索一下这个框架。


3

3

就像前面的人所说,对于一个10GB的文件,您很快就会达到磁盘的极限。对于仅涉及代码的改进,生成器建议非常好。在Python 2.x中,它看起来会像这样:

uuid_generator = (line.split(SPLIT_CHAR)[UUID_FIELD] for line in file)

听起来这似乎并不一定是一个Python问题。如果你所做的不仅仅是计算UUID数量,Unix可能比Python更快地解决你的问题。

cut -d${SPLIT_CHAR} -f${UUID_FIELD} log_file.txt | sort | uniq -c 

0
尝试使用分析器http://docs.python.org/library/profile.html测量大部分时间花费的地方。
最佳优化位置取决于你数据的性质:如果uuid列表不是很长,你可能会发现大部分时间都花在“if logFunc.progress(lineCount, logSize)”上。如果列表非常长,可以将uidHits.keys()的结果保存到循环之外的变量中,并在迭代时使用该变量而不是使用字典本身,但Rosh Oxymoron建议先查找id,然后再在uidHits中检查id,这样可能会更有帮助。
无论如何,您都可以消除lineCount变量,并改用i。如果行非常长,则find(uid)!= -1可能比count(uid)== 1更好。

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