2013-07-23 26 views
8

Mam aplikację, która zapisuje duże pliki w wielu segmentach. Używam FileStream.Seek do pozycjonowania każdego wirtu. Wygląda na to, że gdy wywołuję FileStream.Write na głębokiej pozycji w pliku rzadkim, zapis wyzwala operację "backfill" (zapisanie 0s) na wszystkich poprzednich bajtach, która jest wolna.Tworzenie szybkich i wydajnych zapisów strumieniowych na dużych, rzadkich plikach

Czy istnieje skuteczniejszy sposób radzenia sobie z tą sytuacją?

Poniższy kod ilustruje problem. Wstępne zapisanie zajmuje około 370 MS na moim komputerze.

public void WriteToStream() 
    { 
     DateTime dt; 
     using (FileStream fs = File.Create("C:\\testfile.file")) 
     { 
      fs.SetLength(1024 * 1024 * 100); 
      fs.Seek(-1, SeekOrigin.End); 
      dt = DateTime.Now; 
      fs.WriteByte(255);    
     } 

     Console.WriteLine(@"WRITE MS: " + DateTime.Now.Subtract(dt).TotalMilliseconds.ToString()); 
    } 

Odpowiedz

7

NTFS obsługuje Sparse Files, jednak nie ma sposobu, aby to zrobić w .NET bez p/wywoływanie niektórych metod natywnych.

Nie jest trudno oznaczyć plik jako rozrzedzony, wystarczy wiedzieć, że gdy plik jest oznaczony jako plik rozrzedzony, nigdy nie można go przekonwertować z powrotem do pliku nierozdzielonego, z wyjątkiem sytuacji, w których cały plik zostanie skopiowany do nowego pliku. rzadki plik.

Przykład useage

class Program 
{ 
    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    private static extern bool DeviceIoControl(
     SafeFileHandle hDevice, 
     int dwIoControlCode, 
     IntPtr InBuffer, 
     int nInBufferSize, 
     IntPtr OutBuffer, 
     int nOutBufferSize, 
     ref int pBytesReturned, 
     [In] ref NativeOverlapped lpOverlapped 
    ); 

    static void MarkAsSparseFile(SafeFileHandle fileHandle) 
    { 
     int bytesReturned = 0; 
     NativeOverlapped lpOverlapped = new NativeOverlapped(); 
     bool result = 
      DeviceIoControl(
       fileHandle, 
       590020, //FSCTL_SET_SPARSE, 
       IntPtr.Zero, 
       0, 
       IntPtr.Zero, 
       0, 
       ref bytesReturned, 
       ref lpOverlapped); 
     if(result == false) 
      throw new Win32Exception(); 
    } 

    static void Main() 
    { 
     //Use stopwatch when benchmarking, not DateTime 
     Stopwatch stopwatch = new Stopwatch(); 

     stopwatch.Start(); 
     using (FileStream fs = File.Create(@"e:\Test\test.dat")) 
     { 
      MarkAsSparseFile(fs.SafeFileHandle); 

      fs.SetLength(1024 * 1024 * 100); 
      fs.Seek(-1, SeekOrigin.End); 
      fs.WriteByte(255); 
     } 
     stopwatch.Stop(); 

     //Returns 2 for sparse files and 1127 for non sparse 
     Console.WriteLine(@"WRITE MS: " + stopwatch.ElapsedMilliseconds); 
    } 
} 

Gdy plik został oznaczony jako rozrzedzony teraz zachowuje się jak wyjątkiem go zachowywać się w komentarzach też. Nie trzeba pisać bajtu, aby oznaczyć plik do ustawionego rozmiaru.

static void Main() 
{ 
    string filename = @"e:\Test\test.dat"; 

    using (FileStream fs = new FileStream(filename, FileMode.Create)) 
    { 
     MarkAsSparseFile(fs.SafeFileHandle); 

     fs.SetLength(1024 * 1024 * 25); 
    } 
} 

enter image description here

+0

Dziękuję za odpowiedź, to jest bardzo interesujące. Miałem wrażenie, że FileStream.SetLength() utworzył rzadki plik. Czy nie mam racji? Próbuję uniknąć trafienia wydajności związanego z zapisywaniem w rzadkim pliku w dużym punkcie przeszukiwania. Nie jestem całkowicie pewien, w jaki sposób pozwoliłoby to uniknąć tego problemu. – revoxover

+0

