亲宝软件园·资讯

展开

Python中mmap模块处理大文本的操作方法

程序猿-张益达 人气:0

如果现在有一个需求,我们需要处理一个20G的大文件,我们会怎么处理呢?思考下,我们需要怎么实现这个功能。

我们可能会这么实现:

def get_datas():
    source_text_path = "路径"
    with open(source_text_path, 'rb') as f:
        data = f.readlines()
    yield data
if __name__ == '__main__':
    for e in get_datas():
        deal_data(e)  # 处理数据

这样虽然能实现,但是我们处理的时候需要消耗的资源和性能不是很友好,所以我们要优化,也就是使用mmap模块。

mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一映射关系。它省掉了内核态和用户态页copy这个动作(两态间copy),直接将用户态的虚拟地址与内核态空间进行映射,进程直接读取内核空间,速度提高了,内存占用也少了。

简单点来说,mmap函数实现的是内存共享。内存共享是两个不同的进程共享内存的意思:同一块物理内存被映射到两个进程的各自的进程地址空间。这个物理内存已经被规定了大小(大小一定要比实际写入的东东大)以及名称。当需要写入时,找到内存名称,然后写入内存,等需要读取时候, 首先要知道你要读取多大(因为物理内存比你要读取的东西大,全部读取的话会读到一些“空”的东西),然后寻找对应名称的物理块,然后读取。

mmap 介绍

Windows

mmap.mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])

参数说明:

offset:非负整数偏移量,默认从0开始。

Unix

mmap.mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])

参数说明:

fileno文件描述符有如下

支持的方法

对于EOF的处理,write() 和 read_byte() 抛出异常 ValueError,而 write_byte() 和 read() 什么都不做。

使用mmap读取大文件

from mmap import mmap
def read_data(file_path):
    with open(file_path, "r+") as f:
        m = mmap(f.fileno(), 0)
        g_index = 0
        for index, char in enumerate(m):
            if char == b"\n":
                yield m[g_index:index + 1].decode()
                g_index = index + 1
if __name__ == "__main__":
    file_path = ""
    for content in read_data(file_path):
        print(content)

什么时候用mmap?

用mmap来读取超大文件,不是mmap的主要应用场景,Python官方文件也没有提到这一点。如果仅仅是读取超大文件,使用文件对象的read(N),来得更快更好更简单。

关于标准库中的mmap模块。现有一个需求,要对超大文件(接近40G)进行读写,notepad++等工具直接拒绝打开此文件。用 r+ 模式打开文件,可以随意读写,但是要特别小心。readline()是否能够使用,要看这个文件每行都多长,如果没有换行,就不能用,就算知道每行的大小,也要带个参数N来控制最大读取数量。readlines()是肯定不能用的,就算带参数,也可能直接卡死!read(N)没问题,主要控制是N的大小。

总之,传统读写文件的方式可以用,但是不够方便。速度也是个问题,传统的缓存IO方式,涉及到OS内核态的内存和进程虚拟空间内存的内容交换,对于超大文件而言,这种交换会浪费大量的CPU时间和内存。mmap是另一个方式!它省掉了内核态和用户态页拷贝这个动作(两态间copy),直接将用户态的虚拟地址与内核态空间进行映射,进程直接读取内核空间,速度提高了,内存占用也少了。

总结来说,常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此在某些场景下,mmap效率更高。

从python官网上看mmap的介绍,生成的mmap对象,就像一个bytearray对象,可以直接用index的方式读写,可以切片。同时,mmap对象还有一组类似文件操作的接口,read,readline,flush等等。即mmap对象兼具bytearray和file对象的功能。不过还是要注意,对于超大文件的读(先不考虑写的问题吧),从磁盘到内核,依然会占用内存,因此绝对不能一口气全部读出来。read(N)是必须的,mmap的使用只是可能会提高效率。(如果频繁的创建和关闭mmap映射,这种创建是为了指向超大文件的不同位置,反而效率更低。一般情况下的read(N)实现,不需要使用mmap。)

mmap的另一个应用场景,是进程间的内存共享。多个进程将同一个文件map到同一段内核地址上,即实现了相互之间的共同访问。

总结:使用mmap的时机

加载全部内容

相关教程
猜你喜欢
用户评论