Как написать сверхбыстрый код для потоковой передачи файлов на C #?

StackOverflow https://stackoverflow.com/questions/955911

Вопрос

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

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. Есть ли способ сделать Так, чтобы Автор был напрямую связан с Читателем?(То есть, фактически не загружая содержимое в буфер в памяти.)
Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

offset = 1234, length = 1074

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top