为什么 DeflateStream 逐条读取很慢?

问题描述 投票:0回答:1

我有一个类可以以自定义二进制格式读取和写入一些数据。我的(Windows 窗体)应用程序必须在启动时读取该格式的文件。因为我的格式相当冗余,所以我使用了

DeflateStream
,它在压缩数据方面做得很好。

然而,与我的功能测试(185-200ms)相比,我的实际应用程序(6500-7200ms)很慢。该测试方法创建与我在应用程序中读取的完全相同的文件,然后为了测试而读回该文件并比较结果。关于阅读的一切都是相同的。经过一番尝试,我发现将整个

DeflateStream
推到
MemoryStream
比直接读取
DeflateStream
快得多(330-380 毫秒)。

缓慢接近:

System.Diagnostics.Stopwatch sw = new();
Tally tally = new();
string fileName = "data.dat";
using FileStream file = new(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
sw.Start();
try
{
    using DeflateStream ds = new(file, CompressionMode.Decompress, leaveOpen: true);
    tally.Read(ds); // slow
}
finally
{
    sw.Stop();
    MessageBox.Show($"Reading (Count = {tally.Count}) took {sw.ElapsedMilliseconds} ms.");
}

快速接近:

System.Diagnostics.Stopwatch sw = new();
Tally tally = new();
string fileName = "data.dat";
using FileStream file = new(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
sw.Start();
try
{
    using DeflateStream ds = new(file, CompressionMode.Decompress, leaveOpen: true);
    using MemoryStream ms = new();
    ds.CopyTo(ms);
    ms.Seek(0, SeekOrigin.Begin);
    tally.Read(ms); // quick
}
finally
{
    sw.Stop();
    MessageBox.Show($"Reading Tally (Count = {tally.Count}) took {sw.ElapsedMilliseconds} ms.");
}

差异是惊人的,我不知道我做错了什么。

c# .net deflate
1个回答
0
投票

假设您的

Tally.Read()
只是使用
DeflateStream.ReadByte()
逐字节读取流,那么显然 Microsoft 的
DeflateStream
在逐块而不是逐字节膨胀时性能要高得多。 接受这一点,如果您的算法需要逐字节读取,您可以轻松地匹配复制到
MemoryStream
并通过将
DeflateStream
包装在
BufferedStream
中并从副本中读取的性能,并使用适当的大缓冲区大小,例如 8192 字节:

const int BufferSize = 8192;

using FileStream file = new(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
using DeflateStream ds = new(file, CompressionMode.Decompress, leaveOpen: true);
using BufferedStream inputStream = new (ds, BufferSize);
tally.Read(inputStream); // Fast

通过此修复,所需的总时间变得比复制到临时MemoryStream稍微

或者,正如 Jon Skeetcomments 中建议的那样,如果您可以修改

Tally.Read()
方法以将流读入缓冲区,然后迭代缓冲区,您可以获得类似的性能。

例如,如果您原来的

Tally
看起来像:

class Tally
{
    public long Count { get; set; } = 0;

    public void Read(Stream s)
    {
        while (s.ReadByte() is var b && b >= 0)
            Count += b;
    }
}

如果我修改它以使用小的

Span<byte>
缓冲区,如下所示:

const int BufferSize = 128;

public void Read(Stream s)
{
    Span<byte> span = stackalloc byte [BufferSize];

    while (s.Read(span) is var count && count > 0)
        foreach (var b in span.Slice(0, count))
            Count += ((int)b);
}

然后性能再次变得比使用

MemoryStream
更快。

演示此处显示以下结果:

Directly reading from DeflateStream (Count = 122842320) took 111 ms.
Reading from a MemoryStream copied from a DeflateStream (Count = 122842320) took 14 ms.
Reading from a BufferedStream wrapping a DeflateStream (Count = 122842320) took 10 ms.
Directly reading from DeflateStream using a 128-byte span (Count = 122842320) took 11 ms.

正如您所见,使用

BufferedStream
Span<byte>
缓冲区可比从 DeflateStream 逐字节读取提供
10 倍加速

© www.soinside.com 2019 - 2024. All rights reserved.