17

我有一个 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()

由于我将缓冲区设置为10MB,就性能而言,它与我mmap 10MB的文件一样吗?

谢谢你。

4

2 回答 2

34

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

这样做的原因mmap不是将文件映射到物理内存,而是映射到虚拟内存mmapped 文件就像您的程序的特殊交换文件一样。考虑这一点可能会有点复杂,但上面的维基百科链接应该会有所帮助。

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

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

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

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

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

回答您的后续问题:

由于我将缓冲区设置为10MB,就性能而言,它与我mmap 10MB的文件一样吗?

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

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

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

于 2013-01-12T02:04:25.777 回答
1

我来尝试使用mmap是因为我fileh.readline()在一个几十 GB 大小的文件上使用过,并希望让它更快。Unixstrace实用程序似乎显示文件现在以 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) 然后执行 normalopen(filename, 'r', buffering=10*1024*1024)或执行mmap.mmap(open(filename).fileno()). 我当然想避免自己摆弄滑动窗口方法,但该功能是否会做一个简单的决定是否mmap对我来说就足够了。

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

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

于 2016-04-04T23:12:42.447 回答