当整个文件太大时如何在python中使用mmap

Posted

技术标签:

【中文标题】当整个文件太大时如何在python中使用mmap【英文标题】:How to use mmap in python when the whole file is too big 【发布时间】:2013-01-12 01:59:30 【问题描述】:

我有一个 python 脚本,它逐行读取文件并查看每一行是否与正则表达式匹配。

我想通过在搜索之前使用内存映射文件来提高该脚本的性能。我研究了 mmap 示例:http://docs.python.org/2/library/mmap.html

我的问题是当文件对于我的机器内存 (4GB) 来说太大 (15GB) 时,我该如何映射文件

我是这样读取文件的:

fi = open(log_file, 'r', buffering=10*1024*1024)

for line in fi: 
    //do somemthong

fi.close()

由于我把buffer设置为10MB,从性能上来说,和我mmap 10MB文件一样吗?

谢谢。

【问题讨论】:

确保 IO 是性能瓶颈,而不是正则表达式搜索或程序中的其他操作。 @J.F.塞巴斯蒂安:+1。每 80 个字符执行一次带有 re.search(和隐式 find('\n') 等效项)的 Python 循环,而不是每 10MB 一次,这可能足以使 IO 成本相形见绌,并使问题的其余部分变得无关紧要。我更新了我的答案以考虑到这一点。 【参考方案1】:

首先,您机器的内存无关紧要。这是相关的进程address space 的大小。对于 32 位 Python,这将低于 4GB。使用 64 位 Python 就绰绰有余了。

原因是mmap 不是将mapping a file 放入物理内存,而是放入virtual memorymmapped 文件就像您的程序的特殊交换文件一样。考虑这一点可能会有点复杂,但上面的 Wikipedia 链接应该会有所帮助。

所以,第一个答案是“使用 64 位 Python”。但显然这可能不适用于您的情况。

显而易见的替代方法是在前 1GB 中进行映射、搜索、取消映射、在下一个 1GB 中进行映射等。执行此操作的方法是将 lengthoffset 参数指定给 mmap方法。例如:

m = mmap.mmap(f.fileno(), length=1024*1024*1024, offset=1536*1024*1024)

但是,您要搜索的正则表达式可能会在前 1GB 中找到一半,在第二个中找到一半。因此,您需要使用窗口化——在前 1GB 中映射、搜索、取消映射,然后在部分重叠的 1GB 中映射,等等。

问题是,您需要多少重叠?如果您知道匹配的最大可能大小,则不需要更多。如果你不知道……好吧,如果不打破你的正则表达式,就没有办法真正解决问题——如果这不明显,想象一下你怎么可能在一个 1GB 的窗口中找到一个 2GB 的匹配项。

回答您的后续问题:

由于我将buffer设置为10MB,从性能上来说,和我mmap 10MB的文件一样吗?

与任何性能问题一样,如果它真的很重要,您需要对其进行测试,如果不重要,请不要担心。

如果你想让我猜:我认为mmap 在这里可能更快,但这仅仅是因为(正如 J.F. Sebastian 暗示的那样)循环和调用re.match 128K 次可能会导致你的代码受 CPU 限制,而不是IO绑定。但是你可以在没有mmap 的情况下优化它,只需使用read。那么,mmap 会比read 快吗?考虑到所涉及的大小,我预计mmap 在旧 Unix 平台上的性能会快得多,在现代 Unix 平台上大致相同,而在 Windows 上会慢一些。 (如果您使用madvise,您仍然可以从mmap 中获得比readread+lseek 更大的性能优势,但这与这里无关。)但实际上,这只是一个猜测。

使用mmap 最令人信服的原因通常是它比基于read 的代码更简单,而不是它更快。当您必须使用 mmap 的窗口,并且当您不需要使用 read 进行任何搜索时,这就不那么引人注目了,但是,如果您尝试以两种方式编写代码,我希望您的mmap 代码最终会更具可读性。 (特别是如果您尝试从明显的read 解决方案中优化出缓冲区副本。)

【讨论】:

很好的答案,但恕我直言,如果您能解释为什么重要的是地址空间而不是机器中的物理内存量,恕我直言可能会更好。 @martineau:好主意。我试过了,但是……解释这些东西很棘手,特别是如果你不知道观众已经知道了多少。如果最后一段是无法阅读的混乱,我不会太惊讶。有什么改进建议吗? 您添加的内容似乎有点过于详细。我想它可以归结为更重要的地址空间,因为这是您的计算机可以访问多少内存而不使用一些技巧,这已经必须保存您的操作系统、正在运行的程序和非内存映射数据。如果您对文件进行内存映射,则整个内容将需要适合该空间内的空闲内容。在 32 位系统上,无法将 15 GB 的文件放入 @martineau:嗯……你刚才的总结是错误的。我不确定您是在谈论物理内存还是母版页表空间,但这些都与这里无关。地址空间是每个进程可以访问多少内存,而不是整个计算机。其他正在运行的程序及其数据无关紧要。 32 位系统与 64 位系统也是如此——如果您在 64 位系统上运行 32 位进程,它仍然只有 4GB 的地址空间。 我的示例是关于在 32 位操作系统下运行的进程可用的 4 GB virtual address space。我考虑诸如操作系统的页表部分之类的东西。我可能说的是“计算机”而不是更正确的术语“过程”。链接文章中的插图显示了我试图描述的内容——即在这种情况下,包括memory-mapped 文件在内的所有内容都必须在 4 GB 以内。【参考方案2】:

我开始尝试使用mmap,因为我在一个数十 GB 大小的文件上使用了fileh.readline(),并希望让它更快。 Unix strace 实用程序似乎显示文件现在以 4kB 块读取,至少 strace 的输出在我看来打印缓慢,我知道解析文件需要很多小时。

$ strace -v -f -p 32495
Process 32495 attached
read(5, "blah blah blah foo bar xxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
^CProcess 32495 detached
$

到目前为止,这个线程是唯一解释我不应该尝试mmap 一个太大的文件。我不明白为什么还没有像mmap_for_dummies(filename) 这样的辅助函数,它会在内部执行 os.path.size(filename),然后执行正常的open(filename, 'r', buffering=10*1024*1024) 或执行mmap.mmap(open(filename).fileno())。我当然想避免自己摆弄滑动窗口方法,但该函数是否会做一个简单的决定是否执行 mmap 对我来说就足够了。

最后提一下,我仍然不清楚为什么互联网上的一些例子没有解释地提到open(filename, 'rb')(例如https://docs.python.org/2/library/mmap.html)。如果有人经常想通过.readline() 调用在for 循环中使用该文件,我不知道我是否应该以'rb' 或仅'r' 模式打开(我想有必要保留'\n')。

感谢您提及 buffering=10*1024*1024) 参数,这可能比更改我的代码以获得一些速度更有帮助。

【讨论】:

以上是关于当整个文件太大时如何在python中使用mmap的主要内容,如果未能解决你的问题,请参考以下文章

当vector的大小太大时,如何解决C++中内存不足的问题?

如何为python项目创建虚拟环境

当图像放大太大时,UIPinchGestureRecognizer 表现得很有趣

当 flex 项目变得太大时,iPad Safari 会忽略边距

Xcode 在条形按钮项目大小太大时使用 PDF 图像

wpf:当文本对于 1 行来说太大时,使文本块高度扩大