Как написать супер-быстрый файл потокового кода в C #? - PullRequest
39 голосов
/ 05 июня 2009

Мне нужно разбить огромный файл на множество файлов меньшего размера. Каждый из файлов назначения определяется смещением и длиной как количество байтов. Я использую следующий код:

private void copy(string srcFile, string dstFile, int offset, int length)
{
    BinaryReader reader = new BinaryReader(File.OpenRead(srcFile));
    reader.BaseStream.Seek(offset, SeekOrigin.Begin);
    byte[] buffer = reader.ReadBytes(length);

    BinaryWriter writer = new BinaryWriter(File.OpenWrite(dstFile));
    writer.Write(buffer);
}

Учитывая, что я должен вызывать эту функцию около 100 000 раз, она работает очень медленно

  1. Есть ли способ подключить Writer напрямую к Reader? (То есть без фактической загрузки содержимого в буфер в памяти.)

Ответы [ 9 ]

45 голосов
/ 05 июня 2009

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

public static void CopySection(Stream input, string targetFile, int length)
{
    byte[] buffer = new byte[8192];

    using (Stream output = File.OpenWrite(targetFile))
    {
        int bytesRead = 1;
        // This will finish silently if we couldn't read "length" bytes.
        // An alternative would be to throw an exception
        while (length > 0 && bytesRead > 0)
        {
            bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length));
            output.Write(buffer, 0, bytesRead);
            length -= bytesRead;
        }
    }
}

Это имеет небольшую неэффективность при создании буфера при каждом вызове - вы можете создать буфер один раз и передать его в метод:

public static void CopySection(Stream input, string targetFile,
                               int length, byte[] buffer)
{
    using (Stream output = File.OpenWrite(targetFile))
    {
        int bytesRead = 1;
        // This will finish silently if we couldn't read "length" bytes.
        // An alternative would be to throw an exception
        while (length > 0 && bytesRead > 0)
        {
            bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length));
            output.Write(buffer, 0, bytesRead);
            length -= bytesRead;
        }
    }
}

Обратите внимание, что это также закрывает выходной поток (из-за оператора using), чего не было в исходном коде.

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

Я думаю это будет значительно быстрее, но, очевидно, вам нужно попробовать это, чтобы увидеть ...

Это предполагает, конечно, смежные куски. Если вам нужно пропустить биты файла, вы можете сделать это вне метода. Кроме того, если вы пишете очень маленькие файлы, возможно, вы захотите оптимизировать и для этой ситуации - самый простой способ сделать это, вероятно, - ввести BufferedStream, обертывающий поток ввода.

25 голосов
/ 04 марта 2011

Самый быстрый способ выполнить ввод / вывод файла из C # - использовать функции Windows ReadFile и WriteFile. Я написал класс C #, который инкапсулирует эту возможность, а также программу тестирования, которая рассматривает различные методы ввода-вывода, включая BinaryReader и BinaryWriter. Смотрите мое сообщение в блоге по адресу:

http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

6 голосов
/ 05 июня 2009

Насколько велика length? Вы можете лучше использовать буфер фиксированного размера (умеренно большой, но не непристойный) и забыть BinaryReader ... просто используйте Stream.Read и Stream.Write.

(редактировать) что-то вроде:

private static void copy(string srcFile, string dstFile, int offset,
     int length, byte[] buffer)
{
    using(Stream inStream = File.OpenRead(srcFile))
    using (Stream outStream = File.OpenWrite(dstFile))
    {
        inStream.Seek(offset, SeekOrigin.Begin);
        int bufferLength = buffer.Length, bytesRead;
        while (length > bufferLength &&
            (bytesRead = inStream.Read(buffer, 0, bufferLength)) > 0)
        {
            outStream.Write(buffer, 0, bytesRead);
            length -= bytesRead;
        }
        while (length > 0 &&
            (bytesRead = inStream.Read(buffer, 0, length)) > 0)
        {
            outStream.Write(buffer, 0, bytesRead);
            length -= bytesRead;
        }
    }        
}
3 голосов
/ 05 июня 2009

Рассматривали ли вы использование CCR, поскольку вы пишете в отдельные файлы, вы можете делать все параллельно (чтение и запись), а CCR делает это очень легко.

static void Main(string[] args)
    {
        Dispatcher dp = new Dispatcher();
        DispatcherQueue dq = new DispatcherQueue("DQ", dp);

        Port<long> offsetPort = new Port<long>();

        Arbiter.Activate(dq, Arbiter.Receive<long>(true, offsetPort,
            new Handler<long>(Split)));

        FileStream fs = File.Open(file_path, FileMode.Open);
        long size = fs.Length;
        fs.Dispose();

        for (long i = 0; i < size; i += split_size)
        {
            offsetPort.Post(i);
        }
    }

    private static void Split(long offset)
    {
        FileStream reader = new FileStream(file_path, FileMode.Open, 
            FileAccess.Read);
        reader.Seek(offset, SeekOrigin.Begin);
        long toRead = 0;
        if (offset + split_size <= reader.Length)
            toRead = split_size;
        else
            toRead = reader.Length - offset;

        byte[] buff = new byte[toRead];
        reader.Read(buff, 0, (int)toRead);
        reader.Dispose();
        File.WriteAllBytes("c:\\out" + offset + ".txt", buff);
    }

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

3 голосов
/ 05 июня 2009

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

Если длины не слишком велики, вы также можете попробовать сгруппировать несколько вызовов копирования, сгруппировав смещения, расположенные рядом друг с другом, и прочитав весь блок, который вам нужен, например:

offset = 1234, length = 34
offset = 1300, length = 40
offset = 1350, length = 1000

можно сгруппировать в одно чтение:

offset = 1234, length = 1074

Тогда вам остается только «искать» в своем буфере и записывать оттуда три новых файла без необходимости повторного чтения.

1 голос
/ 05 июня 2009

Использование FileStream + StreamWriter Я знаю, что можно создавать массивные файлы за короткое время (менее 1 минуты 30 секунд). Я генерирую три файла общим объемом более 700 мегабайт из одного файла, используя эту технику.

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

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

1 голос
/ 05 июня 2009

Первое, что я бы порекомендовал, это провести измерения. Где ты теряешь время? Это в чтении или записи?

Более 100 000 доступов (сумма времени): Сколько времени тратится на выделение буферного массива? Сколько времени уходит на открытие файла для чтения (это один и тот же файл каждый раз?) Сколько времени тратится на операции чтения и записи?

Если вы не выполняете какого-либо преобразования файла, вам нужен BinaryWriter или вы можете использовать файловый поток для записи? (попробуйте, вы получаете идентичный вывод? Это экономит время?)

0 голосов
/ 05 июня 2009

Никто не предлагает нить? Запись файлов меньшего размера выглядит как пример учебника, где темы полезны. Установите несколько потоков для создания файлов меньшего размера. таким образом, вы можете создавать их все параллельно, и вам не нужно ждать окончания каждого из них. Я предполагаю, что создание файлов (операция с диском) займет больше времени, чем разделение данных. и, конечно, вы должны сначала убедиться, что последовательный подход не является адекватным.

0 голосов
/ 05 июня 2009

(для дальнейшего использования.)

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

Файлы с отображением в памяти поддерживаются в управляемом коде в .NET 4.0.

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

...