System.IO.FileStream очень медленный на огромных файлах

У меня есть фрагмент кода, который должен иметь возможность модифицировать несколько байтов в конце файла. Проблема в том, что файлы огромны. До 100+ Гб.

Мне нужно, чтобы операция была как можно быстрее, но после нескольких часов Googeling, она выглядит как .Net здесь довольно ограничена.

В основном я пытаюсь использовать System.IO.FileStream и не знаю других методов. "Реверсивный" поток будет делать, но я знаю, как его создать (пишите с конца, а не из начала).

Вот что я делаю: (Примечание: время уходит при закрытии потока)

    static void Main(string[] args)
    {    
        //Simulate a large file
        int size = 1000 * 1024 * 1024;
        string filename = "blah.dat";
        FileStream fs = new FileStream(filename, FileMode.Create);
        fs.SetLength(size);
        fs.Close();

        //Modify the last byte
        fs = new FileStream(filename, FileMode.Open);

        //If I don't seek, the modification happens instantly
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);

        //Now, since I am modifying the last byte, 
        //this last step is very slow
        fs.Close();
    }
}

Ответы

Ответ 1

Как уже отмечал Дарин, это артефакт вашего "симулятора" большого файла.

Задержка - это фактически "заполнение" файла, задержка происходит только в первый раз. Если вы повторите часть с //Modify the last byte до fs.Close();, она будет очень быстрой.

Ответ 2

Я выполнил несколько тестов, и результаты немного запутывают. Если вы создадите файл и измените его в одной программе, он будет медленным:

static void Main(string[] args)
{
    //Simulate a large file
    int size = 100 * 1024 * 1024;
    string filename = "blah.datn";
    using (var fs = new FileStream(filename, FileMode.Create))
    {
        fs.SetLength(size);
    }

    using (var fs = new FileStream(filename, FileMode.Open))
    {
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);
    }
}

Но если файл существует, и вы только пытаетесь изменить последний байт, это быстро:

static void Main(string[] args)
{
    string filename = "blah.datn";
    using (var fs = new FileStream(filename, FileMode.Open))
    {
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);
    }
}

Хммм... Забастовкa >


UPDATE:

Пожалуйста, проигнорируйте мои предыдущие наблюдения и отмените это как ответ, потому что это все неправильно.

Дальнейшее исследование проблемы я заметил следующий шаблон. Предположим, что вы выделяете файл с заданным размером с нулевыми байтами:

using (var stream = File.OpenWrite("blah.dat"))
{
    stream.SetLength(100 * 1024 * 1024);
}

Эта операция выполняется очень быстро и создает файл размером 100 МБ, заполненный нулями.

Теперь, если в какой-либо другой программе вы попытаетесь изменить последний байт, закрытие потока будет медленным:

using (var stream = File.OpenWrite("blah.dat"))
{
    stream.Seek(-1, SeekOrigin.End);
    stream.WriteByte(255);
}

Я понятия не имею о внутренних функциях файловой системы или о том, как именно создан этот файл, но у меня есть ощущение, что он не полностью инициализирован, пока вы не попытаетесь его модифицировать, и закрытие дескриптора будет медленным.

Чтобы подтвердить это, я тестировал неуправляемый код (не стесняйтесь исправить любую аберрацию, так как мой C очень ржавый):

void main()
{
    int size = 100 * 1024 * 1024 - 1;
    FILE *handle = fopen("blah.dat", "wb");
    if (handle != NULL) {
        fseek(handle, size, SEEK_SET);
        char buffer[] = {0};
        fwrite(buffer, 1, 1, handle);
        fclose(handle);
    }
}

Это ведет себя так же, как в .NET = > он выделяет файл размером 100 МБ, заполненный нулями, и это очень быстро.

Теперь, когда я пытаюсь изменить последний байт этого файла:

void main()
{
    int size = 100 * 1024 * 1024 - 1;
    FILE *handle = fopen("blah.datn", "rb+");
    if (handle != NULL) {
        fseek(handle, -1, SEEK_END);
        char buffer[] = {255};
        fwrite(buffer, 1, 1, handle);
        fclose(handle);
    }
}

Последний fclose(handle) медленный. Я надеюсь, что некоторые эксперты принесут немного света.

Кажется, что изменение последнего байта реального файла (не разреженного) с использованием предыдущих методов выполняется очень быстро.

Ответ 3

Возможно, самый быстрый способ работы с большими файлами с помощью MemoryMappedFile. Файл с отображением памяти - это файл, который отображается (не загружен) в виртуальную память, поэтому вы можете получить доступ к случайным байтам в нем, не обращаясь к определенному местоположению, буферам загрузки и т.д. Вы также можете читать целые структуры непосредственно из файла без через десериализацию.

Следующий код, выходящий прямо из MSDN, загружает и сохраняет структуру MyColor в середине файла размером 512 МБ:

static void Main(string[] args)
{
    long offset = 0x10000000; // 256 megabytes
    long length = 0x20000000; // 512 megabytes

    // Create a memory-mapped view of a portion of 
    // an extremely large image, from the 256th megabyte (the offset)
    // to the 768th megabyte (the offset plus length).
    using (var mmf = 
        MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data",
                                                    FileMode.Open,"ImgA"))
    {
        using (var accessor = mmf.CreateViewAccessor(offset, length))
        {

            int colorSize = Marshal.SizeOf(typeof(MyColor));
            MyColor color;

            // Make changes to the view.
            for (long i = 0; i < length; i += colorSize)
            {
                accessor.Read(i, out color);
                color.Brighten(10);
                accessor.Write(i, ref color);
            }
        }
    }

}

public struct MyColor
{
    public short Red;
    public short Green;
    public short Blue;
    public short Alpha;

    // Make the view brigher.
    public void Brighten(short value)
    {
        Red = (short)Math.Min(short.MaxValue, (int)Red + value);
        Green = (short)Math.Min(short.MaxValue, (int)Green + value);
        Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
        Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
    }
}

Вы можете найти дополнительную информацию и образцы в Файлы с памятью

Ответ 4

Я предлагаю вам попробовать его с реальным файлом, а не с "смоделированным" файлом. Возможно, что .net использует какой-то разреженный механизм распределения и только записывает файл до последнего байта, на самом деле написанного.

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