开发文章

VC读取大文件之创建文件映射及文件写入效率测试

文件太大,没法一次读取到内存进行操作?Windows提供了内存映射API来读取大文件,与普通文件读取相比,内存映射效率比较高。

从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如图2中过程1,然后再将这些数据拷贝到用户空间,如图2中过程2,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。(引用自http://www.panshy.com/articles/201611/dev-2811.html)

本文主要以代码的方式演示读取大文件的API使用,顺带测试了缓冲区大小与写文件速度的关系,以及绘制文件写入速率图。

复制内容到剪贴板
  1. HANDLE hFile = NULL;    
  2.     HANDLE hFileMap = NULL;    
  3.     LARGE_INTEGER liResult;    
  4.     hFile = CreateFile(L"e:\\1.zip", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    
  5.     if ( INVALID_HANDLE_VALUE == hFile )    
  6.     {    
  7.         goto __TestEnd;    
  8.     }    
  9.     //创建文件映射    
  10.     hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);    
  11.     if ( NULL == hFileMap )    
  12.     {    
  13.         goto __TestEnd;    
  14.     }    
  15.     //得到系统分配粒度    
  16.     SYSTEM_INFO si;    
  17.     GetSystemInfo(&si);    
  18.     DWORD dwSysGran = si.dwAllocationGranularity;    
  19.     //得到文件大小    
  20.     LARGE_INTEGER lFileSize;    
  21.     GetFileSizeEx(hFile, &lFileSize);    
  22.     CloseHandle(hFile);    
  23.     hFile = INVALID_HANDLE_VALUE;    
  24.     //性能方面,都知道容器大小了就先初始化大小,免得用vector自己的扩容机制浪费CPU    
  25.     vecTime.resize(200, 0);    
  26.     char szPath[MAX_PATH] = {0};    
  27.     for ( int i=1; i<=200; ++i )    
  28.     {    
  29.         __int64 qwFileOffset = 0;    
  30.         __int64 qwFileSize = lFileSize.QuadPart;    
  31.         DWORD dwDataLen = 0;    
  32.         FILE* fp = NULL;    
  33.         DWORD dwBlockBytes = i*dwSysGran;    
  34.         if ( lFileSize.QuadPart<dwBlockBytes )    
  35.             dwBlockBytes = lFileSize.QuadPart;    
  36.         sprintf(szPath, "d:\\test\\%d.zip", i);    
  37.         //DeleteFileA(pFile);    
  38.         {    
  39.             //PERFOR_TEST("复制文件测试4KB_1");    
  40.             CPerforTest test(&liResult);    
  41.             fp = fopen(szPath, "ab+");    
  42.             while( qwFileSize>0 )    
  43.             {    
  44.                 dwDataLen = qwFileSize<dwBlockBytes? qwFileSize : dwBlockBytes;    
  45.                 LPBYTE lpData = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ|FILE_MAP_WRITE, (DWORD)(qwFileOffset>>32),    
  46.                     (DWORD)(qwFileOffset&0xffffffff), dwDataLen);    
  47.                 if ( NULL == lpData )    
  48.                     break;    
  49.                 //把文件复制到另一个目录下,写文件操作    
  50.                 //fp = fopen(szPath, "ab+");//追加方式写入文件,不存在则创建    
  51.                 fwrite(lpData, dwDataLen, 1, fp);    
  52.                 //fclose(fp);    
  53.                 UnmapViewOfFile(lpData);    
  54.                 qwFileOffset += dwDataLen;    
  55.                 qwFileSize -= dwDataLen;    
  56.             }    
  57.             fclose(fp);    
  58.         }    
  59.         vecTime[i-1] = liResult.LowPart/300;    
  60.         Sleep(100);    
  61.     }    
  62. __TestEnd:    
  63.     DWORD dwError = GetLastError();    
  64.     //内核句柄清理工作    
  65.     if ( hFile != INVALID_HANDLE_VALUE )    
  66.     {    
  67.         CloseHandle(hFile);    
  68.         hFile = INVALID_HANDLE_VALUE;    
  69.     }    
  70.     if ( hFileMap )    
  71.     {    
  72.         CloseHandle(hFileMap);    
  73.         hFileMap = NULL;    
  74.     }    

