提高分割文件的速度

12
我正在使用这段代码从文件中提取一个块。
// info is FileInfo object pointing to file
var percentSplit = info.Length * 50 / 100; // extract 50% of file
var bytes = new byte[percentSplit];
var fileStream = File.OpenRead(fileName);
fileStream.Read(bytes, 0, bytes.Length);
fileStream.Dispose();
File.WriteAllBytes(splitName, bytes);

有没有办法加快这个过程的速度?

目前对于一个530 MB的文件,需要大约4-5秒的时间。可以提高这个时间吗?


1
提取文件的50%并不高效为什么是4kb到8kb。如果您使用的是.Net 4或更高版本,可以使用内存映射文件 - Jeremy Thompson
1
你的磁盘系统性能如何?100MB/s听起来相当合理。 - Alexei Levenkov
我可以问一下你为什么要分割这个文件吗?你是想把文件分割作为最终结果呢,还是这只是为了解决其他问题而采取的中间步骤? - Scott Chamberlain
2
大于85KB的数组将最终位于很少收集和不压缩的大对象堆上。因此,如果这是从长时间运行的进程中经常调用的东西,您可能会遇到将200多MB读入数组的内存问题。 - devgeezer
1
写入530/2 Mb需要5秒钟,这是常规磁盘子系统的足够性能。程序算法似乎不是瓶颈。 - Sergey P. aka azure
显示剩余7条评论
3个回答

8

有几种情况需要考虑,但都与语言无关

以下是需要注意的一些事项:

  • 源文件/目标文件的文件系统是什么?
  • 您是否想保留原始源文件?
  • 它们是否在同一驱动器上?

在C#中,您几乎没有一个比File.Copy更快的方法,因为它在内部调用WINAPICopyFile。由于百分比为五十,因此以下代码可能不会更快。它复制整个文件,然后设置目标文件的长度。

var info=new FileInfo(fileName);
var percentSplit=info.Length*50/100; // extract 50% of file

File.Copy(info.FullName, splitName);
using(var outStream=File.OpenWrite(splitName))
    outStream.SetLength(percentSplit);

此外,如果:

  1. 您在文件分割后不保留原始源
  2. 目标驱动器与源相同
  3. 您未使用启用加密/压缩的文件系统

那么,最好的做法是根本不复制文件。例如,如果您的源文件位于FATFAT32文件系统中,您可以:

  1. 为新拆分的文件部分创建新的目录条目(entries)
  2. 让目录条目(entries)指向目标部分的簇
  3. 为每个条目设置正确的文件大小
  4. 检查交叉链接并避免它们

如果您的文件系统是NTFS,那么您可能需要花费很长时间学习规范。

祝你好运!


+1:Ken,我已经删除了我的答案,因为我发现一个相当严重的错误,这意味着我的方法不能可靠地执行,并且一旦修复后实际上比你的方法慢得多。我将非常有兴趣看看是否有什么东西可以打败File.Copy的性能。 - nick_w
这实际上是任何建议解决方案的良好基准,应该运行大约两倍快。假设File.Copy()在给定系统的最大值下运行,仅复制其中一半应该需要大约一半的时间。 - Hazzit

2
var percentSplit = (int)(info.Length * 50 / 100); // extract 50% of file
var buffer = new byte[8192];
using (Stream input = File.OpenRead(info.FullName))
using (Stream output = File.OpenWrite(splitName))
{
    int bytesRead = 1;
    while (percentSplit > 0 && bytesRead > 0)
    {
        bytesRead = input.Read(buffer, 0, Math.Min(percentSplit, buffer.Length));
        output.Write(buffer, 0, bytesRead);
        percentSplit -= bytesRead;
    }
    output.Flush();
}

刷新可能并不需要,但也没有坏处。这很有趣,将循环从while改为do-while对性能有很大影响。我想IL(Intermediate Language)并不那么快。我的电脑原本运行的代码需要4-6秒,而附加的代码似乎只需要1秒。


0

在读/写几兆字节的块时,我可以获得更好的结果。性能也取决于块的大小。

FileInfo info = new FileInfo(@"C:\source.bin");
FileStream f = File.OpenRead(info.FullName);
BinaryReader br = new BinaryReader(f);

FileStream t = File.OpenWrite(@"C:\split.bin");
BinaryWriter bw = new BinaryWriter(t);

long count = 0;
long split = info.Length * 50 / 100;
long chunk = 8000000;

DateTime start = DateTime.Now;

while (count < split)
{
    if (count + chunk > split)
    {
        chunk = split - count;
    }

    bw.Write(br.ReadBytes((int)chunk));
    count += chunk;
}

Console.WriteLine(DateTime.Now - start);

您不应分配大于85K的块。请参见问题中devgeezer的备注。 - Simon Mourier
分配大于85k的块是可以的。实际上,越大越好,只要尽可能多地重用该块。唯一的问题是大对象堆的碎片化,这可能导致内存不足异常。重复使用大缓冲区将防止出现此问题,并且当不再使用缓冲区(并且需要内存)时,它将被收集。没有问题。 - Herman Schoenfeld

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