• 使用分页方式读取超大文件的性能试验


    Read extreme large files using paging

    by Nobi Conmajia (conmajia@gmail.com)

    May 15th, 2012

    (注:本文使用FileStream类的Seek()和Read()方法完成文件读取,未使用特别读取方式。)

    我们在编程过程中,经常会和计算机文件读取操作打交道。随着计算机功能和性能的发展,我们需要操作的文件尺寸也是越来越大。在.NET Framework中,我们一般使用FileStream来读取、写入文件流。当文件只有数十kB或者数MB时,一般的文件读取方式如Read()、ReadAll()等应用起来游刃有余,基本不会感觉到太大的延迟。但当文件越来越大,达到数百MB甚至数GB时,这种延迟将越来越明显,最终达到不能忍受的程度。

    通常定义大小在2GB以上的文件为超大文件(当然,这个数值会随着科技的进步,越来越大)。对于这样规模的文件读取,普通方法已经完全不能胜任。这就要求我们使用更高效的方法,如内存映射法、分页读取法等。

    内存映射(Memory Mapping)

    内存映射的方法可以使用下面的Windows API实现。 

    LPVOID MapViewOfFile(HANDLE hFileMappingObject,
      DWORD dwDesiredAccess,
      DWORD dwFileOffsetHigh,
      DWORD dwFileOffsetLow,
      DWORD dwNumberOfBytesToMap);

    虽然使用方便,但使用上限制较多,比如规定的分配粒度(Windows下通常为64KB)等。下面贴出内存映射法实例代码供参考,但本文不做进一步讨论。 

    内存映射法(使用MapViewOfFile)

    分页读取法(Paging)

    另外一种高效读取文件的方法就是分页法,也叫分段法(Segmentation),对应的读取单位被称作页(Page)和段(Segment)。其基本思想是将整体数据分割至较小的粒度再进行处理,以便满足时间、空间和性能方面的要求。分页法的概念使用相当广泛,如嵌入式系统中的分块处理(Blocks)和网络数据的分包传输(Packages)。

    图1 数据局部性示意

    在开始研究分页法前,先来看看在超大文件处理中,最为重要的问题:高速随机访问。桌面编程中,分页法通常应用于文字处理、阅读等软件,有时也应用在大型图片显示等方面。这类软件的一个特点就是数据的局部性,无论需要处理的文件有多么大,使用者的注意力(也可以称为视口ViewPort)通常只有非常局部的一点(如几页文档和屏幕大小的图片)。这就要求了接下来,我们要找到一种能够实现高速的随机访问,而这种访问效果还不能和文件大小有关(否则就失去了高速的意义)。事实上,以下我们研究的分页法就是利用了「化整为零」的方法,通过只读取和显示用户感兴趣的那部分数据,达到提升操作速度的目的。

    图2 文件分页示意

    参考上图,假设计算机上有某文件F,其内容为「01234567890123456」(引号「」中的内容,不含引号,下同),文件大小为FileLength=17字节,以PageSize=3对F进行分页,总页数PageCount=6,得到页号为0~5的6个页面(图中页码=页号+1)。各页面所含数据如下表所示。

     
    页号  页码 内容 至头部偏移量 (Hex) 长度
     0  1  012   00 01 02  3
     1  2  345   03 04 05  3
     2  3  678   06 07 08  3
     3  4  901   09 0a 0b  3
     4  5  234   0c 0d 0e  3
     5  6  56   0f 10  2

    可以看到,最后一页的长度为2(最后一页长度总是小于PageSize)。

    当我们要读取「第n页」的数据(即页码=n)时,实际上读取的是页号PageNumber=n-1的内容。例如n=3时,PageNumber=2,数据为「678」,该页数据偏移量范围从0x06至0x08,长度为3(PageSize)。为便于讲述,在此约定:以下文字中,均只涉及页号,即PageNumber。

    参考图2,设当PageNumber=x时,页x的数据范围为[offsetStart, offsetEnd],那么可以用如下的代码进行计算(C#2.0)。 

    复制代码
     1 offsetStart = pageNumber * pageSize;
     2 
     3 if(offsetStart + pageSize < fileSize)
     4 {
     5     offsetEnd = offsetStart + pageSize;
     6 }
     7 else
     8 {
     9     offsetEnd = fileSize - 1;
    10 }
    复制代码

    我们常用的System.IO.FileStream类有两个重要的方法:Seek()和Read()。  

    复制代码
     1 // 将该流的当前位置设置为给定值。
     2 public override long Seek (
     3     long offset,
     4     SeekOrigin origin
     5 )
     6 
     7 // 从流中读取字节块并将该数据写入给定缓冲区中。
     8 public override int Read (
     9     [InAttribute] [OutAttribute] byte[] array,
    10     int offset,
    11     int count
    12 )
    复制代码

    利用这两个方法,我们可以指定每次读取的数据起始位置(offsetStart)和读取长度(offsetEnd - offsetStart),这样就可以读到任意指定的页数据。我们可以遍历读取所有页,这就相当于普通读取整个文件(实际操作中,一般不会有需求一次读取上GB的文件)。

    指定PageNumber,读取页数据

    由于每次读取的数据长度(PageSize)远远小于文件长度(FileSize),所以使用分页法能够只读取程序需要的那部分数据,最大化提高程序的运行效率。下表是笔者在实验环境下对分页法读取文件的运行效率的测试。

    CPU:Intel Core i3 380M @ 2.53GHz

    内存:DDR3 2048MB x2

    硬盘:TOSHIBA MK3265GSX (320 GB) @ 5400 RPM

    为尽量保证测试质量,测试前系统进行了重装、硬盘整理等维护操作。该硬盘性能测试结果如下图所示。 

    下面是为了测试分页法而制作的超大文件读取器界面截图,图中读取的是本次试验的用例之一Windows8消费者预览版光盘镜像(大小:3.40GB)。 

    本次测试选择了「大、中、小」3种规格的测试文件作为测试用例,分别为:

     
     # 文件名 文件内容 大小(KB)
     1  AlishaHead.png  Poser Pro 6贴图  11,611
     2  ubuntu-11.10-desktop-i386.iso  Ubuntu11.10桌面版镜像  711,980
     3  Windows8-ConsumerPreview-64bit-ChineseSimplified.iso  Windows8消费者预览版64位简体中文版镜像  3,567,486

    通过进行多次读取,采集到如下表A所示的文件读取数据结果。表中项目「分页(单页)」表示使用分页读取法,但设置页面大小为文件大小(即只有1页)进行读取。同样的,为了解分页读取的性能变化情况,使用普通读取方法(一次读取)采集到另一份数据结果,如下表B所示。

    对用例#1,该用例大小仅11MB,使用常规(单次)读取方法,仅用不到20ms即将全部内容读取完毕。而当采用分页法,随着分页大小越来越小,文件被划分为更多的页面,尽管随机访问文件内容使得文件操作更加方便,但在读取整个文件的时候,分页却带来了更多的消耗。例如当分页大小为1KB时,文件被分割为11,611个页面。读取整个文件时,需要重复调用11,611次FileStream.Read()方法,增加了很多消耗,如下图所示。(图中数据仅为全文读取操作对比)

    从图中可以看到,当分页尺寸过分的小(1KB)时,这种过度追求微粒化反而导致了操作性能下降。可以看到,即实现了微粒化,能够进行随机访问,同时仍保有一定量的操作性能,分页大小为64KB和1MB是不错的选择。实际上,上文介绍的MapViewOfFile函数的推荐分页大小正是64KB。

    对用例#2,该用例大小为695.29MB,达到较大的尺寸,因此对读取缓存(cache)需求较高,同时也对合适的分页尺寸提出了要求。可以看到,和用例#1不同,当文件尺寸从11.34MB增加到近700MB时,分页尺寸随之相应的扩大,是提高操作性能的好方法(下图中1MB分页)。

    对用例#3,该用例达到3.4GB大小,符合我们对超大文件的定义。通过前述2个用例的分析,可以推测,为获得最佳性能,分页大小需继续提高(比如从1MB提高到4MB)。由于本次试验时间仓促,考虑不周,未使用「边读取、边丢弃」的测试算法,导致分页读取用例#3的数据时,数据不断在内存中积累,最终引发System.OutOfMemoryException异常,使得分页读取完整文件这项测试不能正常完成。这一问题,需在下次的试验当中加以解决和避免。

    引发System.OutOfMemoryException

    尽管如此,通过试验,仍然可以清楚的看到,在常规文件(GB以下级别)操作中,分页法具有高度灵活性,但额外开销大,全文读取速度慢的问题。当操作超大文件(GB以上级别)时,分页法的优势开始显现。极高的数据读取灵活性带来的是和文件大小无关的随机页面访问速度(仅和分页大小有关)。在这个级别上,文件大小往往远远超过常规方法所能读取的最大值(0x7FFFFFFF),因此只有使用分页法,积少成多,才能完成读取完整文件的工作。

    分页法使用简单,思路清晰,具有很高的灵活性和与文件长度无关的随机读取能力,最大支持文件大小理论上能够达到8,388,608 TB(Int64)。但同时它也具有额外开销大的特点,因此不适合小文件的操作。

    通过扩展该方法,我们可以几乎在所有需要大量、重复、大范围算法处理的程序中加以应用分页法的「化整为零」思想,以减少计算粒度,实现计算的可持续进行。

    分页法,以及上文提到的内存映射法,其实均早已出现多年,更是广泛应用于各个行业。笔者之所以仍旧撰写此文,一则锻炼自己的编程能力、语言归纳能力、文字写作能力,二则加深对方法的理解,通过试验得出的现象来深入方法的本质。鉴于笔者才疏学浅,在此妄言,有些词不达意,甚至出现谬误之处,还望各位读者多加批评、指正。

    野比春熊

    conmajia@gmail.com

    May 15th, 2012

    (全文完)

     
    标签: C#测试文件评测分页超大

    05 2012 档案

     
    摘要: 枯燥的学习往往让人无精打采。那么,来点有意思的情景训练如何?现在,就跟我一起,拿大名鼎鼎的Safari开刀,一步步剖析它的细节,做出我们自己的"Safari"……阅读全文
    posted @ 2012-05-17 22:20 野比 阅读(1879) | 评论 (18) 编辑
     
    摘要: 我们在编程过程中,经常会和计算机文件读取操作打交道。随着计算机功能和性能的发展,我们需要操作的文件尺寸也是越来越大。在.NET Framework中,我们一般使用FileStream来读取、写入文件流。当文件只有数十kB或者数MB时,一般的文件读取方式如Read()、ReadAll()等应用起来游刃有余,基本不会感觉到太大的延迟。但当文件越来越大,达到数百MB甚至数GB时,这种延迟将越来越明显,最终达到不能忍受的程度……阅读全文
    posted @ 2012-05-15 21:52 野比 阅读(3530) | 评论 (20) 编辑
     
    摘要: 体感游戏,从Wii打响第一枪开始至今已经很多年了。NDS的吹气玩法,Microsoft的Kinect等等,无不在提醒着我们,体感控制,正在日趋完善。前段时间看到一个科技视频,美国科学家使用布置在房间的多个摄像机同步监控,然后实时生成房间、物品和人的3D数据模型,实现了人体体感控制任意虚拟物品的科幻效果。看完这个视频,我不由感慨。人们想象了很久的超拟真虚拟现实,看来在不久的将来,真的会成为「现实」了……阅读全文
    posted @ 2012-05-14 09:33 野比 阅读(2147) | 评论 (12) 编辑
     
    摘要: 人脸识别特指利用分析比较人脸视觉特征信息进行身份鉴别的计算机技术。人脸识别是一项热门的计算机技术研究领域,它属于生物特征识别技术,是对生物体(一般特指人)本身的生物特征来区分生物体个体……阅读全文
    posted @ 2012-05-11 17:45 野比 阅读(358) | 评论 (1) 编辑
     
    摘要: 本文的点子是「把图形画到声音里去」,尤其是画到声音的频域之中……阅读全文
    posted @ 2012-05-10 16:08 野比 阅读(111) | 评论 (0) 编辑
     
    摘要: 相信大家对风行全球,迷倒无数潮男潮女,把妹达人必备的苹果的大名已经是如雷贯耳了。苹果流行的一个重要原因是它的界面很骚,而且经常创新,通俗点说,就是独领风骚。 今天我们要研究的,就是如何像 iOS 一样,实现在图标上叠加的提醒数字这个功能……阅读全文
    posted @ 2012-05-10 16:03 野比 阅读(409) | 评论 (0) 编辑
     
    摘要: WinRAR、FlashFXP这类软件的文件列表中,各个文件图标均为系统中的实际关联图标。 像知道怎么获取这些图标吗?请慢慢阅读本文……阅读全文
    posted @ 2012-05-10 16:00 野比 阅读(61) | 评论 (0) 编辑
     
    摘要: 电子爱好者有一个优点:不「挑食」。我们向来是有什么用什么,绝不拘泥于某几种特定器材,我们善于适应艰苦的条件,我们会充分利用手边的藏品, 做出来的东西,绝对和苦等器材到货的作坊有得一拼……阅读全文
    posted @ 2012-05-10 15:56 野比 阅读(76) | 评论 (0) 编辑
     
    摘要: 人脸检测 理论的东西,我还真不太了解,总之就是利用 EmguCV 的层叠识别加人脸训练库,然后对图像进行分析。现成的训练库我放到源码里了,haarcascade_eye.xml(眼睛训练模型)和 haarcascade_frontalface_alt_tree.xml(正面人脸训练模型)……阅读全文
    posted @ 2012-05-10 15:52 野比 阅读(162) | 评论 (3) 编辑
     
    摘要: 2005 年微软发布了 Visual Studio 2005,里面提供了大量新增的控件和类。 FlowLayoutPanel 也是从这时进入了我们的视线。 MSDN 里是这么介绍它的:「引用FlowLayoutPanel 控件沿着水平或垂直流向排列其内容。它的内容可以从一行换到下一行或从一列换到下一列。或者,还可以对它的内容进行剪裁,而不是进行换行……」阅读全文
    posted @ 2012-05-10 15:47 野比 阅读(119) | 评论 (0) 编辑
     
    摘要: 如果你正在设计一款和硬件产品相关的控制软件,如果你的上司要求你把界面设计得尽量接近硬件外观,如果你是一个铁杆电子迷,又需要自己编写软件,那么,你需要阅读这篇文章了……阅读全文
    posted @ 2012-05-10 15:44 野比 阅读(230) | 评论 (2) 编辑
     
    摘要: 我们平时编写软件,常常觉得软件界面看起来很普通,没有出彩的地方。看到别人的软件美观大方,样式华丽,心中难免羡慕。而且很多控件都没有公开,也没有第三方控件可用。这时就要发挥我们的主观能动性,自己动手DIY……阅读全文
    posted @ 2012-05-10 15:30 野比 阅读(196) | 评论 (3) 编辑
     
    摘要: 很多流行的 WinForm 程序设计都会提供形象的可视化数据流动记录功能, 如 FlashGet 及其衍生软件的悬浮窗网速监视图,Windows 任务管理器的 CPU、内存使用图等。 构思 为了在我们自己的程序中实现这种效果,就需要研究、分析它们的原理,掌握其规律,然后加以实现。 很明显,从软件可重用性以及各种随之而来的好处考虑……阅读全文
    posted @ 2012-05-10 15:12 野比 阅读(258) | 评论 (0) 编辑
     
    摘要: 真想睡醒就变成「类库超人」,奔跑在挨踢事业的最前沿……
    阅读全文
    posted @ 2012-05-10 14:24 野比 阅读(86) | 评论 (0) 编辑
  • 相关阅读:
    php 魔鬼训练
    php 验证码
    php 经典分页(推荐和laypage配合)
    php 过时的sql操作库(学习使用)
    php 上传图片
    npm 国内淘宝镜像cnpm、设置淘宝源
    gulp + webpack + sass 学习
    关于分页的解决方案收集
    SVN的学习和安装
    使用和学习 ES2015
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2509413.html
Copyright © 2020-2023  润新知