【问题标题】:How to speed up creation of a FileStream如何加快 FileStream 的创建
【发布时间】:2017-12-13 05:17:28
【问题描述】:

我的应用程序需要打开很多小文件,比如 1440 个文件,每个文件包含 1 分钟的数据来读取某一天的所有数据。每个文件只有几 kB 大。这是一个 GUI 应用程序,所以我希望用户(== 我!)不必等待太久。

事实证明,打开文件相当慢。经过研究,大部分时间都浪费在为每个文件创建一个 FileStream (OpenStream = new FileStream) 上。示例代码:

// stream en reader aanmaken
FileStream OpenStream;
BinaryReader bReader;

foreach (string file in files)
{
    // bestaat de file? dan inlezen en opslaan
    if (System.IO.File.Exists(file))
    {
        long Start = sw.ElapsedMilliseconds;

        // file read only openen, anders kan de applicatie crashen
        OpenStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        Tijden.Add(sw.ElapsedMilliseconds - Start);

        bReader = new BinaryReader(OpenStream);

        // alles in één keer inlezen, werkt goed en snel
        // -bijhouden of appenden nog wel mogelijk is, zonodig niet meer appenden
        blAppend &= Bestanden.Add(file, bReader.ReadBytes((int)OpenStream.Length), blAppend);

        // file sluiten
        bReader.Close();
    }
}

使用秒表计时器,我发现大部分(> 80%)时间都花在为每个文件创建 FileStream 上。创建 BinaryReader 并实际读取文件 (Bestanden.add) 几乎不需要任何时间。

我对此感到困惑,无法找到加快速度的方法。如何加快 FileStream 的创建速度?

更新问题:

  • Windows 7 和 Windows 10 都会发生这种情况
  • 文件是本地的(在 SSD 磁盘上)
  • 一个目录只有1440个文件
  • 奇怪的是,稍后再次读取(相同的)文件,突然创建 FileStreams 几乎没有花费任何时间。操作系统所在的某个地方 记住文件流。
  • 即使我关闭应用程序并重新启动它,“再次”打开文件也几乎不会花费任何时间。这使得它很难找到 性能问题。我不得不制作很多目录副本 一遍又一遍地重现问题。

【问题讨论】:

  • 似乎是一个可能的操作系统问题。您访问的是什么类型的操作系统?它是本地的还是网络上的(关闭运行应用程序的电脑)?目录是否包含其他文件(即 windows 对每个目录的文件数有建议限制)。
  • 这在 windows 7 和 windows 10 上都存在。这些文件位于仅包含这 1440 个文件的目录中。我刚刚意识到我忘了提到一些东西: - 我第一次想读取文件时它很慢 - 如果我再次从应用程序读取文件,创建 FileStreams 突然几乎没有时间(这怎么可能?是操作系统记得文件句柄吗?我的应用程序当然不是)。 - 如果我关闭应用程序并重新开始,再次读取相同的文件几乎不需要任何时间。操作系统中必须有某种缓冲/内存。
  • 你试过File.RealAllBytes吗?
  • 我刚刚使用 File.ReadAllBytes 进行了测试,并且行为是相同的(除了您无法再看到延迟的确切来源)。再次读取文件也几乎不花时间。
  • Windows 确实将文件缓存在内存中,因此更快的后续访问并不令人意外。您可以使用technet.microsoft.com/en-us/sysinternals/ff700229.aspx清除待机列表

标签: c# performance filestream


【解决方案1】:

免责声明:这个答案只是一个(有根据的)推测,它是一个 Windows 错误,而不是您可以使用不同代码修复的问题。

因此,此行为可能与此处描述的 Windows 错误有关:"24-core CPU and I can’t move my mouse"

这些进程都在从 NtGdiCloseProcess 中释放锁。

因此,如果FileStream 在操作系统中使用并持有这样一个关键锁,它会为每个文件等待几微秒,这将增加数千个文件。可能是不同的锁,但上面提到的bug至少增加了类似问题的可能性。

要证明或反驳这个假设,需要深入了解内核的内部工作原理。

【讨论】:

    【解决方案2】:

    正如您在对问题FileStream 的评论中提到的那样,通过创建对象来读取第一个 4K 到缓冲区。您可以更改此缓冲区的大小以反映更好的数据大小。 (例如,如果您的文件小于缓冲区,则减少)。如果您按顺序读取文件,您可以通过FileOptions 向操作系统提示有关此内容的提示。另外,您可以避免BinaryReader,因为您完全读取文件。

        // stream en reader aanmaken
        FileStream OpenStream;
    
        foreach (string file in files)
        {
            // bestaat de file? dan inlezen en opslaan
            if (System.IO.File.Exists(file))
            {
                long Start = sw.ElapsedMilliseconds;
    
                // file read only openen, anders kan de applicatie crashen
                OpenStream = new FileStream(
                    file,
                    FileMode.Open,
                    FileAccess.Read,
                    FileShare.ReadWrite,
                    bufferSize: 2048, //2K for example 
                    options: FileOptions.SequentialScan);
    
                Tijden.Add(sw.ElapsedMilliseconds - Start);
    
                var bufferLenght = (int)OpenStream.Length;
                var buffer = new byte[bufferLenght];
                OpenStream.Read(buffer, 0, bufferLenght);
    
                // alles in één keer inlezen, werkt goed en snel
                // -bijhouden of appenden nog wel mogelijk is, zonodig niet meer appenden
                blAppend &= Bestanden.Add(file, buffer, blAppend);
            }
        }
    

    我不知道Bestanden 对象的类型。但是如果这个对象有从数组中读取的方法,你也可以为文件重用缓冲区。

        //the buffer should be bigger than the biggest file to read
        var bufferLenght = 8192;
        var buffer = new byte[bufferLenght];
    
        foreach (string file in files)
        {
            //skip 
            ...
            var fileLenght = (int)OpenStream.Length;
            OpenStream.Read(buffer, 0, fileLenght);
    
            blAppend &= Bestanden.Add(file, /*read bytes from buffer */, blAppend);
    

    希望对你有帮助。

    【讨论】:

    • 我还没有注意到我可以设置缓冲区大小。这(4K 的标准缓冲区大小)解释了所有一直读取 4K 片段的流示例。我并行打开所有文件,这花费了我一些时间,因为我的 Bestanden 类结果不是线程安全的。我的感觉是,对于小文件,最好并行读取它们,而对于较大的文件,最好一个接一个地读取它们。我可能不得不对此进行试验。并行读取文件使一切速度提高了 +- 3 倍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多