我的问题是关于文件复制性能。 我们有一个媒体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有关的关于慢速复制性能的线程)。
提高方法性能的几个线索:
异步复制模式示例:
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。
三个改变将大大提高性能:
这在我尝试的实验中似乎快了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在最多的情况下是最健壮的。 当然,这并不意味着在某些特定的情况下,你自己的代码可能会打败它…