gpt4 book ai didi

c# - 如何在 C# 中快速读取二进制文件? (ReadOnlySpan 与 MemoryStream)

转载 作者:太空狗 更新时间:2023-10-29 14:12:21 30 4
gpt4 key购买 nike

我正在尝试尽可能快地解析二进制文件。所以这是我第一次尝试做的:

using (FileStream filestream = path.OpenRead()) {
using (var d = new GZipStream(filestream, CompressionMode.Decompress)) {
using (MemoryStream m = new MemoryStream()) {
d.CopyTo(m);
m.Position = 0;

using (BinaryReaderBigEndian b = new BinaryReaderBigEndian(m)) {
while (b.BaseStream.Position != b.BaseStream.Length) {
UInt32 value = b.ReadUInt32();
} } } } }

其中BinaryReaderBigEndian类实现如下:

public static class BinaryReaderBigEndian {
public BinaryReaderBigEndian(Stream stream) : base(stream) { }

public override UInt32 ReadUInt32() {
var x = base.ReadBytes(4);
Array.Reverse(x);
return BitConverter.ToUInt32(x, 0);
} }

然后,我尝试使用 ReadOnlySpan 而不是 MemoryStream 来提高性能。所以,我尝试这样做:

using (FileStream filestream = path.OpenRead()) {
using (var d = new GZipStream(filestream, CompressionMode.Decompress)) {
using (MemoryStream m = new MemoryStream()) {
d.CopyTo(m);
int position = 0;
ReadOnlySpan<byte> stream = new ReadOnlySpan<byte>(m.ToArray());

while (position != stream.Length) {
UInt32 value = stream.ReadUInt32(position);
position += 4;
} } } }

BinaryReaderBigEndian 类更改的位置:

public static class BinaryReaderBigEndian {
public override UInt32 ReadUInt32(this ReadOnlySpan<byte> stream, int start) {
var data = stream.Slice(start, 4).ToArray();
Array.Reverse(x);
return BitConverter.ToUInt32(x, 0);
} }

但是,不幸的是,我没有注意到任何改进。那么,我哪里做错了?

最佳答案

我在我的计算机上对您的代码进行了一些测量(Intel Q9400、8 GiB RAM、SSD 磁盘、Win10 x64 Home、.NET Framework 4/7/2,使用 15 MB(解压后)文件进行测试) 结果如下:

无跨度版本:520 毫秒
跨度版本:720 毫秒

所以 Span版本实际上更慢!为什么?因为new ReadOnlySpan<byte>(m.ToArray())执行整个文件的附加副本以及 ReadUInt32()执行 Span 的许多切片(切片很便宜,但不是免费的)。因为你执行了更多的工作,你不能指望性能会因为使用了 Span 而变得更好。 .

那么我们可以做得更好吗?是的。事实证明,代码中最慢的部分实际上是垃圾回收,这是由重复分配 4 字节 Array 引起的由 .ToArray() 创建来电ReadUInt32()方法。您可以通过实现 ReadUInt32() 来避免它你自己。这很简单,也不需要 Span切片。您也可以替换 new ReadOnlySpan<byte>(m.ToArray())new ReadOnlySpan<byte>(m.GetBuffer()).Slice(0, (int)m.Length); ,它执行便宜的切片而不是整个文件的副本。所以现在代码看起来像这样:

public static void Read(FileInfo path)
{
using (FileStream filestream = path.OpenRead())
{
using (var d = new GZipStream(filestream, CompressionMode.Decompress))
{
using (MemoryStream m = new MemoryStream())
{
d.CopyTo(m);
int position = 0;

ReadOnlySpan<byte> stream = new ReadOnlySpan<byte>(m.GetBuffer()).Slice(0, (int)m.Length);

while (position != stream.Length)
{
UInt32 value = stream.ReadUInt32(position);
position += 4;
}
}
}
}
}

public static class BinaryReaderBigEndian
{
public static UInt32 ReadUInt32(this ReadOnlySpan<byte> stream, int start)
{
UInt32 res = 0;
for (int i = 0; i < 4; i++)
{
res = (res << 8) | (((UInt32)stream[start + i]) & 0xff);
}
return res;
}
}

通过这些更改,我从 720 毫秒 缩短到 165 毫秒(快 4 倍)。听起来不错,不是吗?但我们可以做得更好。我们完全可以避免 MemoryStream复制和内联并进一步优化 ReadUInt32() :

public static void Read(FileInfo path)
{
using (FileStream filestream = path.OpenRead())
{
using (var d = new GZipStream(filestream, CompressionMode.Decompress))
{
var buffer = new byte[64 * 1024];

do
{
int bufferDataLength = FillBuffer(d, buffer);

if (bufferDataLength % 4 != 0)
throw new Exception("Stream length not divisible by 4");

if (bufferDataLength == 0)
break;

for (int i = 0; i < bufferDataLength; i += 4)
{
uint value = unchecked(
(((uint)buffer[i]) << 24)
| (((uint)buffer[i + 1]) << 16)
| (((uint)buffer[i + 2]) << 8)
| (((uint)buffer[i + 3]) << 0));
}

} while (true);
}
}
}

private static int FillBuffer(Stream stream, byte[] buffer)
{
int read = 0;
int totalRead = 0;
do
{
read = stream.Read(buffer, totalRead, buffer.Length - totalRead);
totalRead += read;

} while (read > 0 && totalRead < buffer.Length);

return totalRead;
}

现在只需不到 90 毫秒(比原来快 8 倍!)。没有 Span ! Span在允许执行切片并避免数组复制的情况下非常有用,但盲目使用它不会提高性能。毕竟,Span设计有 performance characteristics on par with Array , 但不是更好(并且仅在对其有特殊支持的运行时,例如 .NET Core 2.1 )。

关于c# - 如何在 C# 中快速读取二进制文件? (ReadOnlySpan 与 MemoryStream),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53416004/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com