首页 > 解决方案 > Stream.Read() 性能缓慢

问题描述

使用memorystream.Read(arr, 0, length)19 Mb 文件将 Stream 转换为 Array。在 machine1 中运行时大约需要 1.26 秒,在 Machine2 中大约需要 3 秒。为什么会有性能差异?!这与机器,CPU的ram使用情况有关吗?!我们需要增加内存吗?!

using (var pdfContent = new MemoryStream(System.IO.File.ReadAllBytes(path))) 
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    byte[] buffer = new byte[pdfContent.Length];
    pdfContent.Read(buffer, 0, (int)pdfContent.Length);
    stopwatch.Stop();
    Console.WriteLine($"End Time:{stopwatch.Elapsed} ");
}

标签: c#.netstreambuffermemorystream

解决方案


TL;DR: 1. 文件操作的结果很大程度上取决于您的机器配置(在此类测试中,硬盘的类型甚至型号是最关键的)。2.你应该逐块读取文件。

让我们看一下这个例子。我准备了一个 21042116 字节的测试文本文件,即 21Mb,创建一个新的控制台应用程序并添加了基准库:BenchmarkDotNet

using System;
using System.Diagnostics;
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace stream_perf
{
    [SimpleJob(RuntimeMoniker.NetCoreApp50)]
    [RPlotExporter]
    public class StreamBenchmarks
    {
        [Benchmark]
        public void Stackoverflow()
        {
            using (var pdfContent = new MemoryStream(System.IO.File.ReadAllBytes("payload.txt"))) 
            {
                byte[] buffer = new byte[pdfContent.Length];
                pdfContent.Read(buffer, 0, (int)pdfContent.Length);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<StreamBenchmarks>();
        }
    }
}

使用控制台运行两个命令:

dotnet build -c release
dotnet run -c release

这给了我以下结果:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i5-8300H CPU 2.30GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.103
  [Host]        : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
  .NET Core 5.0 : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT

Job=.NET Core 5.0  Runtime=.NET Core 5.0

|        Method |     Mean |    Error |   StdDev |
|-------------- |---------:|---------:|---------:|
| Stackoverflow | 24.24 ms | 0.378 ms | 0.353 ms |

正如您在我的机器上看到的那样,它非常快。 但它足够快吗?不,它没有,因为我们两次读取该文件数据,第一次我们在这里读取文件:System.IO.File.ReadAllBytes("payload.txt")第二次我们在这里读取文件:pdfContent.Read(buffer, 0, (int)pdfContent.Length);。所以我在我的基准测试中添加了以下方法:

[Benchmark]
public void ReadChunked()
{
    int totalBytes = 0;
    int readBytes = 0;
    using (var pdfStream = new System.IO.FileStream("payload.txt", FileMode.Open))
    {
        byte[] buffer = new byte[4096];
        while ((readBytes = pdfStream.Read(buffer)) != 0) {
            // do something with buffer
            totalBytes += readBytes;
        }
    }
}

在那个新方法中,我们逐块读取文件,这给了我们一些优势:

  1. 一回读文件
  2. 我们不需要在 RAM 中分配等于文件大小的缓冲区

让我们看一下基准:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i5-8300H CPU 2.30GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.103
  [Host]        : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
  .NET Core 5.0 : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT

Job=.NET Core 5.0  Runtime=.NET Core 5.0

|        Method |     Mean |    Error |   StdDev |
|-------------- |---------:|---------:|---------:|
| Stackoverflow | 23.85 ms | 0.149 ms | 0.132 ms |
|   ReadChunked | 18.68 ms | 0.076 ms | 0.071 ms |

新方法在 21% 上更快


推荐阅读