值得注意的是,内存映射大小必须是系统分配大小基数的倍数。每次读完一段,我们就把这个文件指针位置qwFileOffset后移一段直到读完。还有就是,必须是有多少读多少,最后一次往往其空间比正常分配的小,我们需要计算分配空间:dwDataLen = qwFileSize<dwBlockBytes? qwFileSize : dwBlockBytes;不然的话,分配空间大于文件剩余大小,MapViewOfFile就会失败。

统计绘制效率:

复制内容到剪贴板
  1. //一次冒泡排序找到最小的那个数及其索引,索引很重要,我们可以知道每次写入多大时效率最高    
  2.     DWORD dwMinTime = vecTime[0];    
  3.     size_t nIndex = 0;    
  4.     for ( size_t i=1; i<vecTime.size(); ++i )    
  5.     {    
  6.         if ( vecTime[i]<dwMinTime )    
  7.         {    
  8.             dwMinTime = vecTime[i];    
  9.             nIndex = i;    
  10.         }    
  11.     }    
  12.     g_dwMinTime = dwMinTime;    
  13.     g_nBuffSize = (nIndex+1)*dwSysGran/1024;//换算成KB    
  14.     //通知窗口刷新绘制    
  15.     g_bInit = true;    
  16.     InvalidateRect(hWnd, NULL, TRUE);    
  17.     BringWindowToTop(hWnd);    
  18.     return 0;    

绘制效率图:

复制内容到剪贴板
  1. case WM_SIZE:    
  2.     {    
  3.         g_bSizeChange = true;    
  4.         break;    
  5.     }    
  6. case WM_PAINT:    
  7.     {    
  8.         hdc = BeginPaint(hWnd, &ps);    
  9.         // TODO: 在此添加任意绘图代码...    
  10.         RECT rcClient;    
  11.         GetClientRect(hWnd, &rcClient);    
  12.         if ( g_bSizeChange )    
  13.         {//窗口大小改变了,需要我们重新创建对应大小的缓冲DC    
  14.             if ( g_hMemDC )    
  15.             {    
  16.                 DeleteDC(g_hMemDC);    
  17.                 DeleteObject(g_hMemBmp);    
  18.             }    
  19.             g_hMemDC = CreateCompatibleDC(hdc);    
  20.             g_hMemBmp= CreateCompatibleBitmap(hdc, rcClient.right-rcClient.left, \    
  21.                 rcClient.bottom-rcClient.top);    
  22.             SelectObject(g_hMemDC, g_hMemBmp);    
  23.             g_bSizeChange = false;    
  24.         }    
  25.         if ( g_bInit )    
  26.         {    
  27.             HPEN hOldPen = (HPEN)SelectObject(g_hMemDC, g_hPen);    
  28.             POINT pt;    
  29.             MoveToEx(g_hMemDC, 0, 0, &pt);    
  30.             for ( size_t i=0; i<vecTime.size(); ++i )    
  31.             {    
  32.                 LineTo(g_hMemDC, (i+1)*5, rcClient.bottom-vecTime[i]);    
  33.             }    
  34.             ::SelectObject(g_hMemDC, hOldPen);    
  35.             wchar_t szText[100] = {0};    
  36.             swprintf(szText, L"缓冲区为:%u KB时,写入文件用时最短:%u", g_nBuffSize, g_dwMinTime);    
  37.             SetTextColor(g_hMemDC, RGB(255,0,0));    
  38.             SetBkMode(g_hMemDC, TRANSPARENT);    
  39.             RECT rcText = {10,0,600,40};    
  40.             DrawText(g_hMemDC, szText, wcslen(szText), &rcText, DT_LEFT|DT_VCENTER|DT_SINGLELINE);    
  41.         }    
  42.         ::BitBlt(hdc, 0, 0, rcClient.right, rcClient.bottom, g_hMemDC, 0, 0, SRCCOPY);    
  43.         EndPaint(hWnd, &ps);    
  44.     }    
  45.     break;    

由于循环执行200次,I/O操作相对耗时,为了防止把电脑卡死了,就在每次写完Sleep(100);绘制部分的视图大小有限有的区域无法绘制出来就会出现断线,绘制结果图:

缓冲区变化范围.jpg

我的测试文件是一个大小为15M左右的文件,缓冲区变化范围:1×63KB----200×63KB,这里最优的竟然是每次写入大约1.7M数据时。

感谢 疯狂-的-蜗牛 支持 磐实编程网 原文地址:
blog.csdn.net/mfcing/article/details/43734445

文章信息

发布时间:2016-11-28

作者:疯狂-的-蜗牛

发布者:aquwcw

浏览次数: