Skip to content

Commit

Permalink
[feat]新增SpanWriter/SpanReader,用于Span结构的读写,逐步替代Binary二进制序列化,实现零GC
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhy committed Aug 27, 2024
1 parent b5c9564 commit ac7689b
Show file tree
Hide file tree
Showing 5 changed files with 540 additions and 4 deletions.
21 changes: 19 additions & 2 deletions NewLife.Core/Data/Packet.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using NewLife.Collections;

Expand Down Expand Up @@ -81,6 +82,24 @@ public Packet(Stream stream)
// 必须确保数据流位置不变
if (count > 0) stream.Seek(-count, SeekOrigin.Current);
}

/// <summary>从Span实例化</summary>
/// <param name="span"></param>
public Packet(Span<Byte> span) => Set(span.ToArray());

/// <summary>从Memory实例化</summary>
/// <param name="memory"></param>
public Packet(Memory<Byte> memory)
{
if (MemoryMarshal.TryGetArray<Byte>(memory, out var segment))
{
Set(segment.Array!, segment.Offset, segment.Count);
}
else
{
Set(memory.ToArray());
}
}
#endregion

#region 索引
Expand Down Expand Up @@ -347,7 +366,6 @@ public IList<ArraySegment<Byte>> ToSegments()
return list;
}

#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
/// <summary>转为Span</summary>
/// <returns></returns>
public Span<Byte> AsSpan()
Expand All @@ -365,7 +383,6 @@ public Memory<Byte> AsMemory()

return new Memory<Byte>(ToArray());
}
#endif

/// <summary>获取封包的数据流形式</summary>
/// <returns></returns>
Expand Down
63 changes: 63 additions & 0 deletions NewLife.Core/Extension/SpanHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;

namespace NewLife;

/// <summary>Span帮助类</summary>
public static class SpanHelper
{
/// <summary>获取字符串的字节数组</summary>
public static unsafe Int32 GetBytes(this Encoding encoding, ReadOnlySpan<Char> chars, Span<Byte> bytes)
{
fixed (Char* chars2 = &MemoryMarshal.GetReference(chars))
{
fixed (Byte* bytes2 = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetBytes(chars2, chars.Length, bytes2, bytes.Length);
}
}
}

/// <summary>获取字节数组的字符串</summary>
public static unsafe String GetString(this Encoding encoding, ReadOnlySpan<Byte> bytes)
{
if (bytes.IsEmpty) return String.Empty;

#if NET45
return encoding.GetString(bytes.ToArray());
#else
fixed (Byte* bytes2 = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetString(bytes2, bytes.Length);
}
#endif
}

/// <summary>写入Memory到数据流</summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static Task WriteAsync(this Stream stream, ReadOnlyMemory<Byte> buffer, CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(buffer, out var segment))
return stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken);

var array = ArrayPool<Byte>.Shared.Rent(buffer.Length);
buffer.Span.CopyTo(array);

var writeTask = stream.WriteAsync(array, 0, buffer.Length, cancellationToken);
return Task.Run(async () =>
{
try
{
await writeTask.ConfigureAwait(false);
}
finally
{
ArrayPool<Byte>.Shared.Return(array);
}
});
}
}
5 changes: 3 additions & 2 deletions NewLife.Core/NewLife.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\Doc\newlife.snk</AssemblyOriginatorKeyFile>
<NoWarn>1701;1702;NU5104;NU1505;NETSDK1138;CS7035</NoWarn>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<PackageId>$(AssemblyName)</PackageId>
Expand Down Expand Up @@ -61,8 +62,8 @@
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" IsImplicitlyDefined="true" TargetingPackVersion="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'!='netstandard2.1'">
<!--<PackageReference Include="System.Management" Version="6.0.0" />-->
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
Expand Down
214 changes: 214 additions & 0 deletions NewLife.Core/Serialization/SpanReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace NewLife.Serialization;

