<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      windows 稀疏文件 (sparse file) 的一個(gè)實(shí)用場(chǎng)景——解決 SetEndOfFile 占據(jù)磁盤空間引入的性能問(wèn)題

      前言

      之前寫過(guò)一篇文章說(shuō)明文件空洞:《[apue] 文件中的空洞》,其中提到了 windows 稀疏文件是制造空洞的一種方式,但似乎沒什么用處,如果僅僅處理占用磁盤空間的場(chǎng)景,使用SetEndOfFile 就足夠了。

      后來(lái)在實(shí)際工作中,發(fā)現(xiàn)稀疏文件在解決一個(gè)性能問(wèn)題方面,有著不可替代的作用,下面且聽我一一道來(lái)。

      問(wèn)題現(xiàn)象

      公司的文件下載產(chǎn)品,為了預(yù)防在下載過(guò)程中因磁盤空間不足而失敗,在 windows 上采取預(yù)分配的策略,在下載任務(wù)開始前就占據(jù)了相當(dāng)于文件長(zhǎng)度的磁盤空間。由于數(shù)據(jù)源也包括從 P2P 處獲取的數(shù)據(jù),導(dǎo)致寫入時(shí)并不是順序的,存亂序?qū)懭氲那闆r,這些信息都存儲(chǔ)在數(shù)據(jù)庫(kù)中,當(dāng)應(yīng)用重啟時(shí),會(huì)從數(shù)據(jù)庫(kù)加載塊信息,繼續(xù)對(duì)未落盤的塊進(jìn)行網(wǎng)絡(luò)請(qǐng)求。

      整個(gè)邏輯看起來(lái)沒有問(wèn)題,然而實(shí)測(cè)在距離當(dāng)前寫入位置較遠(yuǎn)的地方寫入塊時(shí),會(huì)發(fā)現(xiàn)落盤速度極慢。

      下面用一個(gè)簡(jiǎn)單的例子驗(yàn)證這一點(diǎn),這個(gè) demo 創(chuàng)建一個(gè)大文件,通常十幾個(gè) GB,具體長(zhǎng)度由用戶輸入決定:

      #include <iostream>
      #include <Windows.h>
      
      int main(int argc, char* argv[])
      {
          if (argc < 3)
          {
              std::cout << "Usage: sparsefile file length (in GB)\n";
              return 1; 
          }
      
          int ret = 0; 
          HANDLE file_handle = INVALID_HANDLE_VALUE;
          LARGE_INTEGER pos = { 0 };
      
          do
          {
              file_handle = CreateFileA(argv[1], (GENERIC_READ | GENERIC_WRITE),
                  FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
              if (file_handle == INVALID_HANDLE_VALUE)
              {
                  std::cout << "CreateFile failed, error " << GetLastError() << std::endl;
                  ret = 2;
                  break;
              }
      
              pos.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024;  // unit in GB
              if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
              {
                  std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
                  ret = 3; 
                  break;
              }
      
              if (!::SetEndOfFile(file_handle))
              {
                  std::cout << "SetEndOfFile failed, error " << GetLastError() << std::endl;
                  ret = 4; 
                  break;
              }
          } while (0); 
      
          if (ret == 0)
              std::cout << "create file with length " << pos.QuadPart << " success, path " << argv[1] << std::endl;
      
          if (file_handle)
              CloseHandle(file_handle); 
      
          return ret; 
      }

      執(zhí)行以下命令創(chuàng)建文件:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 10
      SetEndOfFile failed, error 112
      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      create file with length 4294967296 success, path movie.mp4

      分區(qū) D: 目前僅有 5GB 空間,所以創(chuàng)建 10GB 的文件會(huì)失敗,錯(cuò)誤碼 112 即磁盤空間不足;創(chuàng)建 4GB 的可以成功:

      文件屬性中,文件大小與占用空間是一致的,沒有空洞。

      在完成預(yù)分配后,立即在尾部寫入 1 個(gè)字符,以模擬真實(shí)的使用場(chǎng)景:

      ...
              pos.QuadPart--; 
              if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
              {
                  std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
                  ret = 30;
                  break;
              }
      
              DWORD bytes = 0; 
              c_timer t; 
              if (!::WriteFile(file_handle, " ", 1, &bytes, NULL) || bytes != 1)
              {
                  std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
                  ret = 31;
                  break;
              }
      
              int elapse = t.get_interval(); 
              std::cout << "write file elapse " << elapse << " ms" << std::endl;
      ...

      注意需要前移文件指針,并在寫入前后記錄耗時(shí),c_timer 封裝了 windows 高精度計(jì)時(shí)器,可以精確到毫秒。下面是程序輸出:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      write file elapse 19502 ms
      create file with length 4294967295 success, path movie.mp4

      寫入 4G 文件末尾 1 個(gè)字節(jié),消耗了將近 20s,這還是 SSD,如果換作機(jī)械硬盤,耗時(shí)會(huì)更久。這就是 SetEndOfFile 占據(jù)磁盤空間產(chǎn)生的性能問(wèn)題。

      問(wèn)題分析

      網(wǎng)上搜索了一番,大概明白這個(gè) 20s 耗時(shí)是怎么回事了,簡(jiǎn)單說(shuō)明一下:當(dāng)使用 SetEndOfFile 預(yù)分配磁盤空間時(shí),文件系統(tǒng)會(huì)將原始?jí)K分配給文件,這些塊可能包含用戶之前刪除的文件數(shù)據(jù),如果不寫入新內(nèi)容就讀取,可能會(huì)讀到用戶隱私!黑客完全可以利用這個(gè)漏洞盜取用戶數(shù)據(jù),從而導(dǎo)致嚴(yán)重的安全問(wèn)題。

      為此,windows 在應(yīng)用寫入原始?jí)K時(shí),會(huì)用零填充文件指針到上次寫入位置之間的區(qū)域 (沒有上次寫入位置就從文件頭開始),保證這些內(nèi)容被清空,從而避免原始信息泄漏。上例中的耗時(shí)大部分時(shí)間用于填充整個(gè) 4GB 文件了,速度當(dāng)然快不起來(lái)。

      解決方案

      目前有兩種解決方案,下面分別說(shuō)明

      提權(quán) + SetFileValidData

      這種就是官方提出的解決方案,其實(shí)是給 service 打的補(bǔ)丁,MSDN 上寫的很詳細(xì)了:

      You can use the SetFileValidData function to create large files in very specific circumstances so that the performance of subsequent file I/O can be better than other methods. Specifically, if the extended portion of the file is large and will be written to randomly, such as in a database type of application, the time it takes to extend and write to the file will be faster than using SetEndOfFile and writing randomly. In most other situations, there is usually no performance gain to using SetFileValidData, and sometimes there can be a performance penalty.

      需要注意的是,這個(gè)函數(shù)需要用戶擁有 SeManageVolumePrivilege 權(quán)限。以管理員身份運(yùn)行時(shí),默認(rèn)是沒有這個(gè)權(quán)限的:

      D:\test\sparsefile\Release>whoami /priv
      
      特權(quán)信息
      ----------------------
      
      特權(quán)名                                    描述                               狀態(tài)
      ========================================= ================================== ======
      SeIncreaseQuotaPrivilege                  為進(jìn)程調(diào)整內(nèi)存配額                 已禁用
      SeMachineAccountPrivilege                 將工作站添加到域                   已禁用
      SeSecurityPrivilege                       管理審核和安全日志                 已禁用
      SeTakeOwnershipPrivilege                  取得文件或其他對(duì)象的所有權(quán)         已禁用
      SeLoadDriverPrivilege                     加載和卸載設(shè)備驅(qū)動(dòng)程序             已禁用
      SeSystemProfilePrivilege                  配置文件系統(tǒng)性能                   已禁用
      SeSystemtimePrivilege                     更改系統(tǒng)時(shí)間                       已禁用
      SeProfileSingleProcessPrivilege           配置文件單一進(jìn)程                   已禁用
      SeIncreaseBasePriorityPrivilege           提高計(jì)劃優(yōu)先級(jí)                     已禁用
      SeCreatePagefilePrivilege                 創(chuàng)建一個(gè)頁(yè)面文件                   已禁用
      SeBackupPrivilege                         備份文件和目錄                     已禁用
      SeRestorePrivilege                        還原文件和目錄                     已禁用
      SeShutdownPrivilege                       關(guān)閉系統(tǒng)                           已禁用
      SeDebugPrivilege                          調(diào)試程序                           已禁用
      SeSystemEnvironmentPrivilege              修改固件環(huán)境值                     已禁用
      SeChangeNotifyPrivilege                   繞過(guò)遍歷檢查                       已啟用
      SeRemoteShutdownPrivilege                 從遠(yuǎn)程系統(tǒng)強(qiáng)制關(guān)機(jī)                 已禁用
      SeUndockPrivilege                         從擴(kuò)展塢上取下計(jì)算機(jī)               已禁用
      SeManageVolumePrivilege                   執(zhí)行卷維護(hù)任務(wù)                     已禁用
      SeImpersonatePrivilege                    身份驗(yàn)證后模擬客戶端               已啟用
      SeCreateGlobalPrivilege                   創(chuàng)建全局對(duì)象                       已啟用
      SeIncreaseWorkingSetPrivilege             增加進(jìn)程工作集                     已禁用
      SeTimeZonePrivilege                       更改時(shí)區(qū)                           已禁用
      SeCreateSymbolicLinkPrivilege             創(chuàng)建符號(hào)鏈接                       已禁用
      SeDelegateSessionUserImpersonatePrivilege 獲取同一會(huì)話中另一個(gè)用戶的模擬令牌 已禁用

      為此需要給進(jìn)程提權(quán):

      ...
              if (!EnablePrivilege(SE_MANAGE_VOLUME_NAME, TRUE))
              {
                  std::cout << "EnablePrivilege failed, error " << GetLastError() << std::endl;
                  ret = 10;
                  break;
              }
      ...

      EnablePrivilege 封裝了 OpenProcessTokenLookupPrivilegeValueAdjustTokenPrivileges 幾個(gè)調(diào)用,文章末尾有完整的代碼實(shí)現(xiàn),提權(quán)代碼需要放在CreateFile之前。接著設(shè)置文件有效數(shù)據(jù)長(zhǎng)度:

      ...
              if (!::SetFileValidData(file_handle, pos.QuadPart))
              {
                  std::cout << "SetFileValidData failed, error " << GetLastError() << std::endl;
                  ret = 10;
                  break;
      
              }
      ...

      這段代碼需要插入到 SetEndOfFile WriteFile 之間。以管理員身份啟動(dòng)控制臺(tái)后有如下輸出:

      D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
      write file elapse 0 ms
      create file with length 4294967295 success, path movie.mp4

      尾部寫入數(shù)據(jù)的耗時(shí)大大降低了。如果不以管理員身份啟動(dòng),會(huì)報(bào)下面的錯(cuò)誤:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      EnablePrivilege failed, error 1300

      提權(quán)失敗。如果未提權(quán)或 EnablePrivilege位于 CreateFile 之后,則報(bào)下面的錯(cuò)誤:

      D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
      SetFileValidData failed, error 1314

      權(quán)限不足。

      稀疏文件

      如果無(wú)法獲取管理員身份進(jìn)行提權(quán),則需要借助 NTFS 稀疏文件,在 WriteFile 之前加入下面的代碼即可:

      ...
              DWORD temp = 0;
              if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
              {
                  std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
                  ret = 12;
                  break;
              }
      ...

      以普通用戶身份運(yùn)行:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      write file elapse 0 ms
      create file with length 4294967295 success, path movie.mp4

      耗時(shí)也大大降低了。稀疏文件依賴于文件系統(tǒng)的支持,可查詢某個(gè) Volume 是否支持稀疏文件:

          CHAR szVolName[MAX_PATH], szFsName[MAX_PATH];
          DWORD dwSN, dwFSFlag, dwMaxLen, nWritten;
          BOOL bSuccess;
          HANDLE hFile;
          bSuccess = GetVolumeInformation(NULL,
              szVolName,
              MAX_PATH,
              &dwSN, 
              &dwMaxLen, 
              &dwFSFlag, 
              szFsName,
              MAX_PATH);
      
          if (!bSuccess) 
          {
              printf("errno:%d", GetLastError());
              return -1;
          }
      
          printf("vol name:%s \t fs name:%s sn: %d.\n", szVolName, szFsName, dwSN);
          if (dwFSFlag & FILE_SUPPORTS_SPARSE_FILES)
              printf("support sparse file.\n");
          else
              printf("no support sparse file.\n");

      或查詢某個(gè)文件是否為稀疏文件:

      // HANDLE hFile;
      BY_HANDLE_FILE_INFORMATION stFileInfo;
      GetFileInformationByHandle(hFile, &stFileInfo);
      if(stFileInfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)
          printf("is sparse file.\n");
      else
          printf("no sparse file.\n");

      也可以通過(guò) fsutil 命令快速確認(rèn):

      PS D:\test\sparsefile\Release> fsutil.exe sparse
      ---- 支持 SPARSE 命令 ----
      
      queryflag       查詢稀疏
      queryrange      查詢范圍
      setflag         設(shè)置稀疏
      setrange        設(shè)置稀疏范圍
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
      該文件被設(shè)為稀疏
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
      分配的范圍[1]: 偏移: 0xffff0000  長(zhǎng)度: 0x10000

      fsutil 的 sparse 子命令查詢文件是否稀疏 (queryflag)、以及有效數(shù)據(jù)的范圍 (queryrange)。

      這里 queryrange 返回的長(zhǎng)度 0x10000 對(duì)應(yīng)的空間占用是 64KB,查看文件屬性:

      也是 64KB。看起來(lái)即使寫入 1 個(gè)字節(jié),windows 也會(huì)分配一個(gè) 64K 的塊并將其標(biāo)記為修改。

      帶來(lái)的新問(wèn)題

      兩種方案相比較,稀疏文件方式無(wú)需獲取管理員權(quán)限,看起來(lái)似乎更“親民”一些,不過(guò)也有它自己的問(wèn)題:無(wú)法真正占據(jù)磁盤空間。考察上例中 4GB 文件的屬性,占用空間僅 64KB,此時(shí)再生成一個(gè) 4GB 的文件,仍能成功。然而在實(shí)際寫入過(guò)程中,注定有一個(gè)文件因磁盤空間不足而失敗,甚至兩個(gè)都失敗。修改 demo 以演示這個(gè)場(chǎng)景:

      ...
              long long file_size = pos.QuadPart; 
              pos.QuadPart = 0;
              if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
              {
                  std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
                  ret = 20;
                  break;
              }
      
              char buf[4096] = { 1 };
              c_timer t;
              DWORD bytes = 0;
              for (int i = 0; i < file_size; i += 4096)
              {
                  if (!::WriteFile(file_handle, buf, 4096, &bytes, NULL) || bytes != 4096)
                  {
                      std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
                      ret = 21;
                      break;
                  }
              }
      
              int elapse = t.get_interval();
              std::cout << "write file elapse " << elapse << " ms" << std::endl;
      ...

      寫入整個(gè)文件,每次 4KB,執(zhí)行此程序的同時(shí),通過(guò) dd 開啟另外一個(gè) 4GB 文件的寫入:

      $ dd if=/dev/zero of=./movie1.mp4 bs=1M count=1024
      dd: error writing './movie1.mp4': No space left on device
      335+0 records in
      334+0 records out
      350224384 bytes (350 MB, 334 MiB) copied, 2.08525 s, 168 MB/s

      因磁盤剩余空間不足 8GB,最終 dd & demo 都會(huì)報(bào)錯(cuò)退出:

      PS D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
      WriteFile failed, error 112, written 0
      write file elapse 16420 ms

      這表明稀疏文件即使占據(jù)了空間,也會(huì)受磁盤實(shí)際剩余空間的影響,真是占了個(gè)寂寞!

      換句話,稀疏文件使 SetEndOfFile 的磁盤空間占用能力"消失"了,不過(guò)后者的剩余空間檢查能力還在當(dāng)剩余空間不足 4GB 時(shí),demo 會(huì)直接在 SetEndOfFile 處失敗:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      SetEndOfFile failed, error 112

      所以上例不能采用 dd 預(yù)先寫入 4GB 的方式進(jìn)行測(cè)試,兩個(gè)程序必需一先一后啟動(dòng)。

      行文至此,正好驗(yàn)證一個(gè)說(shuō)法:windows 稀疏文件會(huì)對(duì)零進(jìn)行壓縮,從而節(jié)省空間占用。如果這種說(shuō)法為真,當(dāng)寫入的數(shù)據(jù)也是零,稀疏文件占用的空間也會(huì)大大小于文件大小,實(shí)際情況會(huì)怎樣?修改一行代碼進(jìn)行驗(yàn)證:

      ...
              char buf[4096] = { 0 };
      ...

      將緩存區(qū)默認(rèn)值從 1 改為 0,此時(shí)寫入的數(shù)據(jù)全為零:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      write file elapse 11690 ms
      create file with length 0 success, path movie.mp4
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
      分配的范圍[1]: 偏移: 0x0         長(zhǎng)度: 0x100000000

      看起來(lái)沒有任何空洞,查看文件屬性:

      確實(shí)如此。這個(gè)實(shí)驗(yàn)說(shuō)明:稀疏文件并不是對(duì)零進(jìn)行壓縮,而是標(biāo)記了哪些塊有寫入,從而記錄有有效數(shù)據(jù)區(qū)間,和 linux ext4 稀疏文件的實(shí)現(xiàn)應(yīng)該是異曲同工的。

      方案對(duì)比

      總結(jié)一下目前的兩個(gè)方案的缺點(diǎn)

      • SetFileValidData:需要提權(quán)
      • 稀疏文件:無(wú)法占據(jù)磁盤空間

      看起來(lái)都挺致命的,難道就沒有十全十美的方案了嗎?在一次偶然的測(cè)試中,發(fā)現(xiàn)稀疏文件也能占據(jù)空間,只要關(guān)閉稀疏文件尾部 1 字節(jié)的寫入

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      create file with length 4294967295 success, path movie.mp4
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
      分配的范圍[1]: 偏移: 0x0         長(zhǎng)度: 0x100000000

      看起來(lái)沒有文件空洞,查看文件屬性:

      這樣看起來(lái)能占據(jù)磁盤空間了?使用 dd 灌一些數(shù)據(jù):

      $ dd if=/dev/zero of=./movie.mp4 bs=1M count=1 seek=4095
      1+0 records in
      1+0 records out
      1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.0284056 s, 36.9 MB/s

      在 4095M 位置寫入 1M,結(jié)果露餡了:

      看來(lái)還是占不了空間,之前顯示空間占用 4G 是虛的靠不住。

      再回過(guò)頭來(lái)看 SetFileValidData,windows 將它設(shè)計(jì)為提權(quán)才能使用,主要就是為了防范之前說(shuō)的信息泄漏問(wèn)題,而一般能獲取管理員身份的應(yīng)用,基本就是后臺(tái)服務(wù)或用戶授權(quán)的應(yīng)用,這也是文章開頭說(shuō)它是"給 service 打的補(bǔ)丁"的原因,畢竟后臺(tái)服務(wù)大部分是有管理員權(quán)限的。

      然而提權(quán)也會(huì)引入新的問(wèn)題,由于用戶身份與當(dāng)前登錄的不同,很多信息獲取會(huì)有差異,例如用戶做的 NAS 盤符映射,以管理員身份就讀取不到,導(dǎo)致 UI 傳遞進(jìn)來(lái)的路徑下載進(jìn)程打開文件時(shí)會(huì)報(bào)文件路徑不存在的錯(cuò)誤。我司一開始就是采取的這種方式,結(jié)果收到了客戶投訴,在嘗試以管理員身份讀取用戶的盤符映射信息時(shí),遇到了很大的困難,出了幾個(gè)方案試圖同步盤符映射信息都不夠完美,只能在 UI 層解析映射前的路徑再傳遞給下載進(jìn)程。這個(gè)方案不太優(yōu)雅,如果發(fā)生了其它方面的用戶信息不一致,還需要進(jìn)行專門治理,無(wú)法做到一勞永逸根除問(wèn)題,只能是修修補(bǔ)補(bǔ)。

      新的探索

      看了下開源的種子下載神器 Transmission,當(dāng)它采用 prealloc 模式時(shí),在 windows 上底層使用的就是稀疏文件方式:

      bool tr_sys_file_preallocate(tr_sys_file_t handle, uint64_t size, int flags, tr_error** error)
      {
          TR_ASSERT(handle != TR_BAD_SYS_FILE);
      
          if ((flags & TR_SYS_FILE_PREALLOC_SPARSE) != 0)
          {
              DWORD tmp;
      
              if (!DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &tmp, nullptr))
              {
                  set_system_error(error, GetLastError());
                  return false;
              }
          }
      
          return tr_sys_file_truncate(handle, size, error);
      }

      不過(guò)在占用空間方面,它使用的是 SetFileInformationByHandle

      bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error** error)
      {
          TR_ASSERT(handle != TR_BAD_SYS_FILE);
      
          FILE_END_OF_FILE_INFO info;
          info.EndOfFile.QuadPart = size;
      
          bool ret = SetFileInformationByHandle(handle, FileEndOfFileInfo, &info, sizeof(info));
      
          if (!ret)
          {
              set_system_error(error, GetLastError());
          }
      
          return ret;
      }

      這個(gè)和 SetEndOfFile 有何區(qū)別,修改代碼進(jìn)行驗(yàn)證:

      ...
                  FILE_END_OF_FILE_INFO info;
                  info.EndOfFile.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024;  // unit in GB
                  if (!SetFileInformationByHandle(file_handle, FileEndOfFileInfo, &info, sizeof(info)))
                  {
                      std::cout << "SetFileInformationByHandle failed, error " << GetLastError() << std::endl;
                      ret = 5;
                      break;
                  }
      
                  DWORD temp = 0;
                  if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
                  {
                      std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
                      ret = 6;
                      break;
                  }
      ...

      這段代碼替換整個(gè) SetEndOfFile 及之后的代碼。再次運(yùn)行 demo:

      PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
      create file with length 4294967296 success, path movie.mp4
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
      該文件被設(shè)為稀疏
      PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
      分配的范圍[1]: 偏移: 0x0         長(zhǎng)度: 0x100000000

      看起來(lái)沒空洞了,但用 dd 測(cè)下尾部寫入 1MB 后,結(jié)果就和之前一樣了:

      看起來(lái)沒什么改善。

      結(jié)語(yǔ)

      本文嘗試說(shuō)明 SetEndOfFile 占用空間存在的一個(gè)性能問(wèn)題,并講解了兩種解決方案,分別是SetFileValidData 和稀疏文件,以及它們的局限性;隨后嘗試破解稀疏文件局限性但失敗了;最后驗(yàn)證了 Transmission 開源庫(kù)基于SetFileInformationByHandle的方案也不可行。

      綜合各方面資訊,windows 文件在磁盤空間占用尾部塊寫入速度之間,存在著目標(biāo)性矛盾,得其一不可得其二,兩者不能兼得。具體如何取舍,需要看用戶場(chǎng)景:

      • 順序?qū)懭胛募艞壩膊繅K寫入速度,以獲取磁盤空間占用能力
      • 隨機(jī)寫入文件,放棄磁盤空間占用能力,以獲取尾部塊寫入速度

      你學(xué)會(huì)了嗎?另外附錄中羅列了一些如何高效的拷貝稀疏文件的方法,關(guān)鍵在于遍歷稀疏文件中的有效數(shù)據(jù)區(qū)間,感興趣的讀者可以參考附錄 8。

      代碼

      本期測(cè)試代碼上傳到了 github:https://github.com/goodpaperman/sparsefile

      各種接口的調(diào)用盡量做成了選項(xiàng),方便組合進(jìn)行測(cè)試,參數(shù)不足時(shí)會(huì)展示 Usage:

      PS D:\test\sparsefile\Release> .\sparsefile.exe movie.mp4
      Usage: sparsefile file length(in GB) [set-file-end-of-file-info] [set-file-valid-data] [sparse-file] [write-file-mode 0|1|2|3] [fill-char]

      文件路徑和大小是必選項(xiàng),5 個(gè)可選項(xiàng)分別控制:

      • set-file-end-of-file-info:使用SetFileInformationByHandle方式,默認(rèn)為 0 使用 SetEndOfFile方式
      • set-file-valid-data:使用 SetFileValidData 方式,此時(shí)需要以管理員身份啟動(dòng)控制臺(tái),默認(rèn)為 0
      • sparse-file:使用稀疏文件,默認(rèn)為 1
      • write-file-mode:寫文件模式,默認(rèn)為 0
        • 0:不寫
        • 1:寫文件末尾 1 字節(jié)
        • 2:間隔 1M 寫 4KB 數(shù)據(jù)
        • 3:寫整個(gè)文件
      • fill-char:寫文件時(shí)填充的字符,默認(rèn)為空 ('')

      通過(guò)設(shè)置參數(shù),可以驗(yàn)證本文的各種方案:

      • .\sparsefile movie.mp4 4SetEndOfFile+ 稀疏文件 的方式
        • .\sparsefile movie.mp4 4 0 0 1 1 ' ',在末尾寫入數(shù)據(jù)耗時(shí)小,且空間占用失敗
        • .\sparsefile movie.mp4 4 0 0 1 1,末尾寫入零時(shí)對(duì)空間占用無(wú)影響
        • .\sparsefile movie.mp4 4 0 0 1 3
          ,寫入全零塊時(shí)文件占用空間并未壓縮
      • .\sparsefile movie.mp4 4 0 0 0
        ,僅SetEndOfFile的方式
        • .\sparsefile movie.mp4 4 0 0 0 1 ' '
          ,在末尾寫入數(shù)據(jù)耗時(shí)大
        • .\sparsefile movie.mp4 4 0 0 0 1
          ,在末尾寫入零耗時(shí)小
      • .\sparsefile movie.mp4 4 1SetFileInformationByHandle+ 稀疏文件 的方式
        • .\sparsefile movie.mp4 4 1 0 1 1 ' ',在末尾寫入數(shù)據(jù)耗時(shí)小,且空間占用失敗
      • .\sparsefile movie.mp4 4 1 0 0,僅SetFileInformationByHandle的方式
        • .\sparsefile movie.mp4 4 1 0 0 1 ' ',在末尾寫入數(shù)據(jù)耗時(shí)大
      • .\sparsefile movie.mp4 4 0 1 0SetEndOfFile + SetFileValidData + 提權(quán) 的方式
        • .\sparsefile movie.mp4 4 0 1 0 1 ' ',在末尾寫入數(shù)據(jù)耗時(shí)小,且空間占用成功

      可執(zhí)行文件已經(jīng)編譯為了靜態(tài)鏈接并上傳到 git,理論上不需要裝 VS 也能運(yùn)行,配置是 Release x86 & x64 兩個(gè)平臺(tái),方便沒有編譯環(huán)境的同學(xué)直接上手。

      參考

      [1]. 什么是稀疏文件(Sparse File)

      [2]. 建希文件

      [3]. windows 高精度計(jì)時(shí)器

      [4]. Windows環(huán)境下提升進(jìn)程的權(quán)限

      [5]. SetFileValidData function

      [6]. Windows 下的文件預(yù)分配與 SetFileValidData 函數(shù)

      [7]. linux 稀疏文件(Sparse File)

      [8]. 稀疏文件簡(jiǎn)介

      posted @ 2025-03-20 13:22  goodcitizen  閱讀(710)  評(píng)論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 性夜夜春夜夜爽夜夜免费视频| 久久香蕉国产线看观看猫咪av| 亚洲高清日韩专区精品| 精品人妻人人做人人爽| 色哟哟www网站入口成人学校| 国产老熟女一区二区三区| 麻豆果冻国产剧情av在线播放| 99久久激情国产精品| 精品偷拍一区二区三区在| 婷婷丁香五月深爱憿情网| 亚洲午夜福利精品无码不卡| 久久影院午夜伦手机不四虎卡| 泸溪县| 国产精品爱久久久久久久电影 | 日韩一区二区三区日韩精品| 国产乱码精品一区二区三上| 亚洲AV日韩精品久久久久| 久久99精品久久久久麻豆| 国产精品夫妇激情啪发布| 在线看av一区二区三区| 草草浮力影院| 久久精品国产亚洲av熟女| 五月综合婷婷开心综合婷婷| 四虎精品视频永久免费| 亚洲成熟女人av在线观看| 一本大道无码av天堂| 精品国产精品中文字幕| 国内自拍第一区二区三区| 国产精品无码无卡在线播放| 国产欧美久久一区二区| 国产福利深夜在线播放| 无码一区二区三区免费| 国产亚洲无线码一区二区| 55大东北熟女啪啪嗷嗷叫| 最近免费中文字幕大全免费版视频 | 精品一区二区三区蜜桃久| 亚洲国产美女精品久久久| 午夜福利国产精品视频| 国产粉嫩系列一区二区三| 女人被狂躁c到高潮| 丝袜美腿视频一区二区三区|