File.Copy или Manual FileStream.Write для копирования файла - PullRequest
32 голосов
/ 08 августа 2009

Моя проблема связана с производительностью копирования файлов. У нас есть система управления мультимедиа, которая требует много перемещения файлов в файловой системе в разные места, включая общие папки 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 КБ является оптимальным для наших «маленьких» файлов, а 256 Кбайт является лучшим размером буфера для наших «больших» файлов - но в любом случае он работает намного хуже, чем File.Copy ). Заранее спасибо!

Ответы [ 8 ]

23 голосов
/ 08 августа 2009

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

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

  1. Как многие говорили ранее, удалите метод Flush из вашего цикла. Вам это совсем не нужно.
  2. Увеличение буфера может помочь, но только при межфайловых операциях, для сетевых ресурсов или FTP-серверов это замедлится. 60 * 1024 идеально подходит для сетевых ресурсов, по крайней мере, до перспективы. для ftp 32k будет достаточно в большинстве случаев.
  3. Помогите нам, предоставив свою стратегию кэширования (в вашем случае последовательное чтение и запись), используйте переопределение конструктора FileStream с параметром FileOptions (SequentalScan).
  4. Вы можете ускорить копирование с помощью асинхронного шаблона (особенно полезно для случаев с сетью в файл), но не используйте для этого потоки, вместо этого используйте перекрывающийся io (BeginRead, EndRead, BeginWrite, EndWrite в .net) и не забудьте установить асинхронный параметр в конструкторе 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);
7 голосов
/ 08 августа 2009

Очистив отражатель, мы видим, что File.Copy фактически вызывает Win32 API:

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

6 голосов
/ 08 августа 2009

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

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

4 голосов
/ 08 августа 2009

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

  1. Увеличьте размер буфера, попробуйте 1 МБ (просто эксперимент)
  2. После того, как вы откроете свой fileStream, вызовите fileStream.SetLength (inStream.Length), чтобы выделить весь блок на диске заранее (работает, только если inStream доступен для поиска)
  3. Remove fileStream.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);
            }
       }
    }
1 голос
/ 08 августа 2009

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

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

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

1 голос
/ 08 августа 2009

Вот аналогичный ответ

Как скопировать содержимое одного потока в другой?

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

1 голос
/ 08 августа 2009

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

Иногда ОС лучше всего знает, когда нужно очистить IO. Это позволяет ей лучше использовать свои внутренние буферы.

0 голосов
/ 08 августа 2009

Одна вещь, которая выделяется, это то, что вы читаете кусок, пишете этот кусок, читаете другой кусок и так далее.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...