2013-07-23 12 views
8

Ho un'applicazione che scrive file di grandi dimensioni in più segmenti. Io uso FileStream.Seek per posizionare ogni wirte. Sembra che quando chiamo FileStream.Write in una posizione profonda in un file sparse, la scrittura attiva un'operazione di "backfill" (scrivendo 0) su tutti i byte precedenti che è lenta.Come creare scritture di filestream veloci ed efficienti su file sparsi di grandi dimensioni

Esiste un modo più efficiente di gestire questa situazione?

Il seguente codice illustra il problema. La scrittura iniziale richiede circa 370 MS sulla mia macchina.

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

risposta

7

NTFS fa supporto Sparse Files, ma non c'è modo di farlo in .net senza p/invocare alcuni metodi nativi.

Non è molto difficile contrassegnare un file come sparse, basta sapere una volta che un file è contrassegnato come file sparse non può mai essere riconvertito in un file non sparse se non copiando l'intero file in un nuovo non file sparse.

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

volta che un file è stato contrassegnato come sparse ora si comporta come si eccettuato a comportarsi nei commenti troppo. Non è necessario scrivere un byte per contrassegnare un file con una dimensione impostata.

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

Grazie per la risposta, questo è molto interessante. Ho avuto l'impressione che FileStream.SetLength() abbia creato un file sparse. Mi sbaglio? Sto cercando di evitare il colpo di prestazioni subite durante la scrittura su un file sparse in un punto di ricerca ampio. Non sono del tutto chiaro su come questo eviterebbe quel problema. – revoxover

+0

Internamente SetLength chiama [SetEndOfFile] (http://msdn.microsoft.com/en-us/library/aa365531%28v=vs.85%29.aspx). Non so se questo sta creando un file sparse o no. –

+1

Ho appena eseguito un test rapido, non è così. –

1

Ecco il codice per utilizzare i file sparse:

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

e codice di esempio per testare la classe superiore.

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

Spero che questo aiuti

+0

NOTA: Windows Explorer non sa come copiare file sparsi. Copierà tutti i dati a 0 byte come dati a 0 byte effettivi. Quindi, se si desidera mantenere la scarsità, probabilmente si dovrebbero conservare alcuni metadati nel proprio file sulle regioni sparse in modo da poterli ripristinare dopo che un amministratore (utente) copia il file. – Paul

Problemi correlati