如何从mmapped文件中读取行?

25

似乎mmap接口只支持readline()方法。如果我尝试迭代该对象,我会得到单个字符而不是完整的行。

在Python中,如何以"pythonic"的方式逐行读取一个mmap文件?

import sys
import mmap
import os


if (len(sys.argv) > 1):
  STAT_FILE=sys.argv[1]
  print STAT_FILE
else:
  print "Need to know <statistics file name path>"
  sys.exit(1)


with open(STAT_FILE, "r") as f:
  map = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
  for line in map:
    print line # RETURNS single characters instead of whole line

1
出于好奇,使用内存映射文件的动机是什么,而不是普通文件? - NPE
2
@aix:我可能有几GB的原始数据,我想以最有效的方式访问它们。但真正的原因是:这很酷 :) - Maxim Veksler
1
我不知道它是否更酷,但你不应该简单地假设它更快(如果你真的在意,你应该进行性能分析)。 - NPE
1
我在下面的帖子中添加了一些时间记录。 - hochl
5个回答

36

迭代遍历 mmap 行的最简洁方式是:

with open(STAT_FILE, "r+b") as f:
    map_file = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    for line in iter(map_file.readline, b""):
        # whatever

请注意,在Python 3中,iter()的sentinel参数必须是bytes类型,而在Python 2中需要是str类型(即使用""而不是b"")。


3
我不知道iter函数可以接受这种由callablesentinel参数组成的形式。我点了个赞并删除了自己的回答,支持这个回答。 - Fred Foo
请将打开模式更改为r+b,而不是下面提到的r(如下所述)。 - hochl
@SvenMarnach,您能否解释一下为什么在readline中使用b""作为第二个参数?谢谢。 - Gerasimos Ragavanis
1
@GerasimosRagavanis iter() 的两个参数版本基本上意味着:重复调用第一个参数中的函数并产生连续的返回值,但一旦第二个参数中的标记被返回就停止。因此,我们基本上调用 map_file.readline() 直到它不再返回任何数据。对于常规文件,您可以简单地编写 for line in file,但是 mmap 不直接支持行迭代,因此我们需要使用 iter() - Sven Marnach
1
@SvenMarnach 我该如何使用 mmap 获取大文件的行数以避免内存问题? - Kar
显示剩余2条评论

15

我把你的例子修改成了这样:

with open(STAT_FILE, "r+b") as f:
        m=mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
        while True:
                line=m.readline()
                if line == '': break
                print line.rstrip()

建议:

  • 不要将变量命名为map,因为它是一个内置函数。
  • 以Python示例上的 mmap 帮助页面所述的方式,用r+b模式打开文件。 它声明: 在任何情况下,您都必须为已打开以进行更新的文件提供文件描述符。 请参见http://docs.python.org/library/mmap.html#mmap.mmap
  • 最好不要使用UPPER_CASE_WITH_UNDERSCORES全局变量名称,正如在Global Variable Names中所述。 在其他编程语言(如C)中,常量通常写成全大写字母。

希望这可以帮到您。

编辑: 我在Linux上进行了一些时间测试,因为评论让我很好奇。以下是针对137MB文本文件进行的5次连续运行的时间比较结果:

普通文件访问:

real    2.410 2.414 2.428 2.478 2.490
sys     0.052 0.052 0.064 0.080 0.152
user    2.232 2.276 2.292 2.304 2.320

mmap文件访问:

real    1.885 1.899 1.925 1.940 1.954
sys     0.088 0.108 0.108 0.116 0.120
user    1.696 1.732 1.736 1.744 1.752

这些时间不包括print语句(我已经排除了它)。根据这些数字,我想说内存映射文件访问速度要快得多。

Edit 2: 使用python -m cProfile test.py,我得到了以下结果:

5432833    2.273    0.000    2.273    0.000 {method 'readline' of 'file' objects}
5432833    1.451    0.000    1.451    0.000 {method 'readline' of 'mmap.mmap' objects}

如果我没有弄错的话,那么 mmap 要快得多。

此外,似乎 not len(line) 的性能比 line == '' 差,至少这是我根据分析器输出的解释。


AttributeError: 'mmap.mmap' object has no attribute 'readlines' - Fred Foo
1
hochl:谢谢你。这些基准测试很棒。你能附上一个脚本来重现测试并确认分析吗? - Maxim Veksler
2
我只是在您的程序中将打印部分注释掉,然后执行了 time test.py 大约十次,然后取了5个中间值。检查 python -m cProfile test.py 的结果会很有趣。 - hochl

1

以下内容相当简洁:

with open(STAT_FILE, "r") as f:
    m = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    while True:
        line = m.readline()  
        if line == "": break
        print line
    m.close()

请注意,line 保留了换行符,因此您可能想要将其删除。这也是为什么 if line == "" 能够正常工作的原因(空行被返回为 "\n")。
原始迭代方式之所以能够正常工作,是因为 mmap 尝试同时看起来像文件和字符串。它在迭代方面看起来像一个字符串。
我不知道为什么它不能(或选择不)提供 readlines()/xreadlines()

文件对象的readlines()方法返回文件所有行的列表。在mmapped文件上执行此操作将完全破坏mmap的作用。 - Sven Marnach
@SvenMarnach:它可能是一个生成器。无论如何,说实话我看不出在整个问题中需要记忆映射文件的必要性。 - NPE
你是完全正确的。也许这种生成器不存在的原因是它没有意义。 :) - Sven Marnach

0

在Windows上,Python 2.7 32位版本在mmapped文件上的速度比普通文件快超过两倍

在一个27MB、509k行的文本文件上(我的“parse”函数并不重要,它主要只是非常快地读取每一行):

with open(someFile,"r") as f:
    if usemmap:
        m=mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    else:
        m=f
        e.parse(m)

使用MMAP:

read in 0.308000087738

没有MMAP:
read in 0.680999994278

-1

如果您使用mmap()出现错误,那就更好了:

with open('/content/drive/MyDrive......', "r+b") as f:
    # map_file = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mmap not recogn. import something
    for line in iter(f.readline, b""):
      print(line)

您的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认您的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - moken

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