2009-08-07 62 views
32

我的问题是关于文件复制性能。我们有一个媒体管理系统,需要将文件系统上的大量移动文件移动到不同的位置,包括同一网络上的Windows共享,FTP站点,AmazonS3等等。当我们都在一个Windows网络中时,我们可以放弃使用System.IO.File.Copy(源,目标)复制文件。由于很多时候我们只有一个输入流(就像一个MemoryStream),我们尝试抽象复制操作来获取输入流和输出流,但是我们看到性能大幅下降。以下是一些用于复制文件以用作讨论点的代码。File.Copy与手动FileStream.Write复制文件

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 )。提前致谢!

+3

这可能与本机互操作有关。我怀疑File.Copy()和流IO操作是建立在Windows API之上的,并且在一个循环中重复调用流读/写比一个本地拷贝文件调用File.Copy()更加昂贵,会做出。 – 2009-08-07 20:50:13

+0

@Steve:你是对的,看到我的回应。 – 2009-08-07 21:26:18

回答

23

File.Copy是围绕打造CopyFile Win32函数和此功能需要大量的注意力从MS船员(记住这个Vista的关于慢速复制性能的相关线程)。

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

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

突出的一件事是您正在阅读一个块,编写该块,读取另一个块等。

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

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

+0

我目前正在调查多线程。是否有很好的开源项目能够做到这一点?我会继续调查。感谢您及时的回复。 – jakejgordon 2009-08-07 20:52:34

1

尝试删除Flush调用,并将其移动到循环之外。

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

+0

我也不认为复制操作涉及多线程,我个人认为这是一个坏主意。这意味着要为每个复制操作创建一个线程,这可能比使用流成本更高。 – 2009-08-07 20:56:45

+0

@aviadbenov:创建自己的线程来处理IO操作确实是过度的。但是.NET为此目的明确维护了一个线程池。使用异步IO调用正确地允许我们禁止这些线程而不必自己创建和销毁它们。 – AnthonyWJones 2009-08-07 22:03:40

+0

@Anthony:你说的是真的,但也很危险。如果许多线程将复制文件,则线程池本身将成为复制操作的瓶颈! – 2009-08-08 06:35:14

4

三个变化将极大地提高性能:

  1. 你打开你的FILESTREAM后增加您的缓冲区的大小,尽量1MB(井 - 只是实验)
  2. ,调用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); 
      } 
     } 
    } 
1

标记Russinovich将这个权力。

他在他的blog上写了一个条目Inside Vista SP1 File Copy Improvements,其中总结了Windows SP1的最新技术状态。

我的半受教育的猜测是File.Copy在最多的情况下是最健壮的。当然,这并不在某些特定的极端情况的意思是,你自己的代码可能战胜它......

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

6

你永远不会能够在做一些这样fundemental用自己的代码击败操作系统,甚至没有你在汇编程序中精心制作。

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

相关问题