使用re和mmap时可能存在内存泄漏(?)问题

3
我在使用Python 3.4(64位Windows)处理一个大约30G的mmap文件时,遇到了正则表达式搜索问题。事实上,在匹配之间,内存占用量会增长到与匹配之间的字节数大致相同,尽管它并没有崩溃,但内存占用量足以减缓其他进程的速度(由于mmapped文件的大小)。
我的正则表达式来自于字节字符串,它非常明确,所有限定符都有界限。在我的表达式中没有星号或加号,因此不会发生可怕的超限制的情况(最坏的情况下,匹配将有2200个字节长,多数命中较小)。我将匹配的字符串存储在一个列表中,但通常只有几千个命中,因此不是所有空间都被命中所占据。
目前我假定正则表达式引擎(sre right?)会在匹配之间保留所有字符串在内存中,在数据集很小时这是可以接受的,但对于我的情况不算理想。所以我的问题是:这种假设是否正确,如果正确,我该如何改变这种行为(最好是不需重新编译库)?
以下是代码:
pattern = re.compile(b"PATTERN.{1,20}", re.DOTALL)
f = open("file.bin", "rb")
mem = mmap.map(f.fileno(), 0, access=mmap.ACCESS_READ)

results = []
for match in pattern.finditer(mem):
    results.append(match.group(0))

f.close()

很难说。微软喜欢滥用缓存,所以可能文件的一部分被Windows缓存在内存中。 - Jason Hu
我不确定这是Windows的错(或者说不完全是Windows的错)。每次匹配时内存占用减少的行为感觉像是来自库内部...我需要在一个类Unix系统的环境下运行这个测试... - LexyStardust
1个回答

0

我不确定有没有办法解决这个问题。你正在以硬盘能提供的速度读取大量数据。除非你拥有大量的RAM,否则在某个点上你会耗尽RAM并且需要释放一些出来。大多数操作系统会使用LRU(最近最少使用)算法来决定从RAM中踢出什么。由于你正以可能的最快速度访问数据,内存映射文件使用的大部分内存将具有较近的访问时间。这意味着它们是“不合适”的被踢出RAM的候选项(至少根据操作系统而言)。

基本上,当内存用完时,操作系统在选择从RAM中踢出什么方面做出了错误的选择。

然而,你更了解哪些内存可以释放。因此,你可以按块扫描文件。这将明确告诉操作系统你不再需要文件的早期部分,并允许释放那部分内存。当然,在块的边界处会产生问题。

以下是改善程序内存性能的示例:

import re
import mmap
import os

filename = "some_file.txt"
file_size = os.stat(filename).st_size
chunk_size = 2**32
# chunk_size = 50 # smaller chunk_size I used for testing
regex = re.compile(rb"PATTERN\d{1,20}\n")
max_length = len("PATTERN") + 20 + len("\n")

matches = []
f = open(filename, "rb")    
for i in range(0, file_size, chunk_size - max_length + 1):
    # compute length of data to search over
    length = chunk_size if i + chunk_size <= file_size else file_size - i 

    m = mmap.mmap(f.fileno(), length=length, offset=i, access=mmap.ACCESS_READ)
    # f.seek(i) # used for testing
    # m = f.read(length)

    for match in regex.finditer(m):
        if not (match.end() == len(m) and len(match.group()) < max_length and length == chunk_size):
            # if match ends at end of string
            # and not maximum length of regex
            # but not also at the end of the file
            # THEN there *may* be a cross chunk-boundary match
            # THUS, defer match to next loop iteration
            matches.append(match.group())
    m.close()
f.close()

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