2013-07-23 5 views
8

Ich habe eine Anwendung, die große Dateien in mehrere Segmente schreibt. Ich benutze FileStream.Seek, um jedes Wirte zu positionieren. Es scheint, dass, wenn ich FileStream.Write an einer tiefen Position in einer Sparse-Datei aufruft, der Schreibvorgang eine "Backfill" -Operation (Schreiben von 0s) auf allen vorhergehenden Bytes auslöst, was langsam ist.So erstellen Sie schnelle und effiziente Filestream-Schreibvorgänge in großen Dateien mit geringer Dichte

Gibt es eine effizientere Möglichkeit, diese Situation zu behandeln?

Der folgende Code demonstriert das Problem. Der anfängliche Schreibvorgang dauert ungefähr 370 MS auf meinem Computer.

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()); 
    } 

Antwort

7

NTFS unterstützt jedoch Sparse Files, aber es gibt keine Möglichkeit, es in .net zu tun, ohne p/einige native Methoden aufrufen.

Es ist nicht schwer, eine Datei als sparse zu markieren. Sobald eine Datei als Sparse-Datei markiert ist, kann sie nicht wieder in eine Datei mit geringer Speicherdichte konvertiert werden, außer wenn die gesamte Datei in eine neue Datei kopiert wird Sparse-Datei.

Beispiel 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); 
    } 
} 

Sobald eine Datei nun als spärlich markiert wurde es verhält sich wie Sie es vorbehalten zu in den Kommentaren zu verhalten. Sie müssen kein Byte schreiben, um eine Datei auf eine festgelegte Größe zu markieren.

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

Sie für die Antwort danken, das ist sehr interessant. Ich hatte den Eindruck, dass FileStream.SetLength() eine Sparse-Datei erstellt hat. Liege ich damit falsch? Ich versuche, den Leistungseinbruch zu vermeiden, der beim Schreiben in eine Sparse-Datei an einem großen Suchpunkt auftritt. Ich bin mir nicht ganz im Klaren darüber, wie dies vermieden werden könnte. – revoxover

+0

Intern ruft SetLength [SetEndOfFile] (http://msdn.microsoft.com/en-us/library/aa365531%28v=vs.85%29.aspx) auf. Ich weiß nicht, ob das eine sparse Datei erstellt oder nicht. –

+1

Habe gerade einen Schnelltest gemacht, tut es nicht. –

1

Hier einige Code Sparse-Dateien zu verwenden:

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; 
    } 
} 

Und Beispielcode die obige Klasse zu testen.

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); 
     } 
    } 
} 

hoffe, das hilft

+0

Hinweis: Windows Explorer kann nicht sparse Dateien kopieren. Es kopiert alle 0-Byte-Daten als tatsächliche 0-Byte-Daten. Wenn Sie die Spärlichkeit beibehalten möchten, sollten Sie daher einige Metadaten in Ihrer Datei über sparse regions aufbewahren, damit Sie sie wiederherstellen können, nachdem ein Administrator (Benutzer) die Datei kopiert hat. – Paul

Verwandte Themen