hel*_*ist 10 .net html c# memorystream
我正在尝试尽可能快地解析二进制文件。所以这是我首先尝试做的:
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();
} } } } }
Run Code Online (Sandbox Code Playgroud)
哪里 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);
} }
Run Code Online (Sandbox Code Playgroud)
然后,我尝试使用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;
} } } }
Run Code Online (Sandbox Code Playgroud)
哪里 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);
} }
Run Code Online (Sandbox Code Playgroud)
但是,不幸的是,我没有发现任何改善。那么,我在哪里做错了?
Ňuf*_*Ňuf 10
我对计算机上的代码进行了一些测量(英特尔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。
那我们可以做得更好吗?是。事实证明,代码中最慢的部分实际上是由于重复分配in调用中Array创建的4个字节而导致的垃圾回收。您可以通过实现自己来避免这种情况。这非常容易,而且也无需切片。您也可以替换为,它执行廉价切片,而不是复制整个文件。所以现在代码看起来像这样:.ToArray()ReadUInt32()ReadUInt32()Spannew 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;
}
}
Run Code Online (Sandbox Code Playgroud)
通过这些更改,我从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;
}
Run Code Online (Sandbox Code Playgroud)
现在只需不到90毫秒(比原始速度快8倍!)。没有Span!Span在允许执行切片并避免数组复制的情况下非常有用,但不能仅仅盲目地使用它来提高性能。毕竟,它Span被设计为具有与一样的性能Array,但并不是更好(并且仅在具有特殊支持的运行时,例如.NET Core 2.1)上。
| 归档时间: |
|
| 查看次数: |
1626 次 |
| 最近记录: |