我有一个类可以以自定义二进制格式读取和写入一些数据。我的(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.");
}
差异是惊人的,我不知道我做错了什么。
假设您的
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 Skeet 在 comments 中建议的那样,如果您可以修改
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 倍加速。