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

我的问题是关于文件复制性能。 我们有一个媒体pipe理系统,需要在文件系统上的大量移动文件到不同的位置,包括在同一networking上的Windows共享,FTP站点,AmazonS3等。当我们都在一个Windowsnetworking上,我们可以逃脱使用System.IO.File.Copy(源,目标)复制文件。 由于很多时候我们只有一个inputstream(就像一个MemoryStream),所以我们尝试抽象复制操作来获取一个inputstream和一个输出stream,但是我们看到一个巨大的性能下降。 下面是一些复制文件的代码作为讨论点。

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

有谁知道为什么这个performance比File.Copy慢得多? 有什么我可以做的改善performance? 我只需要把特殊的逻辑,看看我是否从一个窗口位置复制到另一个 – 在这种情况下,我只是使用File.Copy,在其他情况下,我会使用stream?

请让我知道你的想法和是否需要额外的信息。 我已经尝试了不同的缓冲区大小,似乎64k缓冲区大小对于我们的“小”文件是最佳的,256k +对于我们的“大”文件是更好的缓冲区大小 – 但是在任何情况下,它的性能都比File.Copy )。 提前致谢!

File.Copy是围绕CopyFile Win32函数构建的,这个函数需要MS机组的大量关注(请记住这个与Vista有关的关于慢速复制性能的线程)。

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

  1. 像许多人之前说的从你的循环中移除Flush方法。 你根本不需要它。
  2. 增加缓冲区可能会有所帮助,但是只能在文件到​​文件操作,网络共享或ftp服务器上运行,而不是缓慢运行。 至少在Vista之前,60 * 1024是网络共享的理想选择。 在大多数情况下,ftp 32k就足够了。
  3. 通过提供缓存策略(在您的情况下顺序读取和写入)来帮助OS,使用FileOptions参数(SequentalScan)使用FileStream构造器重写。
  4. 你可以使用异步模式加速复制(对于网络到文件的情况尤其有用),但是不要使用线程,而是使用重叠的io(BeginRead,EndRead,BeginWrite,EndWrite in .net),并且不要忘记在FileStream构造函数中设置Asynchronous选项(请参阅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); 

除尘反射器我们可以看到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的文档

用你自己的代码做一些非常有趣的事情,你将永远无法击败操作系统,即使你用汇编语言精心打造它。

如果您需要确保您的操作以最佳性能进行,并且您想要混合和匹配各种来源,那么您将需要创建一个描述资源位置的类型。 然后创建一个具有诸如Copy这样的函数的API,其中两个这样的类型,并且已经检查了两者的描述来选择最佳执行的复制机制。 例如,确定这两个位置是Windows文件的位置,你会选择File.Copy或如果源是Windows文件,但目的地是HTTP POST它使用WebRequest。

三个改变将大大提高性能:

  1. 增加你的缓冲区大小,尝试1MB(良好的实验)
  2. 打开你的fileStream后,调用fileStream.SetLength(inStream.Length)在磁盘前面分配整个块(只有当inStream是可搜索时才起作用)
  3. 删除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); } } } 

有一点很突出,就是你正在读一块,写这个块,读另一块,等等。

流媒体操作是多线程的好选择。 我的猜测是File.Copy实现多线程。

尝试阅读一个线程和写在另一个线程。 您将需要协调线程,以便写入线程不会开始写入缓冲区,直到读取线程完成填充。 你可以通过使用两个缓冲区来解决这个问题,一个正在被读取,另一个正在被写入,另一个标志则说明哪个缓冲区正在被用于哪个目的。

尝试删除刷新呼叫,并将其移动到循环之外。

有时操作系统知道什么时候刷新IO ..它允许它更好地使用它的内部缓冲区。

这是一个类似的答案

如何将一个流的内容复制到另一个流?

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

马克Russinovich将是这方面的权威。

他在自己的博客上写了一篇文章 “Vista SP1文件复制改进” ,其中总结了Windows SP1的最新技术状态。

我半读过的猜测是,File.Copy在最多的情况下是最健壮的。 当然,这并不意味着在某些特定的情况下,你自己的代码可能会打败它…