在C#中实现高效的文件复制?

7

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

这与GoGrid的存储服务器性能非常差有很大关系,但同时,我相信有更有效的方式在内存/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() 只是一种语法糖。


这取决于主机操作系统,应该是顶尖的。 - John Gietzen
2
语法纳粹说:“Performant”不是一个词 :) - Ed S.
2
"Performant"是一个词。http://dictionary.reference.com/browse/performant - JSBձոգչ
每个单词都是在某个时候通过使用而确立的,通过制定关于什么是“技术上”的单词或不是单词的规则来抵制语言的演变,就相当于拒绝采用新技术。一个单词的真正测试是读者是否理解作者在使用它时的意思。 - trampster
对于一个好的实际问题,肯定会影响到大多数大型网站,给予+1。 - Neil Fenwick
显示剩余2条评论
6个回答

10

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


实际上这正是需要做的,EnumerateFiles返回的是枚举器而不是整个列表。你可以节省数组所需的所有内存。假设有500k个文件* 100字节= 50MB的RAM。使用枚举器只会使用100字节,因为每次只获取1个文件。 - Kugel
+1,.Net 4.0在System.IO方面有很多非常好的功能。不确定它是否会改善目录中有一百万个文件的情况 :-D - user7116

3

谢谢Mauricio…这个解决了RAM的问题,但不是CPU。完成仍需要数小时,但至少RAM不会爆掉。 - Scott Klarenbach
这个解决方案已经足够好,可以解决我的问题。虽然需要大约2小时的时间,但现在它可以在后台运行,并且最多只使用4兆字节的RAM,而以前则会使用数百兆字节。 - Scott Klarenbach

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

我建议,如果可能的话,改变目录结构以减少每个目录中的文件数量。这将极大地改善情况。
另外,我建议考虑从GetFiles("*.*")更改为GetFiles()。既然你要求的是所有内容,没有必要在每一步应用globbing规则。

大部分操作都是dirInfo.GetFiles(".")语句。我正在进行一个只有5天文件的测试,但在我甚至无法从中获取文件计数以执行linq查询之前,我就已经耗尽了RAM/耐心。是否有更好的GetFiles[]方法,比如只返回在范围内的文件,而不必全部返回?至少这样,我可以将此操作分成10%的块,第一次运行时,然后每晚运行归档程序。目前情况下,我真的无法走到任何地方。 - Scott Klarenbach
是的,改变目录结构是我的首要目标。但首先我需要在不等待整天和超时服务器的情况下访问文件。 :) - Scott Klarenbach

2
你应该考虑使用第三方工具来为你执行复制操作。像robocopy这样的工具可以显著加快处理速度。请参阅此处了解更多信息。

2
而且 robocopy 工具已经默认包含在 Win7 和 Server 2008 中! - joshperry

1

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

只有当它现在受到CPU限制时,才能提高性能。但如果这在RAID上运行,则可能有效。


我相信GoGrid是“云计算”中的一员。可能会有关于活动连接的限制。不过,这是一个好建议。 - user7116

0
听一下Hanselminutes podcast,这是关于程序开发的。Scott与Banshee媒体播放器的作者Aaron Bockover进行了交流,在播客的8:20谈到了他们遇到的确切问题。
如果你可以使用.Net 4.0,那么像Thomas Levesque提到的那样使用Directory.EnumerateFiles。如果不能,你可能需要编写自己的目录遍历代码,就像在Mono.Posix中使用本机Win32 API一样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接