本文将.netcore新出现的与Buffer操做相关的类型进行简单分析与讲解,因为资料有限,一些看法为我的看法,可能不是很准确。这些新类型将包括BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>数组
在网络传输中,最小单位是byte,不少场景,咱们须要将int long short等类型与byte[]相互转换。好比,将int转换为BigEndian的4个字节,在过去,咱们很容易就想到BitConverter,但BitConverter设计得不够好友,BitConverter.GetBytes(int value)获得的byte[]的字节顺序永远与主机的字节顺序同样,咱们不得再也不根据BitConverter的IsLittleEndian属性判断是否须要对获得byte[]进行转换字节顺序,而BinaryPrimitives的Api设计为严格区分Endian,每一个Api都指定了目标Endian。网络
BitConverter异步
var intValue = 1; var bytes = BitConverter.GetBytes(intValue); if (BitConverter.IsLittleEndian == true) { Array.Reverse(bytes); }
BinaryPrimitives工具
var intValue = 1; var bytes = new byte[sizeof(int)]; BinaryPrimitives.WriteInt32BigEndian(bytes, intValue);
Span
读写代码测试
public class DemoContext { private byte[] array = new byte[1024]; [Benchmark] public void ByteArray() { for (var i = 0; i < array.Length; i++) { array[i] = array[i]; } } [Benchmark] public void ByteSpan() { var span = array.AsSpan(); for (var i = 0; i < span.Length; i++) { span[i] = span[i]; } } [Benchmark] unsafe public void BytePointer() { fixed (byte* pointer = &array[0]) { for (var i = 0; i < array.Length; i++) { *(pointer + i) = *(pointer + i); } } } }
Benchmark报告this
| Method | Mean | Error | StdDev | |------------ |---------:|--------:|--------:| | ByteArray | 577.4 ns | 9.07 ns | 8.48 ns | | ByteSpan | 323.8 ns | 0.87 ns | 0.81 ns | | BytePointer | 499.4 ns | 4.09 ns | 3.82 ns |
若是尝试将Span<>做为全局变量,或在异步方法声明为变量,你会获得编译器的错误,缘由不在本文讲解范围内,而Memory<>类型能够知足这些需求,Memory<>提供了用于数据读写的Span属性,这个Span属性是每将获取时都有一些计算,因此咱们应该尽可能避免屡次获取它的Span属性。spa
合理的获取Span.net
var span = memory.Span; for (var i = 0; i < span.Length; i++) { span[i] = span[i]; }
不合理的获取Span设计
for (var i = 0; i < memory.Length; i++) { memory.Span[i] = memory.Span[i]; }
Benchmark报告
| Method | Mean | Error | StdDev | |------------ |-----------:|---------:|---------:| | ByteMemory1 | 325.8 ns | 1.03 ns | 0.97 ns | | ByteMemory2 | 3,344.9 ns | 11.91 ns | 11.14 ns |
ArrayPool<>用于解决频繁申请内存和释放内存致使GC压力过大的场景,好比System.Text.Json在序列对象时为utf8的byte[]时,事先是没法计算最终byte[]的长度的,过程当中可能要不断申请和调整缓冲区的大小。在没有ArrayPool加持的状况下,高频次的序列化,则会生产高频建立byte[]的过程,随之GC压力也会增大。ArrayPool的设计逻辑是,从pool申请一个指定最小长度的缓冲区,缓冲区在不须要的时候,将其返回到pool里,待以重复利用。
var pool = ArrayPool<byte>.Shared; var buffer = pool.Rent(1024); // 开始利用buffer // ... // 使用结束 pool.Return(buffer);
Rent用于申请,其实是租赁,Return是归还,返回到池中。咱们可使用IDisposable接口来包装Return功能,使用上更方便一些:
/// <summary> /// 定义数组持有者的接口 /// </summary> /// <typeparam name="T"></typeparam> public interface IArrayOwner<T> : IDisposable { /// <summary> /// 获取持有的数组 /// </summary> T[] Array { get; } /// <summary> /// 获取数组的有效长度 /// </summary> int Count { get; } } /// <summary> /// 表示共享的数组池 /// </summary> public static class ArrayPool { /// <summary> /// 租赁数组 /// </summary> /// <typeparam name="T">元素类型</typeparam> /// <param name="minLength">最小长度</param> /// <returns></returns> public static IArrayOwner<T> Rent<T>(int minLength) { return new ArrayOwner<T>(minLength); } /// <summary> /// 表示数组持有者 /// </summary> /// <typeparam name="T"></typeparam> [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(ArrayOwnerDebugView<>))] private class ArrayOwner<T> :IDisposable, IArrayOwner<T> { /// <summary> /// 获取持有的数组 /// </summary> public T[] Array { get; } /// <summary> /// 获取数组的有效长度 /// </summary> public int Count { get; } /// <summary> /// 数组持有者 /// </summary> /// <param name="minLength"></param> public ArrayOwner(int minLength) { this.Array = ArrayPool<T>.Shared.Rent(minLength); this.Count = minLength; } /// <summary> /// 归还数组 /// </summary> Public void Dispose() { ArrayPool<T>.Shared.Return(this.Array); } } /// <summary> /// 调试视图 /// </summary> /// <typeparam name="T"></typeparam> private class ArrayOwnerDebugView<T> { [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get; } /// <summary> /// 调试视图 /// </summary> /// <param name="owner"></param> public ArrayOwnerDebugView(IArrayOwner<T> owner) { this.Items = owner.Array.AsSpan(0, owner.Count).ToArray(); } } }
改造以后的使用
using var buffer = ArrayPool.Rent<byte>(1024); // 尽情的使用buffer吧,自动回收
Memorypool<>本质上仍是使用了ArrayPool<>,Memorypool只提供了Rent功能,返回一个IMomoryOwner<>,对其Dispose等同于Return过程,使用方式和咱们上面改造过的ArrayPool静态类的使用方式是同样的。
MemoryMarshal是一个工具类,相似于咱们指针操做时经常用到的Marshal类,它操做一些更底层的Span或Memory操做,好比提供将不一样基元类型的Span相互转换等。
获取Span的指针
var span = new Span<byte>(new byte[] { 1, 2, 3, 4 }); ref var p0 = ref MemoryMarshal.GetReference(span); fixed (byte* pointer = &p0) { Debug.Assert(span[0] == *pointer); }
Span泛型参数类型转换
Span<int> intSpan = new Span<int>(new int[] { 1024 }); Span<byte> byteSpan = MemoryMarshal.AsBytes(intSpan);
ReadonlyMemory<>转换为Memory
// 至关于给ReadonlyMemory移除只读功能 Memory<T> MemoryMarshal.AsMemory<T>(ReadonlyMemory<T> readonly)