/// <summary>Span读取器</summary>
/// <param name="span"></param>
public ref struct SpanReader(Span<Byte> span)
{
#region 属性
private readonly Span<Byte> _span = span;

private Int32 _index;
/// <summary>已写入字节数</summary>
public Int32 WrittenCount => _index;

/// <summary>总容量</summary>
public Int32 Capacity => _span.Length;

/// <summary>空闲容量</summary>
public Int32 FreeCapacity => _span.Length - _index;

/// <summary>是否小端字节序。默认true</summary>
public Boolean IsLittleEndian { get; set; } = true;
#endregion

#region 基础方法
/// <summary>告知有多少数据已写入缓冲区</summary>
/// <param name="count"></param>
public void Advance(Int32 count)
{
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (_index > _span.Length - count) throw new ArgumentOutOfRangeException(nameof(count));

_index += count;
}

/// <summary>返回要写入到的Span,其大小按 sizeHint 参数指定至少为所请求的大小</summary>
/// <param name="sizeHint"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Span<Byte> GetSpan(Int32 sizeHint = 0)
{
if (sizeHint > FreeCapacity) throw new ArgumentOutOfRangeException(nameof(sizeHint));

return _span[.._index];
}
#endregion

#region 读取方法
/// <summary>确保缓冲区中有足够的空间。</summary>
/// <param name="size">需要的字节数。</param>
private void EnsureSpace(Int32 size)
{
if (_index + size > _span.Length)
throw new InvalidOperationException("Not enough data to read.");
}

/// <summary>读取单个字节</summary>
/// <returns></returns>
public Byte ReadByte()
{
var size = sizeof(Byte);
EnsureSpace(size);
var result = _span[_index];
_index += size;
return result;
}

/// <summary>读取Int16整数</summary>
/// <returns></returns>
public Int16 ReadInt16()
{
var size = sizeof(Int16);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadInt16LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadInt16BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取UInt16整数</summary>
/// <returns></returns>
public UInt16 ReadUInt16()
{
var size = sizeof(UInt16);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadUInt16LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadUInt16BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取Int32整数</summary>
/// <returns></returns>
public Int32 ReadInt32()
{
var size = sizeof(Int32);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadInt32LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadInt32BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取UInt32整数</summary>
/// <returns></returns>
public UInt32 ReadUInt32()
{
var size = sizeof(UInt32);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadUInt32LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadUInt32BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取Int64整数</summary>
/// <returns></returns>
public Int64 ReadInt64()
{
var size = sizeof(Int64);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadInt64LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadInt64BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取UInt64整数</summary>
/// <returns></returns>
public UInt64 ReadUInt64()
{
var size = sizeof(UInt64);
EnsureSpace(size);
var result = IsLittleEndian ?
BinaryPrimitives.ReadUInt64LittleEndian(_span.Slice(_index, size)) :
BinaryPrimitives.ReadUInt64BigEndian(_span.Slice(_index, size));
_index += size;
return result;
}

/// <summary>读取单精度浮点数</summary>
/// <returns></returns>
public unsafe Single ReadSingle()
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
return BitConverter.Int32BitsToSingle(ReadInt32());
#else
var result = ReadInt32();
return Unsafe.ReadUnaligned<Single>(ref Unsafe.As<Int32, Byte>(ref result));
#endif
}

/// <summary>读取双精度浮点数</summary>
/// <returns></returns>
public Double ReadDouble()
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
return BitConverter.Int64BitsToDouble(ReadInt64());
#else
var result = ReadInt64();
return Unsafe.ReadUnaligned<Double>(ref Unsafe.As<Int64, Byte>(ref result));
#endif
}

/// <summary>读取字符串</summary>
/// <param name="length"></param>
/// <param name="encoding"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public String ReadString(Int32 length, Encoding encoding)
{
EnsureSpace(length);

var result = encoding.GetString(_span.Slice(_index, length));
_index += length;
return result;
}

/// <summary>读取字节数组</summary>
/// <param name="length"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public Span<Byte> ReadBytes(Int32 length)
{
EnsureSpace(length);

var result = _span.Slice(_index, length);
_index += length;
return result;
}

/// <summary>读取结构体</summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Read<T>() where T : struct
{
var size = Unsafe.SizeOf<T>();
EnsureSpace(size);

var result = MemoryMarshal.Read<T>(_span.Slice(_index));
_index += size;
return result;
}
#endregion
}
Loading

0 comments on commit ac7689b

Please sign in to comment.