Wewnętrznie SetLength wywołuje [SetEndOfFile] (http://msdn.microsoft.com/en-us/library/aa365531%28v=vs.85%29.aspx). Nie wiem, czy to tworzy rzadki plik, czy nie. –

+1

Po prostu wykonałem szybki test, nie ma. –

1

Oto kod używać plików rzadkich:

using System; 
using System.ComponentModel; 
using System.IO; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Threading; 

using Microsoft.Win32.SafeHandles; 

public static class SparseFiles 
{ 
    private const int FILE_SUPPORTS_SPARSE_FILES = 64; 

    private const int FSCTL_SET_SPARSE = 0x000900c4; 

    private const int FSCTL_SET_ZERO_DATA = 0x000980c8; 

    public static void MakeSparse(this FileStream fileStream) 
    { 
     var bytesReturned = 0; 
     var lpOverlapped = new NativeOverlapped(); 
     var result = DeviceIoControl(
      fileStream.SafeFileHandle, 
      FSCTL_SET_SPARSE, 
      IntPtr.Zero, 
      0, 
      IntPtr.Zero, 
      0, 
      ref bytesReturned, 
      ref lpOverlapped); 

     if (!result) 
     { 
      throw new Win32Exception(); 
     } 
    } 

    public static void SetSparseRange(this FileStream fileStream, long fileOffset, long length) 
    { 
     var fzd = new FILE_ZERO_DATA_INFORMATION(); 
     fzd.FileOffset = fileOffset; 
     fzd.BeyondFinalZero = fileOffset + length; 
     var lpOverlapped = new NativeOverlapped(); 
     var dwTemp = 0; 

     var result = DeviceIoControl(
      fileStream.SafeFileHandle, 
      FSCTL_SET_ZERO_DATA, 
      ref fzd, 
      Marshal.SizeOf(typeof(FILE_ZERO_DATA_INFORMATION)), 
      IntPtr.Zero, 
      0, 
      ref dwTemp, 
      ref lpOverlapped); 
     if (!result) 
     { 
      throw new Win32Exception(); 
     } 
    } 

    public static bool SupportedOnVolume(string filename) 
    { 
     var targetVolume = Path.GetPathRoot(filename); 
     var fileSystemName = new StringBuilder(300); 
     var volumeName = new StringBuilder(300); 
     uint lpFileSystemFlags; 
     uint lpVolumeSerialNumber; 
     uint lpMaxComponentLength; 

     var result = GetVolumeInformationW(
      targetVolume, 
      volumeName, 
      (uint)volumeName.Capacity, 
      out lpVolumeSerialNumber, 
      out lpMaxComponentLength, 
      out lpFileSystemFlags, 
      fileSystemName, 
      (uint)fileSystemName.Capacity); 
     if (!result) 
     { 
      throw new Win32Exception(); 
     } 

     return (lpFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES) == FILE_SUPPORTS_SPARSE_FILES; 
    } 

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool DeviceIoControl(
     SafeFileHandle hDevice, 
     int dwIoControlCode, 
     IntPtr InBuffer, 
     int nInBufferSize, 
     IntPtr OutBuffer, 
     int nOutBufferSize, 
     ref int pBytesReturned, 
     [In] ref NativeOverlapped lpOverlapped); 

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool DeviceIoControl(
     SafeFileHandle hDevice, 
     int dwIoControlCode, 
     ref FILE_ZERO_DATA_INFORMATION InBuffer, 
     int nInBufferSize, 
     IntPtr OutBuffer, 
     int nOutBufferSize, 
     ref int pBytesReturned, 
     [In] ref NativeOverlapped lpOverlapped); 

    [DllImport("kernel32.dll", EntryPoint = "GetVolumeInformationW")] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool GetVolumeInformationW(
     [In] [MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName, 
     [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpVolumeNameBuffer, 
     uint nVolumeNameSize, 
     out uint lpVolumeSerialNumber, 
     out uint lpMaximumComponentLength, 
     out uint lpFileSystemFlags, 
     [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileSystemNameBuffer, 
     uint nFileSystemNameSize); 

    [StructLayout(LayoutKind.Sequential)] 
    private struct FILE_ZERO_DATA_INFORMATION 
    { 
     public long FileOffset; 

     public long BeyondFinalZero; 
    } 
} 

i kod przykładowy, aby przetestować powyższą klasę.

class Program 
{ 
    static void Main(string[] args) 
    { 
     using (var fileStream = new FileStream("test", FileMode.Create, FileAccess.ReadWrite, FileShare.None)) 
     { 
      fileStream.SetLength(1024 * 1024 * 128); 
      fileStream.MakeSparse(); 
      fileStream.SetSparseRange(0, fileStream.Length); 
     } 
    } 
} 

Nadzieja to pomaga

+0

UWAGA: Eksplorator Windows nie wie, jak kopiować pliki rozrzedzone. Skopiuje wszystkie dane z bajtów 0 jako rzeczywiste 0 bajtowe dane. Jeśli chcesz zachować rozrzedzenie, prawdopodobnie powinieneś zachować w swoim pliku metadane dotyczące rzadkich regionów, aby móc je przywrócić po tym, jak administrator (użytkownik) skopiuje plik. – Paul