Вопрос по filestream, c#, file-io, sparse-matrix – Как создать быструю и эффективную запись файлового потока на большие разреженные файлы

8

У меня есть приложение, которое пишет большие файлы в нескольких сегментах. Я использую FileStream.Seek для позиционирования каждого wirte. Похоже, что когда я вызываю FileStream.Write в глубокой позиции в разреженном файле, запись запускает "засыпку» операция (запись 0s) на всех предшествующих байтах, которая является медленной.

Есть ли более эффективный способ справиться с этой ситуацией?

Код ниже демонстрирует проблему. Начальная запись занимает около 370 мс на моей машине.

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

Ваш Ответ

2   ответа
7

NTFS поддерживаетРедкие файлыОднако в .net нет способа сделать это без p / вызова некоторых нативных методов.

Не очень сложно пометить файл как разреженный, просто знайте, что если файл помечен как разреженный файл, его никогда нельзя преобразовать обратно в не разреженный файл, кроме как путем копирования всего файла в новый не разреженный файл.

Пример использования

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

После того, как файл был помечен как разреженный, он теперь ведет себя так, как если бы вы исключали его из поведения в комментариях. Ты неНе нужно писать байт, чтобы пометить файл с заданным размером.

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

@PhonicUK Я нея знаю Scott Chamberlain
Просто провел быстрый тест, это не так. Scott Chamberlain
Спасибо за ответ, это очень интересно. У меня сложилось впечатление, что FileStream.SetLength () создал разреженный файл. Я ошибаюсь? Я пытаюсь избежать снижения производительности при записи в разреженный файл при большой точке поиска. Я не совсем понимаю, как это могло бы избежать этой проблемы. revoxover
Внутренне SetLength вызываетSetEndOfFile , Я нене знаю, создает ли это разреженный файл или нет. Scott Chamberlain
1

Вот некоторый код для использования разреженных файлов:

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

И пример кода для проверки вышеуказанного класса.

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

Надеюсь это поможет

ПРИМЕЧАНИЕ. Проводник Windows не знает, как копировать разреженные файлы. Он скопирует все 0-байтовые данные как фактические 0-байтовые данные. Поэтому, если вы хотите сохранить разреженность, вам, вероятно, следует сохранить в файле некоторые метаданные о разреженных областях, чтобы вы могли восстановить их после того, как администратор (пользователь) скопирует файл. Paul

Похожие вопросы