【问题标题】:Performant File Copy in C#?C# 中的高性能文件复制?
【发布时间】:2010-12-13 05:31:00
【问题描述】:

我有一个包含大约 500k jpg 文件的巨大目录,我想归档所有早于某个日期的文件。目前,该脚本需要数小时才能运行。

这与 GoGrid 存储服务器的性能非常差有很大关系,但与此同时,我确信有一种更有效的方式 Ram/Cpu 明智地完成我正在做的事情。

这是我的代码:

var dirInfo = new DirectoryInfo(PathToSource);
var fileInfo = dirInfo.GetFiles("*.*");
var filesToArchive = fileInfo.Where(f => 
    f.LastWriteTime.Date < StartThresholdInDays.Days().Ago().Date
      && f.LastWriteTime.Date >= StopThresholdInDays.Days().Ago().Date
);

foreach (var file in filesToArchive)
{
    file.CopyTo(PathToTarget+file.Name);
}

Days().Ago() 只是语法糖。

【问题讨论】:

  • 那个依赖主机操作系统,应该是一流的。
  • 是的,事实是那里可能有数百万个文件,由于类似的性能问题,我什至无法通过 Windows 资源管理器获取目录计数。
  • 语法纳粹说:“表演”不是一个词:)
  • Performant 就是这么一个词。 dictionary.reference.com/browse/performant
  • 嗯,这是因为它被使用了,而字典是一个活生生的、不断变化的东西。但在技术意义上,它与“Homie”一样多。

标签: c# performance file-copying


【解决方案1】:

您可以尝试使用(有限数量的)线程来执行 CopyTo()。目前整个操作仅限于 1 个核心。

这只会在现在受 CPU 限制的情况下提高性能。但如果这在 RAID 上运行,它可能会起作用。

【讨论】:

  • 我相信 GoGrid 是“在云端”。活动连接可能存在限制。无论如何,很好的建议。
【解决方案2】:

我会牢记 80/20 规则 并注意,如果大部分减速是 file.CopyTo,并且这种减速远远超过 LINQ 查询的性能,那么我不会担心。您可以通过删除file.CopyTo 行并将其替换为Console.WriteLine 操作来测试这一点。时间与真实副本。您会发现 GoGrid 与其他操作相比的开销。我的预感是你不会有任何现实的大收获

编辑:好的,所以 80% 是 GetFiles 操作,如果实际上目录中有一百万个文件,这并不奇怪。您最好的选择可能是直接开始使用 Win32 API(如FindFirstFilefamily)和P/Invoke

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
static extern IntPtr FindFirstFile(string lpFileName, 
    out WIN32_FIND_DATA lpFindFileData);

如果可能,我还建议更改目录结构以减少每个目录的文件数。这将极大地改善这种情况。

EDIT2:我还考虑将GetFiles("*.*") 改为GetFiles()。既然你要求一切,让它在每一步都应用通配规则是没有意义的。

【讨论】:

  • 大部分操作是 dirInfo.GetFiles(".") 语句。我正在用只有 5 天的文件进行测试,并且在我什至可以计算目录中用于执行 linq 查询的文件之前,我就用完了 RAM/Patience。有没有更好的 GetFiles[] 方法,比如让 GetFiles[] 返回某个范围内的文件,而不是必须全部返回?至少这样,我第一次可以将这个操作分成 10% 的块,然后让归档器每晚运行。就目前而言,我真的无处可去。
  • 是的,改变目录结构是我想要做的,但首先我需要访问文件而不需要等待一整天并让服务器超时:)
【解决方案3】:

我认为您可以改进的唯一部分是dirInfo.GetFiles("*.*")。在 .NET 3.5 及更早版本中,它返回一个包含所有文件名的数组,这需要时间来构建并使用大量 RAM。在 .NET 4.0 中,有一个新的 Directory.EnumerateFiles 方法,它返回一个 IEnumerable&lt;string&gt;,并在从磁盘读取结果时立即获取结果。这可能会稍微提高性能,但不要指望奇迹......

【讨论】:

  • 实际上这正是需要做的,EnumerateFiles 返回 Enumerator 而不是整个列表。您保存了阵列所需的所有内存。假设它的 500k 文件 * 100 字节 = 50MB 的 RAM。使用 Enumerate 你只会用完 100 字节,因为你一次得到 1 个文件。
  • +1,.Net 4.0 在 System.IO 中有很多非常好的特性。不确定它是否会改善目录中有一百万个文件的情况:-D
【解决方案4】:

您应该考虑使用第三方实用程序为您执行复制。 robocopy 之类的东西可能会显着加快您的处理速度。另见https://serverfault.com/questions/54881/quickest-way-of-moving-a-large-number-of-files

【讨论】:

  • 并且robocopy默认包含在Win7和Server 2008中!
【解决方案5】:

收听这个Hanselminutes podcast。 Scott 与 Banshee 媒体播放器的作者 Aaron Bockover 交谈,他们遇到了这个确切的问题,并在 8:20 在播客中谈论它。

如果您可以使用 .Net 4.0,请使用 Thomas Levesque 提到的 Directory.EnumerateFiles。如果没有,那么您可能需要像在 Mono.Posix 中那样使用本机 Win32 API 编写自己的目录遍历代码。

【讨论】:

    【解决方案6】:

    【讨论】:

    • 感谢 Mauricio...这适用于 RAM 问题,但不适用于 CPU。仍然需要几个小时才能完成,但至少 RAM 不会在我身上膨胀。
    • 这足以解决我的问题。大约需要 2 小时,但现在它可以在后台运行,最多使用 4 兆内存,而以前,它会使用数百兆内存。
    猜你喜欢
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-11
    • 2012-07-16
    • 1970-01-01
    • 1970-01-01
    • 2016-07-12
    相关资源
    最近更新 更多