【问题标题】:Improve speed of splitting file提高分割文件的速度
【发布时间】:2013-01-25 12:31: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 秒。这个时间可以改善吗?

【问题讨论】:

  • 提取文件的 50% 效率不高why 4kb to 8kb。如果您有 .Net 4 或更高版本,您可以使用 Memory Mapped Files
  • 您的磁盘系统性能如何? 100MB/s 听起来确实很合理。
  • 请问您要分割文件是为了什么?拆分文件是您的最终结果还是解决另一个问题的中间步骤?
  • 大于 85KB 的数组最终会被收集到很少被收集的从未压缩过的大对象堆上。因此,如果这是在长时间运行的进程中经常调用的内容,您可能会遇到将 200+MB 读取到数组中的内存问题。
  • 5 秒写入 530/2 Mb 是常规磁盘子系统的足够性能。程序算法似乎不是瓶颈

标签: c# filestream file-copying


【解决方案1】:

您有几个问题,但都与语言无关

以下是值得关注的事情

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

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

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. 为文件的新拆分部分创建新的目录条目(条目)
  2. 让 entry(entries) 指向目标部件的集群
  3. 为每个条目设置正确的文件大小
  4. 检查交叉链接并避免这种情况

如果您的文件系统是NTFS,您可能需要花很长时间来研究规范。

祝你好运!

【讨论】:

  • +1:Ken,我删除了我的答案,因为我发现了一个相当严重的错误,这意味着我的方法不能可靠地执行,并且一旦修复实际上比你的慢得多。我真的很想看看是否有任何东西可以真正击败File.Copy 的性能。
  • 对于任何建议的解决方案来说,这实际上都是一个很好的基准,它的运行速度应该是原来的两倍。假设 File.Copy() 以给定系统的最大值运行,则仅复制其中一半应该花费大约一半的时间。
【解决方案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();
}

可能不需要刷新,但它没有伤害,这很有趣,将循环更改为 do-while 而不是 while 有一个很大的打击性能。我想IL没有那么快。我的电脑在 4-6 秒内运行原始代码,附加代码似乎在 1 秒左右运行。

【讨论】:

    【解决方案3】:

    以几兆字节的块读取/写入时,我得到了更好的结果。性能也会根据块的大小而变化。

    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 备注。
    • 分配大于 85k 的块是可以的。事实上,越大越好,只要你尽可能多地重用那个块。唯一的问题是大对象堆的碎片,这可能导致内存不足异常。重用大缓冲区将防止这种情况发生,并且当不再使用缓冲区(并且需要内存)时,它将被收集。没问题。
    【解决方案4】:

    我认为解决这个问题的最快方法是通过二进制缓冲文件输入。

    我对 C# 不是很熟悉,但是您可以研究一些缓冲技术来转到文件的特定部分,并从该部分进行缓冲输入。

    您还可以使用带有 Windows API 的 C++ 来实现本机缓冲技术,或者查找具有快速缓冲方法的库。

    我不能建议太多,希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2012-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-29
      • 2014-01-20
      • 2023-03-03
      • 1970-01-01
      相关资源
      最近更新 更多