好闻要顶

mmap vs read

先放个结论:

  • 内存映射通常比随机访问更快,尤其访问的对象是分离的和不可预测的.
  • 内存映射会持续占用pages, 直到完成访问. 这意味当长时间重度使用一个文件很久之前, 然后你关闭了它, 然后再重新打开, 它会直接cache hit, 文件命中. 而Read方法, 这个文件已经早被flush走了. mmap 用完立马丢弃它, 它把文件映射到了内存上.
  • Read读文件比较简单, 而且比较快.
    总结, 使用mmap: 访问数据随机地, 保存它长时间, 或想着共享给其它进程; Read 适合访问数据连续存储的数据, 或者读完就丢弃掉.

https://stackoverflow.com/questions/45972/mmap-vs-reading-blocks

上述的Stackoverflow上的讨论非常值得阅读,高票下的评论区在争论mmap的开销问题, 尤其是连续的文件读取的性能上.

一.操作数据的两种方式

https://blog.schmichael.com/2011/05/15/sharing-python-data-between-processes-using-mmap/

Usually in the UNIX world you have 2 ways of accessing/manipulating data: memory addresses or streams (files). Manipulating data via memory addresses means pointers, offsets, malloc/free, etc. Stream interfaces manipulate data via read/write/seek system calls for files and send/recv/etc for sockets.

通常在UNIX世界中,有两种访问/操作数据的方式:内存地址或流(文件)。文件的操作大多是基于流操作。

  • 通过内存地址操作数据意味着指针,偏移,malloc / free等。
  • 流接口操作数据通过对文件的系统调用( read/write/seek) 和socket操作(send / recv / etc)。

二.文件操作的两种方式

1. 标准文件I/O

I/O的原理: https://blog.csdn.net/jfengamarsoft/article/details/76216486
I/O请求包括数据从缓冲区排出(写操作)和数据填充缓冲区(读操作)。 每一次IO操作,都会发生用户态--内核态这种 system call。

I/O操作有一个巨大的缺陷,就是当文件很大,比如有1亿行时,如果每读一行都进行一次IO操作,那么,这个系统调用的次数是1亿多次,频繁的IO操作严重影响程序的性能。

2. 内存映射I/O

内存映射意味着将文件加载到内存的用户空间,这意味着内存地址与文件中的字之间存在一对一的对应关系。此资源通常是物理存在于磁盘上的文件,但也可以是设备,共享内存对象或操作系统可通过文件描述符引用的其他资源。一旦存在,文件和存储空间之间的这种相关性允许应用程序将映射部分视为主存储器。程序员可以直接通过内存访问文件,与任何其他内存驻留数据相同 - 甚至可以允许写入内存区域透明地映射回磁盘上的文件。

优点: 如果一个大文件,假设每次进行内存映射50M,那么I/O操作的次数便少了, 提高了I / O性能。
缺点: 对于小文件,内存映射文件会导致浪费空间。因为内存映射始终与页面大小对齐,大多为4 KB。因此,5 KB文件将分配8 KB,因此浪费了3 KB。

3.两个方法的对比:

https://en.wikipedia.org/wiki/Memory-mapped_file

  • 访问内存映射文件比使用直接读写操作更快。首先,系统调用比程序本地内存的简单更改慢几个数量级。其次,在大多数操作系统中,实际映射的内存区域是内核的页面缓存(文件缓存),这意味着不需要在用户空间中创建副本。
  • 只有具有MMU的硬件架构才能支持内存映射文件。在没有MMU的体系结构中,操作系统可以在发出映射请求时将整个文件复制到内存中,但如果只访问文件的一小部分,这将非常浪费和缓慢,并且只能用于文件这将适合可用的内存。

简而言之,内存映射性能更好。由于系统调用开销和内存复制,标准I/O方法成本很高。内存映射文件的另一个常见用途是在多个进程之间共享内存。在现代保护模式操作系统中,通常不允许进程访问分配给另一进程使用的存储器空间,内存映射可以安全地共享内存。

三. python mmap = 内存映射I/O

https://www.safaribooksonline.com/library/view/linux-system-programming/0596009585/ch04s03.html

上面这篇文章很好讲述了mmap的原理: 即

As an alternative to standard file I/O, the kernel provides an interface that allows an application to map a file into memory, meaning that there is a one-to-one correspondence between a memory address and a word in the file. The programmer can then access the file directly through memory, identically to any other chunk of memory-resident data—it is even possible to allow writes to the memory region to transparently map back to the file on disk.

mmap本质上是内存映射。文件被映射到内存之后,这个文件就如同一个字符串变量一样,可以随意的操作,诸如 end/recv/ 等socket操作。

作为标准文件I / O的替代,内核提供了一个允许应用程序将文件映射到内存的接口,这意味着内存地址与文件中的字之间存在一对一的对应关系。然后程序员可以直接通过内存访问文件,与任何其他内存驻留数据相同 - 甚至可以允许写入内存区域透明地映射回磁盘上的文件。

读取和写入内存映射文件可避免在使用read( )或write( )系统调用时发生的无关副本,其中必须将数据复制到用户空间缓冲区和从用户空间缓冲区复制数据。

四. 发生的Bug

mmap读取一个10G 大文件(系统镜像)时, 我犯了一些错误:

1.在使用mmap时,我想当然以为系统会自动的cache, 执行swamp in 和 swamp out。 实际上mmap如果不指定分页数和读取的字节,它会直接读取整个文件。导致随着find的操作不断执行,内存越来越小...这里有个好处是“延迟加载”,因此即使对于非常大的文件也使用少量RAM。 所以当我的的虚拟内存资源变得饱和时,会发生trash(颠簸),从而导致分页状态不变,排除了大多数应用程序级别的处理。这会导致计算机性能下降或崩溃。这种情况可以无限期地持续下去,直到用户关闭某些正在运行的应用程序或活动进程释放额外的虚拟内存资源。

https://stackoverflow.com/questions/31963124/memory-leakish-when-using-re-and-mmap

后来指定读取的offset,解决了这个问题。

offset = 0
length = mmap.ALLOCATIONGRANULARITY * 10

with open(p, "rb") as f:
    while offset < file_size:
    mm = mmap.mmap(f.fileno(), length=length, offset=offset,
                                     access=mmap.ACCESS_READ)
    offset += (mmap.ALLOCATIONGRANULARITY * 10)

2.在使用mmap时,由于mmap中使用了find()操作,它其实是socket操作,在不停地执行该操作时,导致WebSocket被阻塞,不能与前端进行交互。 由于这个原因,我最终还是放弃了mmap。

3.标准文件I/O的read()操作也可以指定字节读取,这是我想当然以为它一次读完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值