|
|
51CTO旗下网站
|
|
移动端

内存映射文件

《iOS和macOS性能优化:Cocoa、Cocoa Touch、Objective-C和Swift》第7章内存:陷阱和优化技巧,本章将围绕这个话题来进行讨论。除此之外,我们将展示一些陷阱,尤其是在Objective-C 代码中经常出现的问题。本节为大家介绍内存映射文件。

作者:李俊阳 等译来源:电子工业出版社|2018-07-17 17:52

内存映射文件

虽然文件的内存映射需要将文件读入内存,但内存映射文件也可以被视为可清除内存的一种特殊情况(请忽略在可清除内存出现之前,内存映射文件就已存在多年这个事实)。与可清除内存类似,映射文件的内存可随时被系统回收,但是与可清除内存所不同的是,这个操作对用户而言是完全开放的。由于我们为系统提供了内存数据源(底层文件),所以当我们访问该系统时,系统可以自动从数据源中恢复内存中的内容。由于这个操作可以随时进行,所以它甚至不需要去实际读取任何数据,只需要等到切实需要访问内存数据时即可。

对于NSData 的方法dataWithContentsOfURL:options:error:而言,通过使用相关的标记符(无论是NSDataReadingMappedIfSafe 还是NSDataReading-MappedAlways),便可以完全改变这些方法的作用,尽管最终结果(很大程度上)在语义层面上是完全不可区分的。

如果没有添加这些映射标记的话(如示例7.8 所示),这些函数将会分配内存,然后使用系统调用来从磁盘中读取数据,读取完数据后将其返回。由于已经将数据写入已分配的内存中了,因此这段内存现在被标记为“脏内存”。

示例7.8 读取文件

  1. NSData *data;  
  2. data=[NSData dataWithContentsOfFile:@"documentation.pdf"  
  3. options:0  
  4. error:nil]; 

如果使用了映射标记,如示例7.9 所示,在调用期间基本都是在创建地址空间,并且地址空间会被标记为“由相关文件所支持”。图7.7 则说明了这一过程。在整个调用过程中,实际上是没有分配任何物理内存的,也没有执行任何的I/O 操作;整个过程完全是在虚拟地址空间上进行的。只有这些映射过的地址被引用时,实际的I/O 操作才会执行。如果只访问部分文件,那么也只有这部分文件被读取(虽然系统可能会尝试选择去读取更多的内容,而不仅仅是所请求的那部分文件)。此外,由于进程还没有写入这些页面中,因此这些页面仍然还是“干净页面”。即便系统执行了I/O 操作从文件中将数据填充到页面中,这部分页面仍然还是“干净”的,因为内存中数据和内存背后的文件之间并没有什么区别。

示例7.9 映射文件

  1. NSData *mapped;  
  2. mapped=[NSData dataWithContentsOfFile:@"documentation.pdf"  
  3. options:NSDataReadingMappedAlways  
  4. error:nil]; 

当出现内存警告,系统想要释放一些内存空间时,干净页面和脏页面之间的差异也会变得异常明显。如果是将映射文件的部分内容读入内存中,那么系统可以简单地撤销映射,然后将特定的地址空间指向原始文件。如果是使用UNIX 的I/O 来读取文件的话,那么系统就无法撤销,所以系统要重用这块内存的话,就必须首先将内存中的内容暂存到交换文件中。对于超过物理RAM 容量的大文件,这很可能会导致系统直接从硬盘去读取数据,然后将需要读取的数据写入交换文件中,再从这个交换文件读取相关的内容,这个过程可能会反复进行。在iOS 上,很显然是不会有交换发生的,因此脏页面无法由操作系统回收。这里有两种选择:一是进程在收到内存警告响应时,去主动回收内存;二是操作系统必须将进程终止。

借助内存映射,首先可以减少内存的消耗。它可以在需要的时候快速、有效地回收内存空间,并且I/O 和相关处理操作在很多情况下都可以实现交叉存取,而不是在所有I/O 操作完成后才能开始处理。因此,文件访问基本上都要使用内存映射文件。

使用内存映射文件时有一个重要的注意事项:由于I/O 操作是惰性加载的,因此代码中很可能会出现未预料的I/O 问题。简而言之,这很有可能只是正在进行的操作内存访问消耗很大,从而导致I/O 的访问延迟,但是这些问题也很有可能会在数据交换时发生,所以可能并不是很糟糕。然而,在最坏的情况下,即物理卷被删除时,就很可能会导致延迟时间无限延长。例如,连接到外部挂载卷的网络中断,或者外部硬盘被拔除,如果发生了这种情况,所产生的错误就会导致内存访问时产生段冲突。虽然这个UNIX信号理论上是可以处理的,但是实际上它会导致程序崩溃。不过如果制定的映射标记是NSDataReadingMappedIfSafe 的话,那么系统只会将文件映射到安全的磁盘卷上(例如引导卷),也就是说,即便是发生突然消失的情况该磁盘也是安全的。

要减轻这个问题造成的破坏,有一种方法便是预读取所映射的数据:例如,在线程中运行一个所谓的页面迁移器,它用来创建映射文件的每个页面,然后就可以将这些页面读入内存。示例7.10 展示了一个最简单的页面迁移器实现。它假设页面容量为4096字节,然后从NSData 参数中的每个页面中读取1 个字节。更复杂的版本还可以在主计算线程之前进行,例如顺序处理较大的内存映射文件的时候。

示例7.10 页面迁移器

  1. int pageWalker( NSData *data )  
  2. {  
  3. int dummyResult=0;  
  4. const char *bytes=[data bytes];  
  5. for ( int i=0max=[data length]; i<max; i+=4096 ) {  
  6. dummyResult+=bytes[i];  
  7. }  
  8. return dummyResult;  

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Linux编程技术详解

本书全面介绍了Linux编程相关的知识,内容涵盖Linux基本知识、如何建立Linux开发环境、Linux开发工具、Linux文件系统、文件I/O操作、设备文...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