How to create fast and efficient filestream writes on large sparse files

后端 未结 2 1100
小蘑菇
小蘑菇 2021-02-08 10:09

I have an application that writes large files in multiple segments. I use FileStream.Seek to position each wirte. It appears that when I call FileStream.Write at a deep position

相关标签:
2条回答
  • 2021-02-08 10:37

    NTFS does support Sparse Files, however there is no way to do it in .net without p/invoking some native methods.

    It is not very hard to mark a file as sparse, just know once a file is marked as a sparse file it can never be converted back in to a non sparse file except by coping the entire file in to a new non sparse file.

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

    Once a file has been marked as sparse it now behaves like you excepted it to behave in the comments too. You don't need to write a byte to mark a file to a set size.

    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 讨论(0)
  • 2021-02-08 10:49

    Here is some code to use sparse files:

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

    And sample code to test the above class.

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

    Hope this helps

    0 讨论(0)
提交回复
热议问题