内存映射是将磁盘上某文件的一部分或整个文件映射到应用程序地址空间内某个地址范围的一种机制。然后,应用程序可采用与访问动态内存相同的方法访问磁盘上的文件。与使用fread和fwrite等函数相比,能够加快文件的读取和写入速度。
实际上,就是将文件在磁盘上的地址映射为虚拟内存。
1.读写二进制文件
将int16二进制文件映射到内存中,跳过前1000个字节。注意是字节,不是数的个数。对于int16类型来说,相当于跳过前500个数。
m = memmapfile('E:\BDSdata\IF_0115_20M.bin','Format','int16','offset',1000)
可以看到,m是一种memmapfile数据类型。(本质上就是个结构体)
此时,文件已经被映射到虚拟内存上,可以利用m.Data进行读取。
如果想要进行数据写入的话,需要将Writable值设为true,然后直接修改m.Data值,即可完成对磁盘上文件的修改。
m.Writable = true; m.Data(1e8+2) = 0; % 将第1e8+2个数从-261修改为0 m.Data(1e8:1e8+5)
2.读写文本文件
%% TODO
3.读写性能实战
在之前一篇关于并行连续滤波的文章中,其中的一个重要的限制就是,文件的读取与写入不能并行,导致性能提升有限。
这里采用内存映射的读取方式,从而实现了多进程读写。滤波器函数lowpassFilter与多进程滤波函数multiProcessFilter都是上文中已经出现的函数。
function main() objFilter = lowpassFilter; % parpool(6) tic;multiProcessFilter(objFilter,'raw.bin','multi.bin',6);toc % 时间已过 44.099015 秒。 tic;memMapMultiFilter(objFilter,'raw.bin','memMap.bin',6);toc % 时间已过 31.000704 秒。 visdiff('multi.bin','memMap.bin','binary'); % 这些文件相同 end function memMapMultiFilter(objFilter,rawFile,processFile,M) % 复制一份原文件,在新文件的基础上写入得到滤波后的文件 copyfile(rawFile,processFile,'f'); % 将原文件映射到虚拟内存 raw = memmapfile(rawFile,'Format','int8'); % 将新文件映射到虚拟内存 filtered = memmapfile(processFile,'Format','int8','Writable',true); overlap = length(objFilter.States); len = length(raw.Data); workload = 2e7; numOfWork = round(len/M/workload); idx = round(linspace(1,len,numOfWork*M+1)); idx_lap = idx + overlap; idx_lap(end) = len; spmd(M) for ii = 1:numOfWork jj = (labindex-1)*numOfWork + ii; if jj == 1 fdata = filter(objFilter,raw.Data(1:idx_lap(2))); filtered.Data(1:idx_lap(2)) = int8(fdata); else fdata = filter(objFilter,raw.Data(idx(jj):idx_lap(jj+1))); filtered.Data(idx_lap(jj):idx_lap(jj+1)) = int8(fdata(overlap+1:end)); end end end end
可以看到,使用68阶FIR滤波器对1.2G的int8文件进行滤波,使用fread/fwrite函数进行读写,6进程滤波需要约44秒。而使用内存映射memmapfile函数,6进程滤波仅需31秒,速度提高了大约30%。
总结
- 使用内存映射比标准I/O函数拥有更快的访问速度,使用操作系统虚拟内存功能读取和写入数据,而不必分配数据缓冲区。
- 利用内存映射,可以像访问数组一样访问文件,直接使用MATLAB的索引操作,更加简洁。
- 利用内存映射,可以在不同的函数之间共享数据。(类似于共享内存)
- 内存映射的释放是一个坑点。官方文档上面声称,退出创建内存映射的函数时,会自动清除掉内存映射,实测某些情况下不会,可能导致无法对文件进行操作,此时只能重启MATLAB。为了避免这点,建议在函数结尾用clear命令清除内存映射。