Подтвердить что ты не робот

Как написать супер-быстрый файл-потоковый код в С#?

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

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? (То есть без фактической загрузки содержимого в буфер в памяти.)
4b9b3361

Ответ 1

Я не верю в .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 обертывание входного потока.

Ответ 2

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

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

Ответ 3

Насколько велика 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;
        }
    }        
}

Ответ 4

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

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

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

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

offset = 1234, length = 1074

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

Ответ 5

Считаете ли вы, что используете 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, который вызывает создание Thread для выполнения кода в методе Split. Это заставляет вас открывать файл несколько раз, но избавляется от необходимости синхронизации. Вы можете сделать его более эффективным с точки зрения памяти, но вам придется пожертвовать скоростью.

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

(для справок в будущем)

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

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

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