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

File.Copy против ручного FileStream.Write для копирования файла

Моя проблема связана с производительностью копирования файлов. У нас есть система управления мультимедиа, которая требует много движущихся файлов в файловой системе в разных местах, включая общие ресурсы Windows в одной сети, FTP-сайтах, AmazonS3 и т.д. Когда мы были в одной сети Windows, мы могли бы избежать использования System.IO.File.Copy(источник, получатель) для копирования файла. Поскольку во многих случаях у нас есть входной поток (например, MemoryStream), мы попытались абстрагировать операцию копирования, чтобы принять входной поток и выходной поток, но мы наблюдаем значительное снижение производительности. Ниже приведен код для копирования файла для использования в качестве точки обсуждения.

public void Copy(System.IO.Stream inStream, string outputFilePath)
{
    int bufferSize = 1024 * 64;

    using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
    {

        int bytesRead = -1;
        byte[] bytes = new byte[bufferSize];

        while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
        {
            fileStream.Write(bytes, 0, bytesRead);
            fileStream.Flush();
        }
    }
}

Кто-нибудь знает, почему это работает намного медленнее, чем File.Copy? Есть ли что-то, что я могу сделать для повышения производительности? Мне просто нужно будет ввести специальную логику, чтобы посмотреть, копирую ли я из одного окна в другое, и в этом случае я бы просто использовал File.Copy, а в других случаях я буду использовать потоки?

Пожалуйста, дайте мне знать, что вы думаете и нужна ли вам дополнительная информация. Я пробовал разные размеры буфера, и кажется, что размер буфера размером 64 КБ оптимален для наших "маленьких" файлов, а 256k + - лучший размер буфера для наших "больших" файлов, но в любом случае он намного хуже, чем File.Copy(). Заранее спасибо!

4b9b3361

Ответ 1

File.Copy был создан вокруг CopyFile Функция Win32, и эта функция привлекает большое внимание со стороны команды MS (помните, что связанные с Vista потоки о медленных копирование).

Несколько подсказок для повышения производительности вашего метода:

  • Как и многие ранее, удалите метод Flush из вашего цикла. Вам это совсем не нужно.
  • Увеличение буфера может помочь, но только при работе файлов в файл, для сетевых ресурсов или ftp-серверов это будет замедляться. 60 * 1024 идеально подходит для сетевых ресурсов, по крайней мере, до перспективы. для ftp 32k будет достаточно в большинстве случаев.
  • Помогите, предоставив свою стратегию кэширования (в вашем случае последовательное чтение и запись), используйте переопределение конструктора FileStream с параметром FileOptions (SequentalScan).
  • Вы можете ускорить копирование с помощью асинхронного шаблона (особенно полезно для случаев с сетью в файл), но не используйте потоки для этого, вместо этого используйте перекрывающиеся io (BeginRead, EndRead, BeginWrite, EndWrite в .net) и не забудьте установить опцию Asynchronous в конструкторе FileStream (см. FileOptions)

Пример шаблона асинхронной копии:

int Readed = 0;
IAsyncResult ReadResult;
IAsyncResult WriteResult;

ReadResult = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null);
do
{
    Readed = sourceStream.EndRead(ReadResult);

    WriteResult = destStream.BeginWrite(ActiveBuffer, 0, Readed, null, null);
    WriteBuffer = ActiveBuffer;

    if (Readed > 0)
    {
      ReadResult = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null);
      BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer);
    }

    destStream.EndWrite(WriteResult);
  }
  while (Readed > 0);

Ответ 2

Сбрасывая отражатель, мы видим, что File.Copy на самом деле вызывает API Win32:

if (!Win32Native.CopyFile(fullPathInternal, dst, !overwrite))

Что разрешает

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
internal static extern bool CopyFile(string src, string dst, bool failIfExists);

И вот документация для CopyFile

Ответ 3

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

Если вам нужно убедиться, что ваши операции происходят с максимальной производительностью. И вы хотите смешивать и сопоставлять различные источники, вам нужно будет создать тип, описывающий расположение ресурсов. Затем вы создаете API, который имеет такие функции, как Copy, который принимает два таких типа, и изучив описания обоих, выбирает наиболее эффективный механизм копирования. Например, определив, что оба местоположения являются местоположениями файлов Windows, вы выбрали File.Copy ИЛИ, если источником является файл Windows, но местом назначения является HTTP POST, он использует WebRequest.

Ответ 4

Три изменения значительно улучшат производительность:

  • Увеличьте свой размер буфера, попробуйте 1MB (хорошо провести эксперимент)
  • После того, как вы откроете файлStream, вызовите fileStream.SetLength(inStream.Length), чтобы выделить весь блок на диске спереди (работает только в том случае, если inStream доступен для поиска)
  • Удалить файлStream.Flush() - он избыточен и, вероятно, имеет самое большое влияние на производительность, поскольку он будет блокироваться до завершения флеша. Поток будет по-прежнему сброшен.

Это было примерно в 3-4 раза быстрее в экспериментах, которые я пробовал:

   public static void Copy(System.IO.Stream inStream, string outputFilePath)
    {
        int bufferSize = 1024 * 1024;

        using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write))
        {
            fileStream.SetLength(inStream.Length);
            int bytesRead = -1;
            byte[] bytes = new byte[bufferSize];

            while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
            {
                fileStream.Write(bytes, 0, bytesRead);
            }
       }
    }

Ответ 5

Одна вещь, которая выделяется, заключается в том, что вы читаете кусок, записываете этот кусок, читаете еще один кусок и т.д.

Потоковые операции - отличные кандидаты на многопоточность. Я предполагаю, что File.Copy реализует многопоточность.

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

Ответ 6

Попробуйте удалить вызов Flush и переместите его за пределы цикла.

Иногда ОС лучше всего подходит для сброса ввода-вывода. Это позволяет ему лучше использовать свои внутренние буферы.

Ответ 8

Марк Руссинович был бы авторитетом в этом вопросе.

Он написал на blog запись Улучшения файлового копирования в Vista Vista SP1, в котором суммируется состояние Windows в Vista через SP1.

Мое полуобученное предположение состояло бы в том, что File.Copy будет наиболее надежным в наибольшем количестве ситуаций. Конечно, это не означает, что в каком-то конкретном случае угол, ваш собственный код может превзойти его...