File.Copy与手动FileStream.Write用于复制文件

时间:2009-08-07 20:42:16

标签: c# windows performance

我的问题在于文件复制性能。我们有一个媒体管理系统,需要在文件系统上大量移动文件到不同的位置,包括同一网络上的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而在其他情况下我将使用流?

请告诉我您的想法以及是否需要其他信息。我尝试了不同的缓冲区大小,似乎64k缓冲区大小对于我们的“小”文件来说是最佳的,256k +对于我们的“大”文件来说是一个更好的缓冲区大小 - 但无论哪种情况下它都比File.Copy执行得更糟糕( )。提前致谢!

8 个答案:

答案 0 :(得分:23)

File.Copy是围绕CopyFile Win32功能构建的,这个功能需要MS工作人员的大量关注(请记住这个与Vista相关的慢速复制性能线程)。

提高方法性能的几条线索:

  1. 像许多人说的那样早先从你的周期中删除Flush方法。你根本不需要它。
  2. 增加缓冲区可能有所帮助,但仅限于文件到文件操作,对于网络共享或ftp服务器,这将会减慢速度。 60 * 1024是网络共享的理想选择,至少在vista之前。在大多数情况下,对于ftp 32k就足够了。
  3. 通过提供缓存策略帮助操作系统(在您的情况下顺序读取和写入),使用带有FileOptions参数的FileStream构造函数覆盖(SequentalScan)。
  4. 您可以使用异步模式加速复制(对于网络到文件的情况尤其有用),但不要使用线程,而是使用重叠的io(BeginRead,EndRead,BeginWrite,EndWrite in .net),以及不要忘记在FileStream构造函数中设置Asynchronous选项(参见FileOptions
  5. 异步复制模式示例:

    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);
    

答案 1 :(得分:7)

除掉反射器后,我们可以看到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);

And here is the documentation for CopyFile

答案 2 :(得分:6)

使用自己的代码做一些如此有趣的事情,你永远无法击败操作系统,即使你在汇编程序中仔细精心制作它也是如此。

如果您需要确保您的操作以最佳性能发生并且您希望混合和匹配各种源,那么您将需要创建一个描述资源位置的类型。然后,您创建一个具有Copy等函数的API,它采用两种类型,并检查两者的描述,选择性能最佳的复制机制。例如,在确定两个位置都是Windows文件位置之后,您将选择File.Copy或如果源是Windows文件,但目标是HTTP POST,则它使用WebRequest。

答案 3 :(得分:4)

三项改变将大大提高绩效:

  1. 增加缓冲区大小,尝试1MB(正确的实验)
  2. 打开fileStream后,调用fileStream.SetLength(inStream.Length)预先在磁盘上分配整个块(仅当inStream可以搜索时才有效)
  3. 删除fileStream.Flush() - 它是多余的,可能对性能产生最大的影响,因为它会在刷新完成之前阻塞。无论如何都会在处理时刷新流。
  4. 在我尝试的实验中,这似乎快了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);
                }
           }
        }
    

答案 4 :(得分:1)

尝试删除Flush调用,并将其移至循环外部。

有时,操作系统最清楚何时刷新IO ..它允许它更好地使用其内部缓冲区。

答案 5 :(得分:1)

这是一个类似的答案

How do I copy the contents of one stream to another?

您的主要问题是调用Flush(),这会将您的性能与I / O的速度绑定。

答案 6 :(得分:1)

马克·鲁西诺维奇将是这方面的权威。

他在blog上写了一个条目Inside Vista SP1 File Copy Improvements,它通过Vista SP1总结了Windows最先进的技术。

我的半受教育的猜测是File.Copy在最多的情况下将是最强大的。当然,这并不意味着在某些特定的角落情况下,您自己的代码可能会击败它......

答案 7 :(得分:0)

有一点值得注意的是,你正在阅读一大块,写下那块,阅读另一块,等等。

流式传输操作是多线程的理想选择。我的猜测是File.Copy实现了多线程。

尝试在一个线程中读取并在另一个线程中写入。您将需要协调线程,以便写入线程不会开始写入缓冲区,直到读取线程完成填充。您可以通过使用两个缓冲区来解决这个问题,一个缓冲区正在读取另一个缓冲区,另一个缓冲区当前正在使用哪个缓冲区。