【发布时间】:2014-08-14 13:59:17
【问题描述】:
我正在使用代码将文件从一个位置复制到另一个位置,同时动态生成校验和。对于小文件,代码可以正常运行,但对于大文件,例如 3.8GB 文件,它的行为很奇怪:复制大约 1 GB 后,它突然变慢了相当快,然后越来越慢(例如,在达到 1 GB 之前,我观察到每秒复制大约 2%-4% 的文件,然后当达到 1 GB 时,每 % 的文件大约需要 4-6 秒)。
int bytesRead = 0;
int bytesInWriteBuffer = 0;
byte[] readBuffer = new byte[1638400];
byte[] writeBuffer = new byte[4915200];
MD5 md5Handler = new MD5CryptoServiceProvider();
using (FileStream sourceStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
md5Handler.TransformBlock(readBuffer, 0, bytesRead, null, 0);
FileStream destinationStream = File.Create(storageFileName);
while (bytesRead = sourceStream.Read(readBuffer, 0, readBuffer.Length))
{
Buffer.BlockCopy(readBuffer, 0, writeBuffer, bytesInWriteBuffer, bytesRead);
bytesInWriteBuffer += bytesRead
if (bytesInWriteBuffer >= 4915200)
{
destinationStream.Write(writeBuffer, 0, bytesInWriteBuffer);
bytesInWriteBuffer = 0;
Thread.Sleep(50);
}
}
}
正如评论中所问的:没有可以观察到的内存泄漏。内存使用量在方法开始时增加,然后保持稳定(运行它的 pc 上的总内存使用量,包括运行 mthod 时的总内存使用量为 56%(对于在该 pc 上总共运行的所有应用程序))。 PC 的总内存为 8 GB。
应用程序本身是 32 位的(本身占用大约 300 MB 内存),使用的框架是 4.5。
作为测试后的更新建议评论:当我通过令牌制作副本并取消它并删除文件(所有在减速开始之后),并立即开始第二次复制过程时,它与另一个是在我取消它的时候(所以减速已经在 1 GB 之前开始了)。但是当我在删除完成后制作第二份副本时,它会正常启动,并且只会在 1 GB 时减速。
同样刷新目标流在那里没有区别。
为了减慢复制速度,起初大约是每秒 84MB,然后在 1 GB 时减慢到每秒大约 14MB。
作为这个问题的一部分(不确定是否更好作为评论):这可能不是 C# 相关的问题,而是“完全”来自操作系统的缓存机制问题吗? (如果可以的话,可以在那里做点什么)
按照建议,我查找了操作系统的 writecache 并运行了性能监视器。 结果:
- 不同的源硬盘和源桌面有相同的结果,也有相同的减速时刻
- 操作系统(目标)中的写入缓存已禁用
- 目标所在服务器上的性能监控显示没有任何意义(写入队列长度只有一次在 4 和一次在 2、写入时间/空闲时间和写入/秒都没有显示 100% 使用缓存或别的东西)。
进一步的测试显示了以下行为:
- 如果在每次写入后通过执行 200 毫秒
Thread.Sleep来减慢复制本身的速度,则平均复制速率为 30 MB/秒,这是恒定的 - 如果我改为在每传输 500 MB 或 800 MB 后延迟 5 秒 (
Thread.Sleep),速度会再次变慢,等待不会改变任何事情。 - 如果我更改位置以使源和目标位于我的本地硬盘驱动器上(通常目标位于网络文件夹上),则速率恒定为 50 MB/s,而读取时间为 100%,瓶颈在那里,写入时间远低于 100%。
- 网络传输监控未显示任何意外情况
- 在将 3 GB 文件从同一源复制到同一目标时,Windows 资源管理器的传输速率为 11 MB/s(因此,尽管总体上发生了减速,但 C# 复制方法比 Windows 资源管理器复制更快)
进一步的行为:
- 根据监控,有一个恒定的流到目标驱动器(因此没有快速的第一部分和减速,但目标以相同的速度不断接收字节)。
作为补充:3 GB 文件的总性能约为 37 MB/s(第一个 GB 为 84 MB,另一个 GB 为 14 MB)。
【问题讨论】:
-
您在文件复制期间是否跟踪了内存使用情况?
-
yepp 内存在方法启动时确实会增加一点(正如预期的那样,由于缓冲区很大),但随后保持稳定并且不会以任何形式奇怪地增加。
-
Thread.Sleep()似乎很奇怪。为什么? -
为什么复制块只是为了写出它——为什么不直接从 ReadBuffer 中写出呢?如果您注释掉 MD5 部分,它是否也会变慢?
-
我是认真的。使用网络搜索了解更多信息。您可以将您的线程标记为执行低优先级 IO。如果没有人在正常优先级与您竞争,您将获得完整的 IO 性能。否则你会被打断,需要等待。