【问题标题】:.net File.Copy very slow when copying many small files (not over network).net File.Copy 复制许多小文件时非常慢(不是通过网络)
【发布时间】:2012-07-07 09:44:54
【问题描述】:

我正在为自己制作一个简单的文件夹同步备份工具,但在使用 File.Copy 时遇到了很大的障碍。在测试将一个包含约 44,000 个小文件(Windows 邮件文件夹)的文件夹复制到我系统中的另一个驱动器时,我发现使用 File.Copy 比使用命令行和运行 xcopy 复制相同的文件/文件夹慢 3 倍以上。我的 C# 版本需要 16 多分钟来复制文件,而 xcopy 只需要 5 分钟。我曾尝试寻求有关此主题的帮助,但我发现的只是人们抱怨通过网络复制大文件的速度很慢。这既不是大文件问题,也不是网络复制问题。

我找到了一个interesting article about a better File.Copy replacement,但发布的代码有一些错误,导致堆栈出现问题,而且我还远没有足够的知识来解决他的代码中的问题。

是否有任何常用或简单的方法可以将 File.Copy 替换为更快速的方法?

【问题讨论】:

    标签: c# .net windows performance copy


    【解决方案1】:

    我在这个级别没有很好的经验。为什么不尝试运行包含 xcopy 命令的批处理文件?查看此帖子:Executing Batch File in C#

    【讨论】:

      【解决方案2】:

      要考虑的一件事是您的副本是否具有在副本期间更新的用户界面。如果是这样,请确保您的副本在单独的线程上运行,否则您的 UI 将在复制期间冻结,并且副本将通过阻塞调用来更新 UI 来减慢速度。

      我编写了一个类似的程序,根据我的经验,我的代码比 Windows 资源管理器副本运行得更快(不确定来自命令提示符的 xcopy)。

      此外,如果您有 UI,请不要更新每个文件;而是更新每 X MB 或每 Y 个文件(以先到者为准),这样可以减少 UI 实际可以处理的更新量。我每 .5MB 或 10 个文件使用一次;这些可能不是最佳的,但它显着提高了我的复制速度和 UI 响应能力。

      另一种加快速度的方法是使用 Enumerate 函数而不是 Get 函数(例如 EnumerateFiles 而不是 GetFiles)。这些函数会尽快开始返回结果,而不是在列表构建完成后等待返回所有内容。它们返回一个 Enumerable,因此您可以在结果上调用 foreach:foreach(System.IO.Directory.EnumerateDirectories(path)) 中的字符串文件。对于我的程序,这也对速度产生了显着影响,并且在像您这样的情况下会更有帮助。目录包含许多文件。

      【讨论】:

      • 我的界面确实在后台线程上。感谢您提供有关减少更新显示频率的提示。不幸的是,这对我的复制时间来说并不是真正的问题。在完全禁用 UI 更新的情况下,复制时间与以前相同。我会看看使用 EnumerateFiles 是否有帮助。
      • 刚刚得到了使用IEnumerables的结果。它根本没有帮助文件复制时间,可能是因为我要逐个文件夹复制文件,所以大多数时候执行 GetFiles 并不需要很长时间。但是,它确实有助于使初始文件计数过程更加顺畅。
      • 哦,至于 Windows 资源管理器的复制时间...是的,我很想知道您的代码与 XCopy 相比如何。我开始尝试使用 Windows 资源管理器复制文件夹,然后……嗯,是的。它告诉我在 1 到 5 个小时之间。我敢肯定它不会运行那么长时间,但我不想浪费时间去找出答案。因此,比资源管理器副本运行得更快并不难实现。 ;)
      • 枚举函数不会加快实际复制的速度,所以如果这是问题,您可能需要使用另一种方法来进行复制。我给出的建议是基于您有许多小文件的情况,在这种情况下,尽量减少副本之间的时间会有所不同。如果这没有帮助,您可能不得不追求 CopyFileEx。貌似在这个帖子中使用成功了:stackoverflow.com/a/187842/1507945
      • 我在打字的时候好像你发了几条信息。太糟糕了,它没有帮助。我的下一个建议是遵循其他帖子的指示。让我知道事情的后续。如果它有很大的不同,我可能不得不考虑将它添加到我的备份软件中。
      【解决方案3】:

      有两种更快的文件复制算法:

      如果源和目标是不同的磁盘则:

      • 一个线程连续读取文件并存储在缓冲区中。
      • 另一个线程从该缓冲区连续写入文件。

      如果源和目标是同一个磁盘,那么:

      • 读取固定的字节块,例如每次 8K,无论有多少文件。
      • 在一个文件或多个文件中将该固定块写入目标。

      这样您将获得显着的性能。

      替代方案是您只需从您的 .net 代码中调用 xcopy。为什么要使用 File.Copy 来做这件事。您可以使用 Process.StandardOutput 捕获 xcopy 输出并显示在屏幕上,以便向用户展示正在发生的事情。

      【讨论】:

        【解决方案4】:

        我认为您至少可以将其并行化,以便同时处理两个文件。当一个线程正在写入时,另一个线程可能已经在读取下一个文件。如果你有一个文件列表,你可以这样做。使用多个线程将无济于事,因为这会使驱动器移动更多,而不是能够顺序写入..

         var files = new List<string>();
         // todo: fill the files list using directoryenumeration or so...
         var po = new ParallelOptions() {MaxDegreeOfParallelism = 2};
         Parallel.ForEach(files, po, CopyAFile);
        
         // Routine to copy a single file
         private void CopyAFile(string file) { }
        

        【讨论】:

          【解决方案5】:

          在旋转磁盘上最能减慢 IO 操作的因素之一是移动磁盘磁头。

          可以合理地假设并且可能非常准确,您的许多小文件(都相互关联)在磁盘上的距离比它们与副本目标的距离更近(假设您从一个部分进行复制)一个磁盘到同一磁盘的另一部分)。如果你先复制一点,然后再写一点,就会为其他进程打开一个机会窗口,让其他进程移动源磁盘或目标磁盘上的磁头。

          XCopy 比 Copy 做得更好的一件事(在这两种情况下都是命令)是 XCopy 在开始将这些文件写出到目标之前读取一堆文件。

          如果您要在同一个磁盘上复制文件,请尝试分配一个大缓冲区以一次读取多个文件,然后在缓冲区已满时将这些文件写出。

          如果您正在从一个磁盘读取并写入另一个磁盘,请尝试启动一个线程以从源磁盘读取,并启动一个单独的线程以写入另一个磁盘。

          【讨论】:

          • 感谢您的宝贵信息!我对尝试像您描述的 XCopy 所做的那样缓冲读/写特别感兴趣。我用 50mb 的缓冲区做了一些测试,发现它让我的复制时间缩短到了 14 分 40 秒。所以不是一个惊人的改进,而是更好。仍然落后于 XCopy 时代。我会看看线程读取/写入是否有帮助...
          • 实际上,在我意识到基于 FileStream 的缓冲复制系统没有复制文件属性(属性、创建时间等)之后,我回到了 16 分钟。时间回到了使用 File.Copy 的位置,只是我失去了 50MB 的内存来缓冲。 :(
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-06-05
          • 2014-07-23
          • 2015-08-19
          • 2013-01-13
          • 2013-08-09
          • 2010-10-08
          • 2014-07-16
          相关资源
          最近更新 更多